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);
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 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).
This question already has answers here:
Inner shadow effect on UIView layer?
(17 answers)
Closed 9 years ago.
I want to apply inner-shadow to a UILabel. I have a solution, but it's not good enough. Anyone with a better solution?
// UILabel subclass
- (void) drawTextInRect:(CGRect)rect {
CGSize myShadowOffset = CGSizeMake(0, 2);
float myColorValues[] = {255, 0, 0, 1};
CGContextRef myContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(myContext);
CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB();
CGColorRef myColor = CGColorCreate(myColorSpace, myColorValues);
CGContextSetShadowWithColor (myContext, myShadowOffset, 5, myColor);
CGContextSetBlendMode(myContext, kCGBlendModeLighten);
[super drawTextInRect:rect];
CGColorRelease(myColor);
CGColorSpaceRelease(myColorSpace);
CGContextRestoreGState(myContext);
}
I'm familiar with the layer property of UILabel, but shadow offset gives us a outer-shadow, NOT inner-shadow (unless i'm missing something).
Borrowing on Ruben's answer above, if you do the reverse ie. set your text color equal to your background color (with low alpha) and then set the shadow to be a stronger color, it creates a decent inset effect. Here's what I mean (Note: My view background is white):
cell.textLabel.textColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.5];
cell.textLabel.shadowColor = [UIColor darkGrayColor];
cell.textLabel.shadowOffset = CGSizeMake(-1.0,-1.0);
[cell.textLabel setText:#"Welcome to MyApp!"];
and this is the output
This would probably only work on very light backgrounds as I suspect it will create unwanted overlay on darker backgrounds.
You can ofcourse vary the shadowOffset to change the direction of light.
I tried to do this but finally opted to use the default shadowOffset and play with the shadowColor to give the inner drop shadow effect to the text. In small texts it gives you a good inner shadow effect. For example, if you have a grayColor background and apply a whiteColor to the shadow, then you have an acceptable inner shadow effect.
Sometimes, it's better to design those texts with graphic tools and make localized copies if needed.
Answer here : Inner Shadow in UILabel Long code but it seems to work
Our main UIView is a UIScrollView with a fixed background image (very common, obviously). In that scrollView, we have several UIViews that hold content and scroll up and down as the user scrolls (also common). Those UIViews each have their own background, a simple gradient from white to black.
The goal is to have the background gradient of those (inner) UIViews be partially opaque AND use a CGBlendMode other than "kCGBlendModeNormal" (specifically, "kCGBlendModeOverlay"). You should be able to see through to the "parent" scrollView’s fixed background image as the UIViews scroll up and down above it.
- (void)drawRect:(CGRect)rect {
gradientStart = [UIColor colorWithRed:1 green:1 blue:1 alpha:1.0];
gradientEnd = [UIColor colorWithRed:0 green:0 blue:0 alpha:1.0];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[2] = { 0.0f, 1.0f };
NSArray *colors = [NSArray arrayWithObjects:(id)gradientStart.CGColor, (id)gradientEnd.CGColor, nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)colors, locations);
CGColorSpaceRelease(colorSpace);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAlpha(context, 0.50); //this works!
CGContextSetBlendMode(context, kCGBlendModeOverlay); //doesn’t seem to do anything!
CGContextClearRect(context, rect);
CGPoint startPoint, endPoint;
startPoint.x = 0.0;
startPoint.y = 0.0;
endPoint.x = 0.0;
endPoint.y = rect.size.height;
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
[super drawRect:rect];
}
Everything works as expected except the CGContextSetBlendMode, which is ignored. We can't seem to find a way to change the blendMode of a UIView relative to what is behind it, the same way you can with alpha. Please note that this is different than building up multiple layers in a SINGLE UIView; in that case, this technique does change the blendMode of the layers "on top". We want to see through to the parent scrollView's fixed background image (as we scroll the child view up and down above it), with both an alpha and an overlay blend applied.
Here's an image showing the issue: http://img2.sbck.us/blendmode.png
Thanks in advance for your help!
I believe what you want is not possible with your current setup. On iOS, it is simply not possible for the blend mode of a view to have an effect on the stuff that is drawn under the view. You would have to draw the scroll view's background and the gradients in the same view.
This is possible, at least with two image views. It might even be possible with more general views. The approach is to implement drawRect in the parent view, and do as follows:
Determine the rect for the foreground view.
Convert the rect in the foreground view to a rect in the background view.
Begin a new graphics context.
Draw the background with the proper blend mode.
Draw the foreground with the proper blend mode.
Extract the image from the graphics context.
End the graphics context.
Use the extracted image accordingly.
This allows a foreground image to blend with a background image.
Seems like you could do this by setting the 'compositingFilter' property of your view's CALayer. The comment in CALayer.h says "A filter object used to composite the layer with its (possibly filtered) background. Default value is nil, which implies source-over compositing."
Alas, CoreImage which provides the filters is not (officially) available on iOS.
I guess your other alternative would be to use OpenGL. You could still use UIView with OpenGL after a fashion by rendering your UIView's into images which could then be used a textures.
I need to create a custom UILabel to display some dynamic multiline text. The text color is white on a black background. But the background should only be visible right behind the text to simulate the effect of an selected text area.
I started with subclassing UILabel and overriding drawTextInRect to do my own drawings.
- (void) drawTextInRect:(CGRect)rect
{
/* do some custom drawings here */
[super drawTextInRect:rect];
}
So far i could not figure out a way to compute the text-bounds do draw my background into.
Does anybody now how do do this kind of stuff? Thanks a lot.
NSString has some additions in UIKit to allow for calculating the size used to render the string, given various parameters. You can use these methods to calculate the size the UILabel needs to render the string, and then resize the UILabel to precisely this size. Look in the documentation for a method called -sizeWithFont:, all of the other variations are listed there. Make sure you use the right method to match how your UILabel is configured.
There is one caveat here, which is that on iOS 4, there is a bug where these methods actually return a slightly different size than is actually used for drawing (at least for the system font on iPhone 4's [e.g. Helvetica Neue], I don't know if this bug affects any other fonts). Unfortunately the only workaround I know of is to switch to Core Text for all your text rendering, so you may just prefer to live with this bug (if it even affects you) until Apple pushes out a software update. This bug does affect Apple's own applications so there is plenty of precedent for not handling it.
Ok. Now after some input from stackoverflow i am using this code snippet to get the textbounds but it only works fine with single line text.
- (void) drawTextInRect:(CGRect)rect
{
CGFloat lineHeight = [self.text sizeWithFont:self.font].height;
CGSize testSize = CGSizeMake(320, lineHeight * self.numberOfLines);
CGSize textSize = [self.text sizeWithFont:self.font constrainedToSize:testSize lineBreakMode:self.lineBreakMode];
//NSLog(#"drawTextInRect lineHeight %f, width %f x height %f", lineHeight, textSize.width, textSize.height);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 1.0);
CGContextAddRect(context, CGRectMake(0, 0, textSize.width, textSize.height));
CGContextFillPath(context);
CGContextRestoreGState(context);
[super drawTextInRect:rect];
}
If i have multiline text i still need some code to compute the path-information of the text and not just the bounding rect so that i can use CGContextDrawPath to draw my background. Any thoughts on that? Thanks.