【问题标题】:How to draw a color wheel in objective-c如何在objective-c中绘制色轮
【发布时间】:2011-02-24 18:33:45
【问题描述】:

我正在尝试为 iPhone 绘制一个色轮,但我无法让渐变围绕一个点旋转。我正在尝试使用渐变,但objective-c提供了一个线性渐变,它可以像这样在直线上绘制渐变:

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

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

【问题讨论】:

  • 很抱歉提出一个老问题,但是在将您的库导入我的项目时,我总是从以下行收到有关“Mach-O-Linker”的错误: MainViewController *mainViewController = [[MainViewController alloc ] 初始化];
  • 最好重新评估 UI 对于触摸这么大的指针有多糟糕。更好地考虑样本,可能在树中。但要获得真正的精度,请使用色彩空间组件滑块和预览样本。

标签: iphone objective-c


【解决方案1】:

现在可以使用 CIFilter 绘制色轮图像(从 iOS 10 开始)。更多信息in this blog post(代码是 Swift 但可以直接翻译成 Objective-C)

【讨论】:

  • 我想这是一个更好的答案。其他使用循环来实现的答案可能无法使用 GPU。我相信 CIFilter 实现的底层应该足够聪明以利用 GPU。
【解决方案2】:

仅使用 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

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

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

【讨论】:

    【解决方案3】:

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

    请注意,这可能没有最佳性能,但它应该可以帮助您入门。此外,您可以使用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];
    }
    

    【讨论】:

    • 重复计算这个浪费电池电量。很好的编码,但不是很好的用户电池。
    • @uchuugaka 这只会在视图被绘制时被调用一次,然后只有在setNeedsDisplay 被调用时才会被调用。
    • 实际上,每当调用 display 时。 setNeedsDisplay 只是设置一个标志,主循环会在需要显示的任何内容及其内部的任何内容上调用 display。 (也可能在布局下被调用)
    • 你在开玩笑吧。看日期。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      您必须按照 hotpaw2 的建议制作位图。最有效的计算方法是使用HSL color space。这将允许您创建一个函数,该函数将一个像素位置作为输入并输出一个 RGB 值,只使用一些基本的算术运算和一点点三角函数来计算它。

      如果您想沿着车轮的“辐条”使用任意颜色,那么这需要更多的工作。您需要计算每个像素的混合值,它涉及到更多的三角函数,而且速度不快。当我这样做时,我必须对其进行矢量化和并行化处理,以消除在 MacBook Pro 上重新计算位图带来的轻微(尽管可察觉)刷新延迟。在 iPhone 上,您没有这些选项,因此您将不得不忍受延迟。

      如果您需要这些技术的更详细说明,请告诉我;我很乐意为您效劳。

      【讨论】:

        【解决方案6】:

        我不知道有什么可以为您做到这一点。你可以通过drawing triangles 来近似它(黄色->红色、红色->紫色、紫色->蓝色等),然后放置一个circle mask over it. 看看CGGradient

        很抱歉,我无法提供更多帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-03-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多