如何在objective-c中绘制色轮

时间:2022-09-07 11:12:38

I am trying to draw a color wheel for an iPhone, but I can't get the gradient to rotate around a point. I am trying to use gradients but objective-c provides a linear gradient, which draws a gradient in a straight line like this: 如何在objective-c中绘制色轮

我正在尝试为iPhone绘制色轮,但我不能让渐变围绕一个点旋转。我试图使用渐变,但objective-c提供了一个线性渐变,它绘制一条直线的渐变,如下所示:

and a radial gradient, which draws the gradient starting at point and radiating out in all directions like this: 如何在objective-c中绘制色轮

和径向渐变,从点开始绘制渐变并向所有方向辐射,如下所示:

I want to draw a linear gradient that rotates around a point like this:

我想绘制一个围绕这样的点旋转的线性渐变:

如何在objective-c中绘制色轮

5 个解决方案

#1


23  

The following draws an HSL color wheel in a UIView subclass. It does this by generating a bitmap by computing, for each pixel, the correct color value. This is not exactly what you're trying to do (looks like it's just hue varies in the circle with a constant luminance/saturation), but you should be able to adapt it for your needs.

下面在UIView子类中绘制HSL色轮。它通过为每个像素计算正确的颜色值来生成位图来实现这一点。这并不是你想要做的(看起来它只是在恒定亮度/饱和度的圆圈中变化),但你应该能够根据你的需要进行调整。

Note that this may not have optimal performance, but it should get you started. Also, you can use getColorWheelValue() to handle user input (clicks/touches at a given coordinate).

请注意,这可能没有最佳性能,但它应该让您入门。此外,您可以使用getColorWheelValue()来处理用户输入(给定坐标处的点击/触摸)。

- (void)drawRect:(CGRect)rect
{
    int dim = self.bounds.size.width; // should always be square.
    bitmapData = CFDataCreateMutable(NULL, 0);
    CFDataSetLength(bitmapData, dim * dim * 4);
    generateColorWheelBitmap(CFDataGetMutableBytePtr(bitmapData), dim, luminance);
    UIImage *image = createUIImageWithRGBAData(bitmapData, self.bounds.size.width, self.bounds.size.height);
    CFRelease(bitmapData);
    [image drawAtPoint:CGPointZero];
    [image release];
}

void generateColorWheelBitmap(UInt8 *bitmap, int widthHeight, float l)
{
    // I think maybe you can do 1/3 of the pie, then do something smart to generate the other two parts, but for now we'll brute force it.
    for (int y = 0; y < widthHeight; y++)
    {
        for (int x = 0; x < widthHeight; x++)
        {
            float h, s, r, g, b, a;
            getColorWheelValue(widthHeight, x, y, &h, &s);
            if (s < 1.0)
            {
                // Antialias the edge of the circle.
                if (s > 0.99) a = (1.0 - s) * 100;
                else a = 1.0;

                HSL2RGB(h, s, l, &r, &g, &b);
            }
            else
            {
                r = g = b = a = 0.0f;
            }

            int i = 4 * (x + y * widthHeight);
            bitmap[i] = r * 0xff;
            bitmap[i+1] = g * 0xff;
            bitmap[i+2] = b * 0xff;
            bitmap[i+3] = a * 0xff;
        }
    }
}

void getColorWheelValue(int widthHeight, int x, int y, float *outH, float *outS)
{
    int c = widthHeight / 2;
    float dx = (float)(x - c) / c;
    float dy = (float)(y - c) / c;
    float d = sqrtf((float)(dx*dx + dy*dy));
    *outS = d;
    *outH = acosf((float)dx / d) / M_PI / 2.0f;
    if (dy < 0) *outH = 1.0 - *outH;
}

UIImage *createUIImageWithRGBAData(CFDataRef data, int width, int height)
{
    CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(data);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGImageRef imageRef = CGImageCreate(width, height, 8, 32, width * 4, colorSpace, kCGImageAlphaLast, dataProvider, NULL, 0, kCGRenderingIntentDefault);
    UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
    CGDataProviderRelease(dataProvider);
    CGColorSpaceRelease(colorSpace);
    CGImageRelease(imageRef);
    return image;
}

