核心图形的三角形渐变

时间:2014-08-19 16:03:02

标签: ios objective-c macos swift core-graphics

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

任何帮助?

谢谢!

1 个答案:

答案 0 :(得分:8)

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

理论

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

Equilateral triangle

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

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

Analyzing

像素越接近角度,它所具有的分量越强,反之亦然。在这里,我们可以将主要目标分解为较小的目标:

  1. 逐个像素地绘制三角形。
  2. 对于每个像素,基于与相应角度的距离计算每个组件的值。
  3. 要解决第一个任务,我们将使用CoreGraphics位图上下文。每个像素每个8位长有4个分量。这意味着组件值可能在0到255之间变化。第四个组件是alpha通道,并且将始终等于最大值 - 255.以下是如何为顶角插值的示例:

    Interpolation example

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

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

    Defining angle-color pair

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

    Choosing point

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

    Line

    如果我们找到 d c 它们的商将等于组件的标准化值,那么可以很容易地计算出值:

    Component value http://www.sciweavers.org/tex2img.php?eq=componentValue%20=%20%5Cfrac%7Bd%7D%7Bc%7D%20*%20255&bc=White&fc=Black&im=jpg&fs=12&ff=arev&edit=0/

    找到两点之间距离的公式很简单:

    Distance Formula http://www.sciweavers.org/tex2img.php?eq=%5Csqrt%7B(x_%7B2%7D%20-%20x_%7B1%7D)%5E2%20(y_%7B2%7D%20-%20y_%7B1%7D)%5E2%7D&bc=White&fc=Black&im=jpg&fs=12&ff=arev&edit=0/

    我们可以轻松找到 d 的距离,但不能找到 c 的距离,因为我们没有交点的坐标。实际上并不是那么难。我们只需要为通过 A 的行构建line equations,并在描述三角形的另一侧并找到他们的intersection

    Line intersection

    有交点我们可以应用距离公式Distance Formula http://www.sciweavers.org/tex2img.php?eq=%5Csqrt%7B(x_%7B2%7D%20-%20x_%7B1%7D)%5E2%20(y_%7B2%7D%20-%20y_%7B1%7D)%5E2%7D&bc=White&fc=Black&im=jpg&fs=12&ff=arev&edit=0/来找到 c ,最后计算当前点的组件值。 Component value http://www.sciweavers.org/tex2img.php?eq=componentValue%20=%20%5Cfrac%7Bd%7D%7Bc%7D%20*%20255&bc=White&fc=Black&im=jpg&fs=12&ff=arev&edit=0/

    同样的流量适用于其他组件。

    代码

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

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