i'm really new to dealing with CGContexts and i'm just trying to get my head around things by following a simple touch paint & sketch tutorial (http://blog.effectiveui.com/?p=8105)
I have hit a brick wall when it's come to changing the background colour of my CGContext though.
I'm initiating the conext like this:
- (BOOL) initContext:(CGSize)size {
int bitmapBytesPerRow;
bitmapBytesPerRow = (size.width * 4);
cacheContext = CGBitmapContextCreate (nil, size.width, size.height, 8, bitmapBytesPerRow, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipLast);
CGContextSetRGBFillColor(cacheContext, 1, 1, 1, 1);
return YES;
}
and changing stroke colours and widths like this:
UIColor *color = [UIColor whiteColor];
CGContextSetStrokeColorWithColor(cacheContext, [color CGColor]);
CGContextSetLineCap(cacheContext, kCGLineCapRound);
CGContextSetLineWidth(cacheContext, 4);
but when i try to change the background colour from black (either in the init or in the drawing/stroke set-up parts) using CGContextSetRGBFillColor(cacheContext, 1, 1, 1, 1); there is no effect.
Is anyone able to point me in the right direction of either a better/correct place to put that call, or the correct call to use? Many thanks for your time!
You can't just set the color and expect the context to fill with the new color. You need to actually draw that color into the context. In the init method, after setting the fill color, try using something like
CGContextFillRect(cacheContext, (CGRect){CGPointZero, size});
On a related note, context don't have a black color by default. They have a transparent color. Now, if your context doesn't have an alpha channel, then this will end up being black, but if your context does have an alpha channel and you're seeing black, it's because you're rendering the output on top of something black (or, alternately, you're putting the transparent image into a layer that has the opaque flag set to true and CoreAnimation ends up drawing it on top of black).
Related
I'm replacing the background view of a UITableViewCell with my own custom subclass of UIView, in which I override the drawRect method with my own, which creates a multi-colored and changing background.
The problem is that when the TableViewCell is selected, the graphics under are completely hidden and it looks odd. I need to create a custom selectedBackgroundView in order to fix this. The problem is that that view needs to create a blue gradient tint over the graphics already there, and I don't know how to draw a CGRect or something similar that is partially transparent.
// Write this In your - (void)drawRect:(CGRect)rect
// To draw semi transparent Square
// Create Current Context To Draw
CGContextRef context = UIGraphicsGetCurrentContext();
UIBezierPath *square = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 100)];
// following method will fill red color with alpha 0.4 last one in parameter list
// first 3 are Red, Green and Blue Respectively.
CGContextSetRGBFillColor(context, 1, 0, 0, 0.4);
[square fill];
[square stroke];
I'm writing some lines of code to get acquainted with the basics of Quartz 2d.
I am trying to draw and image an then clear it through the kCGBlendModeClear blend mode. Here's the code of my UIView subclass code, whose background color is set to orange through IB:
- (void)drawRect:(CGRect)rect {
UIImage *brush = [UIImage imageNamed:#"brush.png"] ;
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(ctx,kCGBlendModeNormal );
CGContextDrawImage(ctx, CGRectMake(100, 100, 26, 25), [brush CGImage]);
CGContextSetBlendMode(ctx, kCGBlendModeClear);
CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextFillEllipseInRect(ctx, CGRectMake(110, 110, 5, 5)); // HERE!
}
Reading the docs and this question I thought that line marked HERE would produce a hole in the image I had previously drawn. Instead it creates a black circle on it (should be orange).
To debug, I tried adding my custom view over an orange uiview. This time my custom view has a black background. The hole of line HERE is correct, but I wonder why the black color of the view. Even more strangely, if I do myView.backgroundColor I can set a background color (shouldn't this be overridden by my drawRect implementation?).
I am clearly missing some basics of Quartz, can anyone help?
Davide
Couple of things.
First, the Porter-Duff blend modes are only guaranteed to work in bitmap-based contexts. (i.e., contexts created by CGBitmapContextCreate)
Second, kCGBlendModeClear is defined as R = 0 — it doesn't even check src or dst pixels. So even though it's not guaranteed to work here, it appears that it is working correctly.
(see CGContext.h for better explanation than the docs give.)
For kCGBlendModeClear to work the view must not be opaque and must not have a background color set.
In addition, the following line is unnecessary, because kCGBlendModeClear does not care about the fill color:
CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor);
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.
I got white backgrounds working (using the default GLPaint code), by just changing the clear colour in the erase method ie,
- (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 brush image seem to just work.
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! =)
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;
}
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.
Actually this works even better:
glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);
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.
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.
I have a small image from a database and the image's average color need to be altered slightly.
It's a CGImageRef and I thought of creating a CGContext, drawing the image to this context, then subsequently changing the bitmap data somehow and finally rendering it.
But how can I alter the color information?
Thanks for your help!
Check out this Apple Q&A on pixel data manipulation for details on how you might go about that.
Draw a color onto the object like this:
// first draw image
[self.image drawInRect:rect];
// prepare the context to draw into
CGContextRef context = UIGraphicsGetCurrentContext();
// set the blend mode and draw rectangle on top of image
CGContextSetBlendMode(context, kCGBlendModeColor);
CGContextClipToMask(context, self.bounds, image.CGImage); // this restricts drawing to within alpha channel
CGContextSetRGBFillColor(context, 0.75, 0.0, 0.0, 1.0); // this is your color, a light reddish tint
CGContextFillRect(context, rect);
I put this into the drawRect: method of a custom UIView. That UIView has an ivar, UIImage *image that holds the image you want to tint or color.
i want to track finger touch and draw the path on top of the imageView.
following is what i did:
In the touch event:
UIgraphicsBeginImageContext(imageView.frame.size);
[imageView.image drawInRect:CGRectMake(0,0,imageView.size.width,imageView.size.height)];
//drawing.....
imageView.image=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
however, the drawing opaque the background Image?
how to drawing the path while still can make the background available?
thanks in advance:)
If you're doing simple vector drawing, you can set a color with a non-1.0 alpha value. These shapes will then be translucent when you draw them on top of your image. For example:
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGFloat values[4] = {1.0, 0.0, 0.0, 0.5};
CGColorRef translucentRed = CGColorCreate(space, values);
CGContextSetStrokeColorWithColor(context, translucentRed);
CGContextSetFillColorWithColor(context, translucentRed)
// Do your drawing here
CGColorRelease(translucentRed);
CGColorSpaceRelease(space);
will create a translucent red color and set the current stroke and fill to use that color.
Note that overlapping drawn elements will have darker areas because of this translucency, which might be an effect that you don't want.