ios - 带核心图形的三角形渐变

标签 ios objective-c macos swift core-graphics

我正在尝试在一个 View (一个 UIView,一个 NSView)中绘制一个像这样的三角形:
Triangle gradient
我的第一个想法是 CoreGraphics,但我找不到任何可以帮助我在任意颜色的三个点之间绘制渐变的信息。

有什么帮助吗?

谢谢!

最佳答案

实际上,使用 CoreGraphics 非常简单。您可以在下面找到呈现给定三角形的代码,但首先让我们想想如何解决这个问题。

理论

想象一下边长 w 的等边三角形。所有三个角都等于 60 度:

Equilateral triangle

每个角度代表一个像素的组成部分:红色、绿色或蓝色。

让我们分析靠近顶角的像素中绿色分量的强度:

Analyzing

像素离角度越近,分量强度就越大,反之亦然。在这里我们可以将我们的主要目标分解为更小的目标:

  1. 逐像素绘制三角形。
  2. 对于每个像素,根据与相应角度的距离计算每个分量的值。

为了解决第一个任务,我们将使用 CoreGraphics 位图上下文。每个像素将有四个分量,每个分量 8 位长。这意味着组件值可能在 0 到 255 之间变化。第四个组件是 alpha channel ,将始终等于最大值 - 255。这是如何为顶角插值的示例:

Interpolation example

现在我们需要考虑如何计算组件的值(value)。

首先,让我们为每个角度定义主色:

Defining angle-color pair

现在让我们在三角形上选择一个坐标为 (x,y) 的任意点 A:

Choosing point

接下来,我们从与红色分量相关的角度画一条线,它穿过 A 直到它与三角形的另一边相交:

Line

如果我们能找到dc,它们的商将等于组件的归一化值,因此可以很容易地计算值:

Component value
(来源:sciweavers.org)

计算两点之间距离的公式很简单:

Distance Formula
(来源:sciweavers.org)

我们可以很容易地找到 d 的距离,但不能找到 c 的距离,因为我们没有交点坐标。其实这并不难。我们只需要构建line equations对于穿过 A 的线和描述三角形对边的线并找到它们的 intersection :

Line intersection

有了交点我们可以应用距离公式Distance Formula找到 c 并最终计算当前点的分量值。 Component value

相同的流程适用于其他组件。

代码

下面是实现上述概念的代码:

+ (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;
        }
    }
}

这是一个由这段代码生成的三角形图像:

Rendered triangle

关于ios - 带核心图形的三角形渐变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25388033/

相关文章:

iphone - 调用方法时 UIView 无法正确显示

c++ - 多次从 std::cin 读取——Linux 和 Mac OS X 上的不同行为

ruby-on-rails - 如何在 Mac os 10.6 上安装 mysql gem?

macos - 对 NSTableView 列的文本字段的所有值求和

ios - UITableView 是否可以按页滚动?

ios - 可达性和reachabilityWithHostName

objective-c - 在 Xcode 的 Objective-C 中,硬编码数学运算是在编译时还是在运行时执行的?

ios - Apple Watch Extension 如何访问 NSDocumentDirectory 中的文件?

ios - Mac 和 iOS 之间的 NSData 结构

iphone - NSDate 日期返回的时间比应有的少 2 小时