Draw CGImage in UITableView - iphone

So, I have a custom cell and I need to draw all images as CGImage in tableView, but I can't get it working. I have created a test project and tested the code with simple views. Everything worked perfect, and when I copypasted the same code to my custom cell it stopped working. Here is the code:
-(void)drawRect:(CGRect)rect {
CGRect contentRect = self.contentView.bounds;
CGFloat boundsX = contentRect.origin.x;
UIImage *karmaImage = [UIImage imageNamed:#"karma.png"];
[self drawImage:karmaImage withRect:CGRectMake(boundsX + 255, 16, 14, 14)];
}
-(void)drawImage:(UIImage *)image withRect:(CGRect)rect {
CGImageRef imageRef = CGImageRetain(image.CGImage);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, rect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, rect, imageRef);
}
Any solutions?

Apple recommends to add your custom view to the UITableViewCell's contentView instead of changing UITableViewCell itself. See TimeZoneCell for an example.

In your drawRect method, you should probably call [super drawRect:rect];

OK, the problem was that contentView of the cell with custom background color was hiding the image. Here is the right code:
-(void) drawRect:(CGRect)rect{
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor colorWithRed:(232.0/255.0) green:(232.0/255.0) blue:(232.0/255.0) alpha:1.0].CGColor);
CGContextFillRect(context, rect);
UIImage *karmaImg = [UIImage imageNamed:#"karma.png"];
[self drawImage:karmaImg withContext:context atPoint:CGPointMake(boundsX + 255, 16)];
}
-(void)drawImage:(UIImage *)image withContext:(CGContextRef)context atPoint:(CGPoint)point {
if(image) {
CGContextDrawImage(context, CGRectMake(point.x, point.y, image.size.width, image.size.height), image.CGImage);
} else {
NSLog(#"Error: Image failed to load.");
}
}

Related

How to save the UIImage after multi point cropping the image?

I want to multi point crop on the image(See the image). Its working fine. My problem is after crop the image the how to I save UIImage. I am using the CAShapeLayer for crop image. Below code using for multi point crop.
- (void)multiPointCrop:(CGPoint)cropPoint
{
UIBezierPath *aPath = [UIBezierPath bezierPath];
[aPath moveToPoint:cropPoint];
for (NSString *pointString in self.touchPoints) {
if ([self.touchPoints indexOfObject:pointString] != 0)
[aPath addLineToPoint:CGPointFromString(pointString)];
}
[aPath addLineToPoint:cropPoint];
[aPath closePath];
[self setClippingPath:aPath andView:self];
[self setNeedsDisplay];
}
- (void)setClippingPath:(UIBezierPath *)clippingPath andView:(UIView *)view;
{
if (![[view layer] mask])
[[view layer] setMask:[CAShapeLayer layer]];
[(CAShapeLayer*) [[view layer] mask] setPath:[clippingPath CGPath]];
}
How to I save UIImage from the CAShapeLayer? If this is the correct way for multi cropping or any other easy way to achieve this. Please give your ideas, suggestions, source code, etc. Anything always welcome.
Try rendering layer into a context and creating an image from that context.
CALayer *layer = view.layer;
CGSize s = layer.frame.size;
// create context
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, s.width, s.height,
8, (s.width * 4),
colorSpace, kCGImageAlphaPremultipliedLast);
// flip Y
CGContextTranslateCTM(context, 0.0, s.height);
CGContextScaleCTM(context, 1.0, -1.0);
// render layer
[layer renderInContext:context];
CGImageRef imgRef = CGBitmapContextCreateImage(context);
// here is your image
UIImage *img = [UIImage imageWithCGImage:imgRef];
// release owned memory
CGImageRelease(imgRef);
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);

UIImage with border. Invalid Context 0x0

