36 votes

Comment dessiner une roue des couleurs en Objective-C

J'essaie de dessiner une roue de couleurs pour un iPhone, mais je n'arrive pas à faire tourner le dégradé autour d'un point. J'essaie d'utiliser des dégradés mais objective-c fournit un dégradé linéaire, qui dessine un dégradé en ligne droite comme ceci : linear gradient

et un gradient radial, qui dessine le gradient en partant du point et en rayonnant dans toutes les directions comme ceci : radial gradient

Je veux dessiner un gradient linéaire qui tourne autour d'un point comme ceci :

colorwheel

1 votes

Publié aujourd'hui code.google.com/p/ios-color-wheel

0 votes

Désolé de remettre sur le tapis une vieille question, mais lorsque j'importe votre bibliothèque dans mon projet, je reçois toujours une erreur de "Mach-O-Linker" à partir de la ligne suivante : MainViewController *mainViewController = [[MainViewController alloc] init] ;

0 votes

Il vaut mieux réévaluer à quel point cette interface est mauvaise pour un pointeur aussi grand qu'une touche. Il vaut mieux envisager des nuanciers, éventuellement dans un arbre. Mais pour une réelle précision, utilisez des curseurs de composants d'espace colorimétrique et un échantillon de prévisualisation.

24voto

Daniel Dickison Points 15182

L'exemple suivant dessine une roue de couleurs HSL dans une sous-classe de UIView. Elle le fait en générant un bitmap en calculant, pour chaque pixel, la valeur de couleur correcte. Ce n'est pas exactement ce que vous essayez de faire (on dirait que la teinte varie simplement dans le cercle avec une luminance/saturation constante), mais vous devriez pouvoir l'adapter à vos besoins.

Notez que les performances ne sont peut-être pas optimales, mais cela devrait vous permettre de commencer. Vous pouvez également utiliser getColorWheelValue() pour gérer les entrées de l'utilisateur (clics/touches à une coordonnée donnée).

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

0 votes

Gaspiller l'énergie de la batterie pour calculer cela de manière répétée. Codage agréable, mais seulement pas grande batterie d'utilisateur de fir.

0 votes

@uchuugaka ceci ne sera appelé qu'une seule fois lorsque la vue sera dessinée, et ensuite seulement si setNeedsDisplay s'appelle.

0 votes

En fait, à chaque fois qu'on appelle display, setNeedsDisplay met juste un drapeau, la boucle principale va appeler display sur tout ce qui a besoin d'être affiché et sur tout ce qui se trouve à l'intérieur. (peut aussi être appelé sous layout).

18voto

Aky Points 687

Utiliser uniquement les méthodes 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

Tout ce que fait le code ci-dessus est de faire le tour du cercle en dessinant des secteurs étroits et en les remplissant de couleurs dont la teinte augmente progressivement.

Vous pourriez bien sûr faire tout le dessin directement dans le contexte graphique d'une vue avec drawRect() et ne pas avoir à créer un contexte d'image explicite.

enter image description here

4voto

hotpaw2 Points 40796

J'ai fait quelque chose comme ça en créant un grand bitmap RGBA, en colorant chaque pixel en fonction de son emplacement converti en coordonnées polaires, puis en convertissant le bitmap en image et en dessinant l'image à échelle réduite. La réduction d'échelle avait pour but d'aider la pixellisation anti-alias près du centre.

2voto

Ryan Ballantyne Points 1462

Vous devrez faire un bitmap comme suggéré par hotpaw2. Le moyen le plus efficace de calculer cela est d'utiliser la fonction Espace couleur HSL . Cela vous permettra de créer une fonction qui prend en entrée l'emplacement d'un pixel et en sort une valeur RVB, en utilisant seulement quelques opérations arithmétiques de base et un peu de trigonométrie pour la calculer.

Si vous voulez des couleurs arbitraires le long des "rayons" de la roue, eh bien, cela demande plus de travail. Vous devez calculer les valeurs de mélange pour chaque pixel, ce qui implique un peu plus de trigonométrie et n'est pas aussi rapide. Quand je l'ai fait, j'ai dû le vectoriser et le paralléliser pour supprimer le léger (bien que perceptible) délai de rafraîchissement dû au recalcul du bitmap - sur un MacBook Pro. Sur l'iPhone, vous n'avez pas ces options, vous devrez donc vivre avec ce délai.

Faites-moi savoir si vous avez besoin d'explications plus détaillées sur ces techniques ; je me ferai un plaisir de vous aider.

1voto

Stephen Furlani Points 3210

Je ne connais rien qui puisse le faire pour vous. Vous pourriez l'approximer en dessiner des triangles qui vont de (jaune->rouge, rouge->purp, purp->bleu, etc) et en plaçant ensuite un masque circulaire par-dessus. Regardez Gradient CGG .

Désolé de ne pas pouvoir vous aider davantage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X