views:

559

answers:

6

Trying to write a simple paint program for iPhone, and I'm using Apple's glPaint sample as a guide. The only problem is, painting doesn't work on a white background, since white + colour = white. I've tried different blending functions, but haven't been able to hit on the right combination of settings and/or brushes to make this work. I've seen similar posts about this problem but no answers. Does anyone know how this might work?

Edit: I don't even really transparency effects, at this point if I could draw solid lines with rounded ends I'd be happy :P

+1  A: 

Rather than adding the colour to the blend, could you subtract its opposite? This is roughly how paint and light work in real life, and should give the correct functionality.

Ex: If the user is painting in Red:255 Green:0 Blue:100 Opacity:0.5, you should do this to the pixel:

pixel.red -= (255-paint.red) * paint.opacity;      //Subtract 0
pixel.green -= (255-paint.green) * paint.opacity;  //Subtract 127.5
pixel.blue -= (255-paint.blue) * paint.opacity;    //Subtract 77.5

EDIT: As you pointed out, it is not what is expected, as painting over full blue with full red will go to black, since they subtract each other.

A possible fix to this would be to combine the additive and subtractive approach.

For instance if you added 0.5*paint.colour and subtracted 0.5*paint.complementaryColour, adding full red to full blue would result in:

newPixel.red -> 0 +  127.5 -  0  = 127.5
newPixel.green -> 0 + 0 - 127.5  = 0     //Cap it off, or invent new math =D
newPixel.blue -> 255 + 0 - 127.5 = 127.5

As you can see, this results in a nice purple colour, which is the combination of blue and red. You can tweak the proportion of additive to subtractive logic to simulate how well the paint mixes.

Hope that helps! =)

Chris Cooper
Good thought... I did try this already, but couldn't get it to work, for instance if I paint with 100% opacity in red (255,0,0) and then paint on top of it in blue (0,0,255), you'd expect it to be blue, but it's actually black (0,0,0)
Adam
@Adam: Hmm. This is true. I will edit.
Chris Cooper
another good thought! i played around with this too, but opengl brushes are funny - outside of the shape area is interpreted as black, and 50% white background + 50% black = nicely coloured shape inside a grey box. i'll keep experimenting and update if i find the right mix... but in theory i think it's a good answer so i'll give you a big checkmark
Adam
Did you have any success? I'm also stuck with this blending. Actually I have same result as author of this post: http://stackoverflow.com/questions/1757914/glpaint-with-white-background
OgreSwamp
A: 

i ran into something similar. the following blending function call solved it for me without any complicated math.

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);

Add this before your glDraw calls and you should be able to draw with any texture as brush.

Sanyam Bhasin
Does this work on a white background? I tried these settings, and I had the same problem, that is I was just adding a color on top of white, which made white.
Adam
Yea I did a glClear setting everything to white, then these settings let me draw on the white background with any color without merging it with white.
Sanyam Bhasin
I used this code. Probably I have bad brush texture, but in my case I have gray contour around brush texture.. Can you tell me plz what kind of texture for the brush do you use? As I understand for white background brush texture should be white on the transparent background. Is it right?
OgreSwamp
A: 

Actually this works even better:

glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);
Sanyam Bhasin
could you send me the code? i tried both of those, but it still didn't work... either it was just white, or the edges of the brush were wrong, i've tried like 100 combinations :(
Adam
Why dont you post your code, lets debug that. My code is being used under an NDA.
Sanyam Bhasin
A: 

The sample code GLPaint is used glBlendFunc(GL_SRC_ALPHA, GL_ONE) and glBlendFunc(GL_SRC_ALPHA, GL_ONE) mode in the function - (id)initWithCoder:(NSCoder*)coder.So while the color is white,all the other colors won't be see. I want to solve it too.

A: 
glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);

This is best answer.And you have to charge the brush picture.The picture must be a alpha backgroud and while ellipse.

A: 

Yea I had the same issue. The edges of the brush were darker than they should be. It turns out that apple's api pre multiplies the alpha into the rgb channels.

So I countered that by making a grayscale brush in photoshop with just rgb values and no alpha channel. This should look the way you want your brush to be with white representing full color pigmentaton and black representing no color pigmentation. I load that brush the way its done in apple's glPaint sample code. I then copy the R-channel (or G or B channels as they all are equal) into the alpha channel of the texture. Following that I set the R-G-B values to maximum for all pixels of the brush texture.

So now your brush has an alpha channel with data of how exactly your brush looks. and the RGB are all 1.

Finally I used the blending function:

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

And dont forget to set the color before you draw.

glColor4f(1.0f,0.0f,0.0f,1.0f); //red color

Check out the code below, see if it works for you:

-(GLuint) createBrushWithImage: (NSString*)brushName
{

    GLuint            brushTexture;
    CGImageRef      brushImage;
CGContextRef    brushContext;
GLubyte         *brushData,*brushData1;
size_t          width, height;

//initialize brush image
brushImage = [UIImage imageNamed:brushName].CGImage;

// Get the width and height of the image
width = CGImageGetWidth(brushImage);
height = CGImageGetHeight(brushImage);

//make the brush texture and context
if(brushImage) {
    // Allocate  memory needed for the bitmap context
    brushData = (GLubyte *) calloc(width * height *4, sizeof(GLubyte));
    // We are going to use brushData1 to make the final texture
    brushData1 = (GLubyte *) calloc(width * height *4, sizeof(GLubyte));
    // Use  the bitmatp creation function provided by the Core Graphics framework. 

    brushContext = CGBitmapContextCreate(brushData, width, height, 8, width *4 , CGImageGetColorSpace(brushImage), kCGImageAlphaPremultipliedLast);
    // After you create the context, you can draw the  image to the context.
    CGContextDrawImage(brushContext, CGRectMake(0.0f, 0.0f, (CGFloat)width, (CGFloat)height), brushImage);
    // You don't need the context at this point, so you need to release it to avoid memory leaks.
    CGContextRelease(brushContext);

    for(int i=0; i< width*height;i++){
                    //set the R-G-B channel to maximum
        brushData1[i*4] = brushData1[i*4+1] =brushData1[i*4+2] =0xff;
                    //store originally loaded brush image in alpha channel
        brushData1[i*4+3] = brushData[i*4];
    }

    // Use OpenGL ES to generate a name for the texture.
    glGenTextures(1, &brushTexture);
    // Bind the texture name. 
    glBindTexture(GL_TEXTURE_2D, brushTexture);
    // Set the texture parameters to use a minifying filter and a linear filer (weighted average)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    // Specify a 2D texture image, providing the a pointer to the image data in memory
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, brushData1);
    // Release  the image data; it's no longer needed
    free(brushData1);
    free(brushData);
}

return brushTexture; }

Sanyam Bhasin