I'm fetching an image and saving it to temporary directory in a background thread. Before saving, I want to add a little white border to this UIImage. I'm doing this:
- (UIImage *)imageWithBorder:(UIImage *)image {
CGSize newSize = CGSizeMake(image.size.width, image.size.height);
CGRect rect = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
UIGraphicsBeginImageContextWithOptions(newSize, NO, image.scale); {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginTransparencyLayer (context, NULL);
//Draw
[image drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0];
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextStrokeRectWithWidth(context, rect, 10);
CGContextEndTransparencyLayer(context);
}
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
Everything is okay, after loading that image from a temporary directory I can see border on an image, but in console I'm getting invalid context 0x0 when the code below implements. What's wrong here?
Update 01
- (UIImage *)imageWithBorder:(UIImage *)image {
CGSize newSize = CGSizeMake(image.size.width, image.size.height);
CGRect rect = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
UIGraphicsBeginImageContextWithOptions(newSize, NO, image.scale); {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage (context, rect, [image CGImage]);
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextStrokeRectWithWidth(context, rect, 10);
}
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
Try changing:
[image drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0];
to
CGContextDrawImage (context, rect, [image CGImage]);
Also, why use a transparency layer? You have the alpha set to 1, so you could just overwrite the original image, no?
EDIT: I just ran the same code in my app, on the background, it worked flawlessly:
- (UIImage *)imageWithBorder:(UIImage *)image
{
NSLog(#"Thread %#", [NSThread currentThread]);
CGSize newSize = CGSizeMake(image.size.width, image.size.height);
CGRect rect = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
UIGraphicsBeginImageContextWithOptions(newSize, NO, image.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
//Draw
CGContextDrawImage(context, rect, [image CGImage]);
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextStrokeRectWithWidth(context, rect, 10);
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
assert(result);
return result;
}
When I do this in viewDidAppear: (but its a small image, maybe the size is relevant?)
dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self imageWithBorder:[UIImage imageNamed:#"02-redo.png"]] ; } );
this is all I see in the log:
2012-08-27 13:48:04.679 Searcher[32825:11103] Thread <NSThread: 0x6d30e30>{name = (null), num = 4}
So something else is going on with you code. I'm building with Xcode 4.4.1, deployment target is 5.1
EDIT: In your block, you cannot refer to "cell" as cell is temporal - with recycling it may not even be visible. So the image name will be routed through a local NSString, then at the end, you need another method that the image can be posted to, along with the cell indexPath. That method will see if that indexPath is in the visibleCells of the table, if so then update the image, if not you want to store the image so it can be used with the tableView next asks for the cell.
EDIT2: This code:
dispatch_async(queue, ^{
UIImage *image = [_thumbnailMaster thumbnailForItemWithFilename:cell.label.text];
dispatch_sync(dispatch_get_main_queue(), ^{ cell.thumbnailView.image = image; });
});
Should be turned into something that does not involve "cell" as cell may go out of scope or be used by another index (assuming recycling). Something like this:
NSString *text = cell.label.text;
NSIndexPath *path = ...;
dispatch_async(queue, ^{
UIImage *image = [_thumbnailMaster thumbnailForItemWithFilename:text];
dispatch_sync(dispatch_get_main_queue(), ^{ [self updateCellImage:image withPath"path]; });
});
Your method determines if that cell is visible, if so update the image, if not save it so next time you need it you have it.
UIGraphicsGetCurrentContext Returns the current graphics context.
CGContextRef UIGraphicsGetCurrentContext ( void ); Return Value The
current graphics context.
Discussion The current graphics context is nil by default. Prior to
calling its drawRect: method, view objects push a valid context onto
the stack, making it current. If you are not using a UIView object to
do your drawing, however, you must push a valid context onto the stack
manually using the UIGraphicsPushContext function.
You should call this function from the main thread of your application
only.
This is from UIKit Function Reference

How to create a flexible frame for photo in iPhone?

I try to create a flexible frame for in my iPhone app with some small pictures. CustomView inherit UIView and override it's setFrame: method. In setFrame:, I try to call [self setNeedsDisplay]; Every time I scale the photo, this frame really display and changed, but something does not work very well. Code and effect below:
//rotate to get mirror image of originImage, and isHorization means the rotation direction
- (UIImage*)fetchMirrorImage:(UIImage*)originImage direction:(BOOL)isHorization{
CGSize imageSize = originImage.size;
UIGraphicsBeginImageContext(imageSize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform transform = CGAffineTransformIdentity;
if (isHorization) {
transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
}else {
transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
}
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -imageSize.height);
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, CGRectMake(0, 0, imageSize.width, imageSize.height), originImage.CGImage);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (UIImage*)fetchPattern:(PatternType)pattern{
if (!self.patternImage) {
return nil;
}
UIImage *tmpPattern = nil;
CGRect fetchRect = CGRectZero;
CGSize imageSize = self.patternImage.size;
switch (pattern) {
case kTopPattern:
fetchRect = CGRectMake(self.insetSize.width, 0, imageSize.width-self.insetSize.width, self.insetSize.height);
tmpPattern = [UIImage imageWithCGImage:CGImageCreateWithImageInRect(self.patternImage.CGImage, fetchRect)];
break;
case kTopRightPattern:
fetchRect = CGRectMake(0, 0, self.insetSize.width, self.insetSize.height);
tmpPattern = [UIImage imageWithCGImage:CGImageCreateWithImageInRect(self.patternImage.CGImage, fetchRect)];
break;
case kRightPattern:
break;
case kRightBottomPattern:
break;
case kBottomPattern:
break;
case kLeftBottomPattern:
break;
case kLeftPattern:
fetchRect = CGRectMake(0, self.insetSize.height, self.insetSize.width, imageSize.height-self.insetSize.height);
tmpPattern = [UIImage imageWithCGImage:CGImageCreateWithImageInRect(self.patternImage.CGImage, fetchRect)];
break;
case kLeftTopPattern:
fetchRect = CGRectMake(0, 0, self.insetSize.width, self.insetSize.height);
tmpPattern = [UIImage imageWithCGImage:CGImageCreateWithImageInRect(self.patternImage.CGImage, fetchRect)];
break;
default:
break;
}
return tmpPattern;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
if (self.patternImage == nil) {
return;
}
// Drawing code.
UIImage *conLeftImage = [self fetchPattern:kLeftTopPattern];
[conLeftImage drawAtPoint:CGPointMake(0, 0)];
UIImage *topImage = [self fetchPattern:kTopPattern];
[topImage drawAsPatternInRect:CGRectMake(self.insetSize.width, 0, self.frame.size.width-self.insetSize.width*2, self.insetSize.height)];
UIImage *leftImage = [self fetchPattern:kLeftPattern];
[leftImage drawAsPatternInRect:CGRectMake(0, self.insetSize.height, self.insetSize.width, self.frame.size.height-self.insetSize.height*2)];
UIImage *conRightImage = [self fetchMirrorImage:conLeftImage direction:YES];
[conRightImage drawAtPoint:CGPointMake(self.frame.size.width-self.insetSize.width, 0)];
UIImage *rightImage = [self fetchMirrorImage:leftImage direction:YES];
CGRect rectRight = CGRectMake(self.frame.size.width-self.insetSize.width, self.insetSize.height, self.insetSize.width, self.frame.size.height-self.insetSize.height*2);
[rightImage drawAsPatternInRect:rectRight];
UIImage *botRightImage = [self fetchMirrorImage:conRightImage direction:NO];
[botRightImage drawAtPoint:CGPointMake(self.frame.size.width-self.insetSize.width, self.frame.size.height-self.insetSize.height)];
UIImage *bottomImage = [self fetchMirrorImage:topImage direction:NO];
CGRect bottomRect = CGRectMake(self.insetSize.width, self.frame.size.height-self.insetSize.height, self.frame.size.width-self.insetSize.width*2, self.insetSize.height);
[bottomImage drawAsPatternInRect:bottomRect];
UIImage *botLeftImage = [self fetchMirrorImage:conLeftImage direction:NO];
[botLeftImage drawAtPoint:CGPointMake(0, self.frame.size.height-self.insetSize.height)];
[super drawRect:rect];
}
- (void)setFrame:(CGRect)frame{
[super setFrame:frame];
[self setNeedsDisplay];
}
Yeah, I make it!!!
The only thing I need to do is set the phase of current context.
CGContextSetPatternPhase(context, CGSizeMake(x, y));
x, y are the start point where I want the pattern to be drawn.
I just made a mistake to use pattern. If I don't set the phase of current context, It start fill the pattern from the left top of context(0,0) every time.
CGColorRef color = NULL;
UIImage *leftImage = [UIImage imageNamed:#"like_left.png"];
color = [UIColor colorWithPatternImage:leftImage].CGColor;
CGContextSetFillColorWithColor(context, color);
CGContextFillRect(context, CGRectMake(0, 0, FRAME_WIDTH, rect.size.height));
UIImage *topImage = [UIImage imageNamed:#"like_top.png"];
color = [UIColor colorWithPatternImage:topImage].CGColor;
CGContextSetFillColorWithColor(context, color);
CGContextFillRect(context, CGRectMake(0, 0, rect.size.width,FRAME_WIDTH));
UIImage *bottomImage = [UIImage imageNamed:#"like_bom.png"];
CGContextSetPatternPhase(context, CGSizeMake(0, rect.size.height - FRAME_WIDTH));
color = [UIColor colorWithPatternImage:bottomImage].CGColor;
CGContextSetFillColorWithColor(context, color);
CGContextFillRect(context, CGRectMake(0, rect.size.height - FRAME_WIDTH, rect.size.width, FRAME_WIDTH));
UIImage *rightImage = [UIImage imageNamed:#"like_right.png"];
CGContextSetPatternPhase(context, CGSizeMake(rect.size.width - FRAME_WIDTH, 0));
color = [UIColor colorWithPatternImage:rightImage].CGColor;
CGContextSetFillColorWithColor(context, color);
CGContextFillRect(context, CGRectMake(rect.size.width - FRAME_WIDTH, 0, FRAME_WIDTH, rect.size.height));
First, I'll say this:
I don't have a real answer right now. But, I have a hint .. I think it'll be useful.
According to the tutorial posted by Larmarche, you can customize the borders of a UIView easily by overriding drawRect:. And, if you scroll down to the comments below (in the tutorial website), you can see the following comment:
I discovered one other glitch. When you change the RoundRect's bounds,
it will not be updated -- so the corners sometimes get all screwy
(because they're scaled automatically).
The quick fix is to add the following to both init methods:
self.contentMode = UIViewContentModeRedraw;
Setting the contentMode to Redraw saves you the trouble of calling setNeedsDisplay. You can easily change the frame of the UIView, instantly or animate it, and the UIView will redraw automatically..
I will try to take a better look at your problem later .. I'll try to actually implement it.

CGContextAddEllipseInRect to CGImageRef to CGImageMaskCreate to CGContextClipToMask

I haven't been able to find one single example on the internets that teaches me how to create a circle on the fly and then use this circle to clip an UIImage.
Here's my code, unfortunately it doesn't give me desired results.
//create a graphics context
UIGraphicsBeginImageContext(CGSizeMake(243, 243));
CGContextRef context = UIGraphicsGetCurrentContext();
//create my object in this context
CGContextAddEllipseInRect(context, CGRectMake(0, 0, 243, 243));
CGContextSetFillColor(context, CGColorGetComponents([[UIColor whiteColor] CGColor]));
CGContextFillPath(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//create an uiimage from the ellipse
//Get the drawing image
CGImageRef maskImage = [image CGImage];
// Get the mask from the image
CGImageRef maskRef = CGImageMaskCreate(CGImageGetWidth(maskImage)
, CGImageGetHeight(maskImage)
, CGImageGetBitsPerComponent(maskImage)
, CGImageGetBitsPerPixel(maskImage)
, CGImageGetBytesPerRow(maskImage)
, CGImageGetDataProvider(maskImage)
, NULL
, false);
//finally clip the context to the mask.
CGContextClipToMask( context , CGRectMake(0, 0, 243, 243) , maskRef );
//draw the image
[firstPieceView.image drawInRect:CGRectMake(0, 0, 320, 480)];
// [firstPieceView drawRect:CGRectMake(0, 0, 320, 480)];
//extract a new image
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
NSLog(#"self.firstPieceView is %#", NSStringFromCGRect(self.firstPieceView.frame));
UIGraphicsEndImageContext();
self.firstPieceView.image = outputImage;
I would appreciate any directions.
I suspect you need to rephrase your question better.
There's plenty of example code for whatever you're trying to do out there.
Here's how you could implement a custom UIView subclass to clip am image to an ellipse:
- (void)drawInRect:(CGRect)rect {
UIImage image;// set/get from somewhere
CGImageRef imageRef = [image CGImageRef];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(context, self.bounds);
CGContextClip(context);
CGContextDrawImage(context, self.bounds, imageRef);
}
caveat emptor
Edit (a day later, free time produces):
- (void)drawRect:(CGRect)rect {
// we're ignoring rect and drawing the whole view
CGImageRef imageRef = [_image CGImage]; // ivar: UIImage *_image;
CGContextRef context = UIGraphicsGetCurrentContext();
// set the background to black
[[UIColor blackColor] setFill];
CGContextFillRect(context, self.bounds);
// modify the context coordinates,
// UIKit and CoreGraphics are oriented differently
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0, CGRectGetHeight(rect));
CGContextScaleCTM(context, 1, -1);
// add clipping path to the context, then execute the clip
// this is in effect for all drawing until GState restored
CGContextAddEllipseInRect(context, self.bounds);
CGContextClip(context);
// stretch the image to be the size of the view
CGContextDrawImage(context, self.bounds, imageRef);
CGContextRestoreGState(context);
}

Setting A CGContext Transparent Background

I am still struggling with drawing a line with CGContext. I have actually go to line to draw, but now I need the background of the Rect to be transparent so the existing background shows thru. Here's my test code:
(void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGContextSetAlpha(context,0.0);
CGContextFillRect(context, rect);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(context, 5.0);
CGContextMoveToPoint(context, 100.0,0.0);
CGContextAddLineToPoint(context,100.0, 100.0);
CGContextStrokePath(context);
}
Any ideas?
After UIGraphicsGetCurrentContext() call CGContextClearRect(context,rect)
Edit:
Alright, got it.
Your custom view with the line should have the following:
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setBackgroundColor:[UIColor clearColor]];
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(context, 5.0);
CGContextMoveToPoint(context, 100.0,0.0);
CGContextAddLineToPoint(context,100.0, 100.0);
CGContextStrokePath(context);
}
My test used this as a very basic UIViewController:
- (void)viewDidLoad {
[super viewDidLoad];
UIImageView *v = [[UIImageView alloc] initWithFrame:self.view.bounds];
[v setBackgroundColor:[UIColor redColor]];
[self.view addSubview:v];
TopView *t = [[TopView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:t];
[v release];
[t release];
}
Easy way:
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
self.opaque = NO;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
//your code
}
Init a context with opaque == false, Swift 3
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
opaque
A Boolean flag indicating whether the bitmap is opaque. If you know the bitmap is fully opaque, specify true to ignore the alpha channel and optimize the bitmap’s storage. Specifying false means that the bitmap must include an alpha channel to handle any partially transparent pixels.
This is what worked for me with a UIImage which had been manually added using InterfaceBuilder.
- (id)initWithCoder:(NSCoder *)aDecoder {
if(self = [super initWithCoder:aDecoder]) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
-(void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(context, 5.0);
CGContextMoveToPoint(context, 100.0,0.0);
CGContextAddLineToPoint(context,100.0, 100.0);
CGContextStrokePath(context);
}
David Kanarek's answer only works when you're manually creating your own UIImageView. If you've created a UIView and manually added it via Interface Builder then you will need a different approach like this calling the initWithCoder method instead.
I have the same problem, then I find it is.
I overwrite the init Method is -(id)initWithFrame:(CGRect)rect.
In this method self.background = [UIColor clearColor];
but i use this view in xib file !!! That will call the init method is
-(id)initWithCoder:(NSCoder*)aDecoder;
So overwrite all the init Method and setup BackgroundColor will work OK.
CGContextClearRect(context,rect)
If the provided context is a window or bitmap context, Quartz effectively clears the rectangle. For other context types, Quartz fills the rectangle in a device-dependent manner. However, you should not use this function in contexts other than window or bitmap contexts.
you can create a image context with this code:
cacheContext = CGBitmapContextCreate (cacheBitmap, size.width, size.height, 8, bitmapBytesPerRow, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast);
CGContextSetRGBFillColor(cacheContext, 0, 0, 0, 0);
CGContextFillRect(cacheContext, (CGRect){CGPointZero, size});
the key is kCGImageAlphaPremultipliedLast.
Having trouble understanding the question here, but if you're unable to have a "background" UIView show through a "top" view into which you're drawing, one solution is topView.backgroundColor = [UIColor clearColor]; I was having (I think) this same problem and this solved it for me.