OpenGL paint program based on Apple's "glPaint" on white background - how to combine?
Attempting to write a simple iPhone paint program and I am using the Apple glPaint sample as a guide. The only problem is painting doesn't work on a white background as white + color = white. I've tried different blending functions, but couldn't press on the correct combination of settings and / or brushes to make this work. I've seen similar posts about this issue, but there are no answers. Does anyone know how this might work?
Edit:
I don't even have transparency effects at the moment, if I could draw solid lines with rounded ends I would be happy.
a source to share
I have a white background working (using the standard GLPaint code) by simply changing the transparent color in the erase method i.e.
- (void) erase
{
[EAGLContext setCurrentContext:context];
// Clear the buffer
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
//glClearColor(0.0, 0.0, 0.0, 0.0);
glClearColor(1.0, 1.0, 1.0, 0.0); // Change to white
glClear(GL_COLOR_BUFFER_BIT);
// Display the buffer
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
The default blend function and the brush image seem to just work.
a source to share
Instead of adding color to the mixture, could you subtract its opposite? This is roughly how paint and light work in real life and should provide correct functionality.
Example: if the user draws in Red:255 Green:0 Blue:100 Opacity:0.5
, you have to do it with a 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, this is not what is expected as painting over blue with full red will turn black as they subtract each other.
A possible solution for this would be to combine the additive and subtractive approaches.
For example, if you added 0.5*paint.colour
and subtracted 0.5*paint.complementaryColour
, adding full red to 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 which is a combination of blue and red. You can set the additive proportion to subtractive logic to simulate how well the paint mixes.
Hope it helps! =)
a source to share
I had the same problem. The edges of the brush were darker than they should be. It turns out that apple api pre multiplies alpha into rgb channels.
So, I objected by doing a grayscale in Photoshop with only rgb and alpha values. This should look like you want your brush to be white, which is full color pigment and black, which is not color pigmentation. I load this brush as in the apple glPaint example code. Then I copy the R channel (or G or B channels as they are all equal) to the alpha channel of the texture. After that I set the RGB values to the maximum for all pixels of the brush texture.
So now your brush has an alpha channel with information about how your brush looks. and RGB are all 1.
Finally, I used the mixing function:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
And don't forget to set the color before painting.
glColor4f(1.0f,0.0f,0.0f,1.0f); //red color
Check 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 no longer needed
free(brushData1);
free(brushData);
}
return brushTexture; }
a source to share