我正在尝试在一个 View (一个 UIView,一个 NSView)中绘制一个像这样的三角形:
我的第一个想法是 CoreGraphics,但我找不到任何可以帮助我在任意颜色的三个点之间绘制渐变的信息。
有什么帮助吗?
谢谢!
最佳答案
实际上,使用 CoreGraphics 非常简单。您可以在下面找到呈现给定三角形的代码,但首先让我们想想如何解决这个问题。
理论
想象一下边长 w 的等边三角形。所有三个角都等于 60 度:
每个角度代表一个像素的组成部分:红色、绿色或蓝色。
让我们分析靠近顶角的像素中绿色分量的强度:
像素离角度越近,分量强度就越大,反之亦然。在这里我们可以将我们的主要目标分解为更小的目标:
- 逐像素绘制三角形。
- 对于每个像素,根据与相应角度的距离计算每个分量的值。
为了解决第一个任务,我们将使用 CoreGraphics 位图上下文。每个像素将有四个分量,每个分量 8 位长。这意味着组件值可能在 0 到 255 之间变化。第四个组件是 alpha channel ,将始终等于最大值 - 255。这是如何为顶角插值的示例:
现在我们需要考虑如何计算组件的值(value)。
首先,让我们为每个角度定义主色:
现在让我们在三角形上选择一个坐标为 (x,y) 的任意点 A:
接下来,我们从与红色分量相关的角度画一条线,它穿过 A 直到它与三角形的另一边相交:
如果我们能找到d和c,它们的商将等于组件的归一化值,因此可以很容易地计算值:
(来源:sciweavers.org)
计算两点之间距离的公式很简单:
(来源:sciweavers.org)
我们可以很容易地找到 d 的距离,但不能找到 c 的距离,因为我们没有交点坐标。其实这并不难。我们只需要构建line equations对于穿过 A 的线和描述三角形对边的线并找到它们的 intersection :
有了交点我们可以应用距离公式找到 c 并最终计算当前点的分量值。
相同的流程适用于其他组件。
代码
下面是实现上述概念的代码:
+ (UIImage *)triangleWithSideLength:(CGFloat)sideLength {
return [self triangleWithSideLength:sideLength scale:[UIScreen mainScreen].scale];
}
+ (UIImage *)triangleWithSideLength:(CGFloat)sideLength
scale:(CGFloat)scale {
UIImage *image = nil;
CGSize size = CGSizeApplyAffineTransform((CGSize){sideLength, sideLength * sin(M_PI / 3)}, CGAffineTransformMakeScale(scale, scale));
size_t const numberOfComponents = 4;
size_t width = ceilf(size.width);
size_t height = ceilf(size.height);
size_t realBytesPerRow = width * numberOfComponents;
size_t alignedBytesPerRow = (realBytesPerRow + 0xFF) & ~0xFF;
size_t alignedPixelsPerRow = alignedBytesPerRow / numberOfComponents;
CGContextRef ctx = CGBitmapContextCreate(NULL,
width,
height,
8,
alignedBytesPerRow,
CGColorSpaceCreateDeviceRGB(),
(CGBitmapInfo)kCGImageAlphaPremultipliedLast);
char *data = CGBitmapContextGetData(ctx);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int edge = ceilf((height - i) / sqrt(3));
if (j < edge || j > width - edge) {
continue;
}
CGFloat redNormalized = 0;
CGFloat greenNormalized = 0;
CGFloat blueNormalized = 0;
CGPoint currentTrianglePoint = (CGPoint){j / scale, (height - i) / scale};
[self calculateCurrentValuesAtGiventPoint:currentTrianglePoint
sideLength:sideLength
sideOne:&redNormalized
sideTwo:&greenNormalized
sideThree:&blueNormalized];
int32_t red = redNormalized * 0xFF;
int32_t green = greenNormalized * 0xFF;
int32_t blue = blueNormalized * 0xFF;
char *pixel = data + (j + i * alignedPixelsPerRow) * numberOfComponents;
*pixel = red;
*(pixel + 1) = green;
*(pixel + 2) = blue;
*(pixel + 3) = 0xFF;
}
}
CGImageRef cgImage = CGBitmapContextCreateImage(ctx);
image = [[UIImage alloc] initWithCGImage:cgImage];
CGContextRelease(ctx);
CGImageRelease(cgImage);
return image;
}
+ (void)calculateCurrentValuesAtGiventPoint:(CGPoint)point
sideLength:(CGFloat)length
sideOne:(out CGFloat *)sideOne
sideTwo:(out CGFloat *)sideTwo
sideThree:(out CGFloat *)sideThree {
CGFloat height = sin(M_PI / 3) * length;
if (sideOne != NULL) {
// Side one is at 0, 0
CGFloat currentDistance = sqrt(point.x * point.x + point.y * point.y);
if (currentDistance != 0) {
CGFloat a = point.y / point.x;
CGFloat b = 0;
CGFloat c = -height / (length / 2);
CGFloat d = 2 * height;
CGPoint intersection = (CGPoint){(d - b) / (a - c), (a * d - c * b) / (a - c)};
CGFloat currentH = sqrt(intersection.x * intersection.x + intersection.y * intersection.y);
*sideOne = 1 - currentDistance / currentH;
} else {
*sideOne = 1;
}
}
if (sideTwo != NULL) {
// Side two is at w, 0
CGFloat currentDistance = sqrt(pow((point.x - length), 2) + point.y * point.y);
if (currentDistance != 0) {
CGFloat a = point.y / (point.x - length);
CGFloat b = height / (length / 2);
CGFloat c = a * -point.x + point.y;
CGFloat d = b * -length / 2 + height;
CGPoint intersection = (CGPoint){(d - c) / (a - b), (a * d - b * c) / (a - b)};
CGFloat currentH = sqrt(pow(length - intersection.x, 2) + intersection.y * intersection.y);
*sideTwo = 1 - currentDistance / currentH;
} else {
*sideTwo = 1;
}
}
if (sideThree != NULL) {
// Side three is at w / 2, w * sin60 degrees
CGFloat currentDistance = sqrt(pow((point.x - length / 2), 2) + pow(point.y - height, 2));
if (currentDistance != 0) {
float dy = point.y - height;
float dx = (point.x - length / 2);
if (fabs(dx) > FLT_EPSILON) {
CGFloat a = dy / dx;
CGFloat b = 0;
CGFloat c = a * -point.x + point.y;
CGFloat d = 0;
CGPoint intersection = (CGPoint){(d - c) / (a - b), (a * d - b * c) / (a - b)};
CGFloat currentH = sqrt(pow(length / 2 - intersection.x, 2) + pow(height - intersection.y, 2));
*sideThree = 1 - currentDistance / currentH;
} else {
*sideThree = 1 - currentDistance / height;
}
} else {
*sideThree = 1;
}
}
}
这是一个由这段代码生成的三角形图像:
关于ios - 带核心图形的三角形渐变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25388033/