// Adapted from Apple sample code.  See http://en.wikipedia.org/wiki/HSV_color_space#Comparison_of_HSL_and_HSV
void HSL2RGB(float h, float s, float l, float* outR, float* outG, float* outB)
{
    float temp1, temp2;
    float temp[3];
    int i;

    // Check for saturation. If there isn't any just return the luminance value for each, which results in gray.
    if(s == 0.0)
    {
        *outR = l;
        *outG = l;
        *outB = l;
        return;
    }

    // Test for luminance and compute temporary values based on luminance and saturation 
    if(l < 0.5)
        temp2 = l * (1.0 + s);
    else
        temp2 = l + s - l * s;
    temp1 = 2.0 * l - temp2;

    // Compute intermediate values based on hue
    temp[0] = h + 1.0 / 3.0;
    temp[1] = h;
    temp[2] = h - 1.0 / 3.0;

    for(i = 0; i < 3; ++i)
    {
        // Adjust the range
        if(temp[i] < 0.0)
            temp[i] += 1.0;
        if(temp[i] > 1.0)
            temp[i] -= 1.0;


        if(6.0 * temp[i] < 1.0)
            temp[i] = temp1 + (temp2 - temp1) * 6.0 * temp[i];
        else {
            if(2.0 * temp[i] < 1.0)
                temp[i] = temp2;
            else {
                if(3.0 * temp[i] < 2.0)
                    temp[i] = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - temp[i]) * 6.0;
                else
                    temp[i] = temp1;
            }
        }
    }

    // Assign temporary values to R, G, B
    *outR = temp[0];
    *outG = temp[1];
    *outB = temp[2];
}

#2


18  

Using only UIKit methods:

仅使用UIKit方法:

//  ViewController.m; assuming ViewController is the app's root view controller
#include "ViewController.h"
@interface ViewController () 
{
    UIImage *img;
    UIImageView *iv;
}
@end

@implementation ViewController
- (void)viewDidLoad
{
    [super viewDidLoad];

    CGSize size = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height);
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width, size.height), YES, 0.0);
    [[UIColor whiteColor] setFill];
    UIRectFill(CGRectMake(0, 0, size.width, size.height));

    int sectors = 180;
    float radius = MIN(size.width, size.height)/2;
    float angle = 2 * M_PI/sectors;
    UIBezierPath *bezierPath;
    for ( int i = 0; i < sectors; i++)
    {
        CGPoint center = CGPointMake(size.width/2, size.height/2);
        bezierPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:i * angle endAngle:(i + 1) * angle clockwise:YES];
        [bezierPath addLineToPoint:center];
        [bezierPath closePath];
        UIColor *color = [UIColor colorWithHue:((float)i)/sectors saturation:1. brightness:1. alpha:1];
        [color setFill];
        [color setStroke];
        [bezierPath fill];
        [bezierPath stroke];
    }
    img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    iv = [[UIImageView alloc] initWithImage:img];
    [self.view addSubview:iv];
}
@end

Basically all that the above code does is go around the circle drawing narrow sectors filling them with colors of incrementally increasing hue.

基本上上面代码所做的就是围绕圆形绘制狭窄的扇区,用渐增色调的颜色填充它们。

You could of course do all the drawing directly into a view's graphics context with drawRect() and not have to create an explicit image context.

您当然可以使用drawRect()将所有绘图直接绘制到视图的图形上下文中,而不必创建显式图像上下文。

如何在objective-c中绘制色轮

#3


4  

I did something like this by creating a large RGBA bitmap, coloring each pixel depending on its location converted to polar coordinates, then converting the bitmap to an image and drawing the image scaled down. The scaling down was to help antialias pixelation near the center.

我做了类似的事情,创建了一个大的RGBA位图,根据转换为极坐标的位置着色每个像素,然后将位图转换为图像并绘制缩小的图像。按比例缩小是为了帮助中心附近的抗锯齿像素化。

#4


2  

You'll have to make a bitmap as suggested by hotpaw2. The most efficient way to calculate this is to use the HSL color space. That will allow you to create a function that takes as input a pixel location and spits out an RGB value, using only a few basic arithmetic operations and a little trig to calculate it.

您必须按照hotpaw2的建议制作位图。计算此问题的最有效方法是使用HSL颜色空间。这将允许您创建一个函数,该函数将像素位置作为输入并吐出RGB值,仅使用几个基本算术运算和一点点触发来计算它。

If you want arbitrary colors along the "spokes" of the wheel, well, that takes more work. You need to calculate blending values for each pixel, and it involves a fair sight more trigonometry, and isn't as fast. When I did this, I had to vectorize and parallelize it to remove the slight (though perceptible) refresh delay from recalculating the bitmap--on a MacBook Pro. On the iPhone you don't have those options, so you'll have to live with the delay.

如果你想要沿着*的“辐条”任意颜色,那么需要更多的工作。您需要为每个像素计算混合值,并且它需要更多的三角测量,并且速度不是很快。当我这样做时,我不得不对它进行矢量化和并行化,以消除MacBook Pro上重新计算位图的轻微(虽然可感知)刷新延迟。在iPhone上你没有这些选项,所以你将不得不忍受延迟。

Let me know if you need more detailed explanations of these techniques; I'll be happy to oblige.

如果您需要更详细的解释这些技术,请告诉我们;我很乐意帮忙。

#5


1  

I'm not aware of anything that will do this for you. You could approximate it by drawing triangles that go from (yellow->red, red->purp, purp->blue, etc) and then placing a circle mask over it. Look at CGGradient.

我不知道会为你做什么。您可以通过绘制三角形(黄色 - >红色,红色 - >紫色,紫色 - >蓝色等)然后在其上放置圆形面罩来近似它。看看CGGradient。

Sorry I can't be of more help.

对不起,我无法提供更多帮助。

#1


23  

The following draws an HSL color wheel in a UIView subclass. It does this by generating a bitmap by computing, for each pixel, the correct color value. This is not exactly what you're trying to do (looks like it's just hue varies in the circle with a constant luminance/saturation), but you should be able to adapt it for your needs.

下面在UIView子类中绘制HSL色轮。它通过为每个像素计算正确的颜色值来生成位图来实现这一点。这并不是你想要做的(看起来它只是在恒定亮度/饱和度的圆圈中变化),但你应该能够根据你的需要进行调整。

Note that this may not have optimal performance, but it should get you started. Also, you can use getColorWheelValue() to handle user input (clicks/touches at a given coordinate).

请注意,这可能没有最佳性能,但它应该让您入门。此外,您可以使用getColorWheelValue()来处理用户输入(给定坐标处的点击/触摸)。

- (void)drawRect:(CGRect)rect
{
    int dim = self.bounds.size.width; // should always be square.
    bitmapData = CFDataCreateMutable(NULL, 0);
    CFDataSetLength(bitmapData, dim * dim * 4);
    generateColorWheelBitmap(CFDataGetMutableBytePtr(bitmapData), dim, luminance);
    UIImage *image = createUIImageWithRGBAData(bitmapData, self.bounds.size.width, self.bounds.size.height);
    CFRelease(bitmapData);
    [image drawAtPoint:CGPointZero];
    [image release];
}

void generateColorWheelBitmap(UInt8 *bitmap, int widthHeight, float l)
{
    // I think maybe you can do 1/3 of the pie, then do something smart to generate the other two parts, but for now we'll brute force it.
    for (int y = 0; y < widthHeight; y++)
    {
        for (int x = 0; x < widthHeight; x++)
        {
            float h, s, r, g, b, a;
            getColorWheelValue(widthHeight, x, y, &h, &s);
            if (s < 1.0)
            {
                // Antialias the edge of the circle.
                if (s > 0.99) a = (1.0 - s) * 100;
                else a = 1.0;

                HSL2RGB(h, s, l, &r, &g, &b);
            }
            else
            {
                r = g = b = a = 0.0f;
            }

            int i = 4 * (x + y * widthHeight);
            bitmap[i] = r * 0xff;
            bitmap[i+1] = g * 0xff;
            bitmap[i+2] = b * 0xff;
            bitmap[i+3] = a * 0xff;
        }
    }
}

void getColorWheelValue(int widthHeight, int x, int y, float *outH, float *outS)
{
    int c = widthHeight / 2;
    float dx = (float)(x - c) / c;
    float dy = (float)(y - c) / c;
    float d = sqrtf((float)(dx*dx + dy*dy));
    *outS = d;
    *outH = acosf((float)dx / d) / M_PI / 2.0f;
    if (dy < 0) *outH = 1.0 - *outH;
}

UIImage *createUIImageWithRGBAData(CFDataRef data, int width, int height)
{
    CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(data);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGImageRef imageRef = CGImageCreate(width, height, 8, 32, width * 4, colorSpace, kCGImageAlphaLast, dataProvider, NULL, 0, kCGRenderingIntentDefault);
    UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
    CGDataProviderRelease(dataProvider);
    CGColorSpaceRelease(colorSpace);
    CGImageRelease(imageRef);
    return image;
}

// Adapted from Apple sample code.  See http://en.wikipedia.org/wiki/HSV_color_space#Comparison_of_HSL_and_HSV
void HSL2RGB(float h, float s, float l, float* outR, float* outG, float* outB)
{
    float temp1, temp2;
    float temp[3];
    int i;

    // Check for saturation. If there isn't any just return the luminance value for each, which results in gray.
    if(s == 0.0)
    {
        *outR = l;
        *outG = l;
        *outB = l;
        return;
    }

    // Test for luminance and compute temporary values based on luminance and saturation 
    if(l < 0.5)
        temp2 = l * (1.0 + s);
    else
        temp2 = l + s - l * s;
    temp1 = 2.0 * l - temp2;

    // Compute intermediate values based on hue
    temp[0] = h + 1.0 / 3.0;
    temp[1] = h;
    temp[2] = h - 1.0 / 3.0;

    for(i = 0; i < 3; ++i)
    {
        // Adjust the range
        if(temp[i] < 0.0)
            temp[i] += 1.0;
        if(temp[i] > 1.0)
            temp[i] -= 1.0;


        if(6.0 * temp[i] < 1.0)
            temp[i] = temp1 + (temp2 - temp1) * 6.0 * temp[i];
        else {
            if(2.0 * temp[i] < 1.0)
                temp[i] = temp2;
            else {
                if(3.0 * temp[i] < 2.0)
                    temp[i] = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - temp[i]) * 6.0;
                else
                    temp[i] = temp1;
            }
        }
    }

    // Assign temporary values to R, G, B
    *outR = temp[0];
    *outG = temp[1];
    *outB = temp[2];
}

#2


18  

Using only UIKit methods:

仅使用UIKit方法:

//  ViewController.m; assuming ViewController is the app's root view controller
#include "ViewController.h"
@interface ViewController () 
{
    UIImage *img;
    UIImageView *iv;
}
@end

@implementation ViewController
- (void)viewDidLoad
{
    [super viewDidLoad];

    CGSize size = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height);
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width, size.height), YES, 0.0);
    [[UIColor whiteColor] setFill];
    UIRectFill(CGRectMake(0, 0, size.width, size.height));

    int sectors = 180;
    float radius = MIN(size.width, size.height)/2;
    float angle = 2 * M_PI/sectors;
    UIBezierPath *bezierPath;
    for ( int i = 0; i < sectors; i++)
    {
        CGPoint center = CGPointMake(size.width/2, size.height/2);
        bezierPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:i * angle endAngle:(i + 1) * angle clockwise:YES];
        [bezierPath addLineToPoint:center];
        [bezierPath closePath];
        UIColor *color = [UIColor colorWithHue:((float)i)/sectors saturation:1. brightness:1. alpha:1];
        [color setFill];
        [color setStroke];
        [bezierPath fill];
        [bezierPath stroke];
    }
    img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    iv = [[UIImageView alloc] initWithImage:img];
    [self.view addSubview:iv];
}
@end

Basically all that the above code does is go around the circle drawing narrow sectors filling them with colors of incrementally increasing hue.

基本上上面代码所做的就是围绕圆形绘制狭窄的扇区,用渐增色调的颜色填充它们。

You could of course do all the drawing directly into a view's graphics context with drawRect() and not have to create an explicit image context.

您当然可以使用drawRect()将所有绘图直接绘制到视图的图形上下文中,而不必创建显式图像上下文。

如何在objective-c中绘制色轮

#3


4  

I did something like this by creating a large RGBA bitmap, coloring each pixel depending on its location converted to polar coordinates, then converting the bitmap to an image and drawing the image scaled down. The scaling down was to help antialias pixelation near the center.

我做了类似的事情,创建了一个大的RGBA位图,根据转换为极坐标的位置着色每个像素,然后将位图转换为图像并绘制缩小的图像。按比例缩小是为了帮助中心附近的抗锯齿像素化。

#4


2  

You'll have to make a bitmap as suggested by hotpaw2. The most efficient way to calculate this is to use the HSL color space. That will allow you to create a function that takes as input a pixel location and spits out an RGB value, using only a few basic arithmetic operations and a little trig to calculate it.

您必须按照hotpaw2的建议制作位图。计算此问题的最有效方法是使用HSL颜色空间。这将允许您创建一个函数,该函数将像素位置作为输入并吐出RGB值,仅使用几个基本算术运算和一点点触发来计算它。

If you want arbitrary colors along the "spokes" of the wheel, well, that takes more work. You need to calculate blending values for each pixel, and it involves a fair sight more trigonometry, and isn't as fast. When I did this, I had to vectorize and parallelize it to remove the slight (though perceptible) refresh delay from recalculating the bitmap--on a MacBook Pro. On the iPhone you don't have those options, so you'll have to live with the delay.

如果你想要沿着*的“辐条”任意颜色,那么需要更多的工作。您需要为每个像素计算混合值,并且它需要更多的三角测量,并且速度不是很快。当我这样做时,我不得不对它进行矢量化和并行化,以消除MacBook Pro上重新计算位图的轻微(虽然可感知)刷新延迟。在iPhone上你没有这些选项,所以你将不得不忍受延迟。

Let me know if you need more detailed explanations of these techniques; I'll be happy to oblige.

如果您需要更详细的解释这些技术,请告诉我们;我很乐意帮忙。

#5


1  

I'm not aware of anything that will do this for you. You could approximate it by drawing triangles that go from (yellow->red, red->purp, purp->blue, etc) and then placing a circle mask over it. Look at CGGradient.

我不知道会为你做什么。您可以通过绘制三角形(黄色 - >红色,红色 - >紫色,紫色 - >蓝色等)然后在其上放置圆形面罩来近似它。看看CGGradient。

Sorry I can't be of more help.

对不起,我无法提供更多帮助。