renderInContext with iPhoto images working for PDFs but not thumbnails - objective-c

I seem to have a curious bug involving renderInContext.
I am working on a "scrapbook" app which allows people to freely arrange text, colored squares and photos within a scrapbookView. Note that users can add both photos from a built-in collection of photos (loaded via imageNamed:) and photos from their asset library.
My code creates both PDFs and thumbnails as needed from the scrapbook view and all its subviews by first creating a PDFContext (for PDFs) or an ImageContext (for thumbnail images), then calling renderInContext on scrapBookView.layer.
In general, the code works, with one exception which I will describe. When I am creating a PDF, both built-in images and asset library images are correctly included in the rendered PDF. However, when I am creating a thumbnail, the built-in images appear, but the asset library images don't correctly appear. I know the imageView is there because elsewhere I set its background color to light gray but the image itself is absent.
I suspect that there may be an asynchronous element to loading the images into image views, and the image doesn't get there fast enough when its an asset library image being rendered into an ImageContext.
Is this possible? If not, why would renderInContext work fine in a PDFContext but not so well in an ImageContext?
And more to the point, how can I get my asset library images included in my thumbnails.
Both the pdf representation and the thumbnail are created via methods in scrapbookView. The code is as follows:
To create pdfs:
-(NSData*) pdfRepresentation
{
//== create a pdf context
NSMutableData* result = [[NSMutableData alloc] init];
UIGraphicsBeginPDFContextToData(result, self.bounds, nil);
UIGraphicsBeginPDFPage();
//== draw this view into it
[self.layer renderInContext: UIGraphicsGetCurrentContext()]; // works for asset library images
//== add a rectangular frame also
CGContextRef pdf = UIGraphicsGetCurrentContext();
CGContextAddRect(pdf, self.bounds);
[[UIColor lightGrayColor] setStroke];
CGContextStrokePath(pdf);
//== clean up and return the results
UIGraphicsEndPDFContext();
return [result copy];
}
To create the thumbnail:
-(UIImage*) thumbnailRepresentation
{
//== create a large bitmapped image context
CGFloat minSpan = MIN(self.bounds.size.height, self.bounds.size.width);
CGSize imageSize = CGSizeMake(minSpan, minSpan);
UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0.0);
//== draw the current view into the bitmap context
self.backgroundColor = [UIColor whiteColor];
[self.layer renderInContext: UIGraphicsGetCurrentContext()]; // doesn't work for asset library images
self.backgroundColor = [UIColor clearColor];
//== get the preliminary results
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//== create a smaller image from this image
CGFloat thumbnailWidth = 40.0;
CGFloat scaleFactor = thumbnailWidth / minSpan;
UIImage* result = [UIImage imageWithCGImage:[image CGImage] scale:scaleFactor orientation:UIImageOrientationUp];
return result;
}
// btw - seems wasteful to create such a large intermediate image
Any help would be appreciated.

Related

Merging/stacking two images with Cocoa/OSX

I have a CGImageRef (lets call it original image) and a transparent png (watermark). I'm trying to write a method to place the watermark on top of the original, and return a CGImageRef.
In iOS I would have used UIKit to draw them both onto a context, but that doesn't seem possible with OSX (doesn't support UIKit).
Whats the simplest way to stack two images? Thanks
For a quick 'n dirty solution you can use the NSImage drawing APIs:
NSImage *background = [NSImage imageNamed:#"background"];
NSImage *overlay = [NSImage imageNamed:#"overlay"];
NSImage *newImage = [[NSImage alloc] initWithSize:[background size]];
[newImage lockFocus];
CGRect newImageRect = CGRectZero;
newImageRect.size = [newImage size];
[background drawInRect:newImageRect];
[overlay drawInRect:newImageRect];
[newImage unlockFocus];
CGImageRef newImageRef = [newImage CGImageForProposedRect:NULL context:nil hints:nil];
If you don't like that, most of the CGContext APIs you'd expect are available cross platform—for drawing with a little more control. Similarly, you could look into NSGraphicsContext.
This is pretty easy when you render to a CGContext.
If you want an image as a result, you can create and render to a CGBitmapContext, then request the image after render.
General flow, with common details and contextual info omitted:
CGImageRef CreateCompositeOfImages(CGImageRef pBackground,
const CGRect pBackgroundRect,
CGImageRef pForeground,
const CGRect pForegroundRect)
{
// configure context parameters
CGContextRef gtx = CGBitmapContextCreate( %%% );
// configure context
// configure context to render background image
// draw background image
CGContextDrawImage(gtx, pBackgroundRect, pBackground);
// configure context to render foreground image
// draw foreground image
CGContextDrawImage(gtx, pForegroundRect, pForeground);
// create result
CGImageRef result = CGBitmapContextCreateImage(gtx);
// cleanup
return result;
}
You would need to create a CGImage from your PNG.
Additional APIs you may be interested in using:
CGContextSetBlendMode
CGContextSetAllowsAntialiasing
CGContextSetInterpolationQuality.
I know a lot of people will generally advise you to use higher level abstractions (i.e. AppKit and UIKit), but CoreGraphics is a great library for rendering in both of those contexts. If you are interested in graphics implementations which are easy to use in both OS X and iOS, CoreGraphics is a good choice to base your work upon if you are comfortable working with those abstractions.
If anyone, like me, needs a Swift version.
This is a functional Swift 5 version:
let background = NSImage(named: "background")
let overlay = NSImage(named: "overlay")
let newImage = NSImage(size: background.size)
newImage.lockFocus()
var newImageRect: CGRect = .zero
newImageRect.size = newImage.size
background.draw(in: newImageRect)
overlay.draw(in: newImageRect)
newImage.unlockFocus()
I wish I had the time to do the same with the CGContext example.

Loading a png into a UIImageView iOS

I have two images that need to be overlaid over one another, and they are both png images (since I need to be able to make them transparent). However, when I load them into a UIImage view in my xib file, neither of them display at all! When I try using the jpg format of the same images it works fine, but because jpg doesn't support transparency, the overlay effect I need is lost. How can I get the png images to actually display in the window?
This is the kind of task that is easier to do from code than from Interface "Crappy" Builder:
CGRect imageFrame = CGRectMake(x, y, width, height);
UIImage *image1 = // however you obtain your 1st image
UIImage *image2 = // however you obtain your 2nd image
UIImageView *imgView1 = [[UIImageView alloc] initWithImage:image1];
// Adjust the alpha of the view
imgView1.alpha = 1.0f; // This is most advisably 1.0 (always)
imgView1.backgroundColor = [UIColor clearColor];
imgView1.frame = imageFrame;
UIImageView *imgView2 = [[UIImageView alloc] initWithImage:image2];
// Adjust the alpha of the view
imgView1.alpha = 0.5f; // or whatever you find practical
imgView1.backgroundColor = [UIColor clearColor];
imgView2.frame = imageFrame;
// Assume a view controller
[self.view addSubview:imgView1];
[self.view addSUbview:imgView2]; // add the image view later which you wanna be on the top of the other one
// If non-ARC environment, we need to take care of the percious RAM
[imgView1 release];
[imgView2 release];
Try to open your png images in a photo editor like photoshop or pixelmator and save it again as NOT interlaced (in the save options of png).

UIButton image rotation issue

I have a [UIButton buttonWithType:UIButtonTypeCustom] that has an image (or a background image - same problem) created by [UIImage imageWithContentsOfFile:] pointing to a JPG file taken by the camera and saved in the documents folder by the application.
If I define the image for UIControlStateNormal only, then when I touch the button the image gets darker as expected, but it also rotates either 90 degrees or 180 degrees. When I remove my finger it returns to normal.
This does not happen if I use the same image for UIControlStateHighlighted, but then I lose the touch indication (darker image).
This only happens with an image read from a file. It does not happen with [UIImage ImageNamed:].
I tried saving the file in PNG format rather than as JPG. In this case the image shows up in the wrong orientation to begin with, and is not rotated again when touched. This is not a good solution anyhow because the PNG is far too large and slow to handle.
Is this a bug or am I doing something wrong?
I was not able to find a proper solution to this and I needed a quick workaround. Below is a function which, given a UIImage, returns a new image which is darkened with a dark alpha fill. The context fill commands could be replaced with other draw or fill routines to provide different types of darkening.
This is un-optimized and was made with minimal knowledge of the graphics api.
You can use this function to set the UIControlStateHighlighted state image so that at least it will be darker.
+ (UIImage *)darkenedImageWithImage:(UIImage *)sourceImage
{
UIImage * darkenedImage = nil;
if (sourceImage)
{
// drawing prep
CGImageRef source = sourceImage.CGImage;
CGRect drawRect = CGRectMake(0.f,
0.f,
sourceImage.size.width,
sourceImage.size.height);
CGContextRef context = CGBitmapContextCreate(NULL,
drawRect.size.width,
drawRect.size.height,
CGImageGetBitsPerComponent(source),
CGImageGetBytesPerRow(source),
CGImageGetColorSpace(source),
CGImageGetBitmapInfo(source)
);
// draw given image and then darken fill it
CGContextDrawImage(context, drawRect, source);
CGContextSetBlendMode(context, kCGBlendModeOverlay);
CGContextSetRGBFillColor(context, 0.f, 0.f, 0.f, 0.5f);
CGContextFillRect(context, drawRect);
// get context result
CGImageRef darkened = CGBitmapContextCreateImage(context);
CGContextRelease(context);
// convert to UIImage and preserve original orientation
darkenedImage = [UIImage imageWithCGImage:darkened
scale:1.f
orientation:sourceImage.imageOrientation];
CGImageRelease(darkened);
}
return darkenedImage;
}
To fix this you need additional normalization function like this:
public extension UIImage {
func normalizedImage() -> UIImage! {
if self.imageOrientation == .Up {
return self
}
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
self.drawInRect(CGRectMake(0, 0, self.size.width, self.size.height))
let normalized = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return normalized
}
}
then you can use it like that:
self.photoButton.sd_setImageWithURL(avatarURL,
forState: .Normal,
placeholderImage: UIImage(named: "user_avatar_placeholder")) {
[weak self] (image, error, cacheType, url) in
guard let strongSelf = self else {
return
}
strongSelf.photoButton.setImage(image.normalizedImage(), forState: .Normal
}

Image Cropping API for iOS

Is there any cropping image API for objective C that crops images dynamically in Xcode project? Please provide some tricks or techniques how could I crop camera images in iPhone.
You can use below simple code to crop an image. You have to pass the image and the CGRect which is the cropping area. Here, I crop image so that I get center part of original image and returned image is square.
// Returns largest possible centered cropped image.
- (UIImage *)centerCropImage:(UIImage *)image
{
// Use smallest side length as crop square length
CGFloat squareLength = MIN(image.size.width, image.size.height);
// Center the crop area
CGRect clippedRect = CGRectMake((image.size.width - squareLength) / 2, (image.size.height - squareLength) / 2, squareLength, squareLength);
// Crop logic
CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], clippedRect);
UIImage * croppedImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
return croppedImage;
}
EDIT - Swift Version
let imageView = UIImageView(image: image)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
All these solutions seem quite complicated and many of them actually degrade the quality the image.
You can do much simpler using UIImageView's out of the box methods.
Objective-C
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
[self.imageView setClipsToBounds:YES];
[self.imageView setImage:img];
This will crop your image based on the dimensions you've set for your UIImageView (I've called mine imageView here).
It's that simple and works much better than the other solutions.
You can use CoreGraphics framework to cropping image dynamically.
Here is a example code part of dynamic image crop. I hope this will be helpful for you.
- (void)drawMaskLineSegmentTo:(CGPoint)ptTo withMaskWidth:(CGFloat)maskWidth inContext:(NXMaskDrawContext)context{
if (context == nil)
return;
if (context.count <= 0){
[context addObject:[NSValue valueWithCGPoint:ptTo]];
return;
}
CGPoint ptFrom = [context.lastObject CGPointValue];
UIGraphicsBeginImageContext(self.maskImage.size);
[self.maskImage drawInRect:CGRectMake(0, 0, self.maskImage.size.width, self.maskImage.size.height)];
CGContextRef graphicsContext = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(graphicsContext, kCGBlendModeCopy);
CGContextSetRGBStrokeColor(graphicsContext, 1, 1, 1, 1);
CGContextSetLineWidth(graphicsContext, maskWidth);
CGContextSetLineCap(graphicsContext, kCGLineCapRound);
CGContextMoveToPoint(graphicsContext, ptFrom.x, ptFrom.y);
CGContextAddLineToPoint(graphicsContext, ptTo.x, ptTo.y);
CGContextStrokePath(graphicsContext);
self.maskImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIGraphicsBeginImageContext(self.displayableMaskImage.size);
[self.displayableMaskImage drawInRect:CGRectMake(0, 0, self.displayableMaskImage.size.width, self.displayableMaskImage.size.height)];
graphicsContext = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(graphicsContext, kCGBlendModeCopy);
CGContextSetStrokeColorWithColor(graphicsContext, self.displayableMaskColor.CGColor);
CGContextSetLineWidth(graphicsContext, maskWidth);
CGContextSetLineCap(graphicsContext, kCGLineCapRound);
CGContextMoveToPoint(graphicsContext, ptFrom.x, ptFrom.y);
CGContextAddLineToPoint(graphicsContext, ptTo.x, ptTo.y);
CGContextStrokePath(graphicsContext);
self.displayableMaskImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[context addObject:[NSValue valueWithCGPoint:ptTo]];
}
Xcode 5, iOS 7, and 4-inch screen example: Here is an open source example of a
SimpleImageCropEditor (Project Zip and Source Code Example. You can load the Image Crop Editor as a Modal View Controller and reuse. Look at the code and please leave constructive comments concerning if this example code answers the question "Image Cropping API for iOS".
Demonstrates, is example source Objective-C code, use of UIImagePickerController, #protocol, UIActionSheet, UIScrollView, UINavigationController, MFMailComposeViewController, and UIGestureRecognizer.

Image Context produces artifacts when compositing UIImages

I'm trying to overlay a custom semi-transparent image over a base image. The overlay image is stretchable and created like this:
[[UIImage imageNamed:#"overlay.png"] stretchableImageWithLeftCapWidth:5.0 topCapHeight:5.0]
Then I pass that off to a method that overlays it onto the background image for a button:
- (void)overlayImage:(UIImage *)overlay forState:(UIControlState)state {
UIImage *baseImage = [self backgroundImageForState:state];
CGRect frame = CGRectZero;
frame.size = baseImage.size;
// create a new image context
UIGraphicsBeginImageContext(baseImage.size);
// get context
CGContextRef context = UIGraphicsGetCurrentContext();
// clear context
CGContextClearRect(context, frame);
// draw images
[baseImage drawInRect:frame];
[overlay drawInRect:frame];// blendMode:kCGBlendModeNormal alpha:1.0];
// get UIImage
UIImage *overlaidImage = UIGraphicsGetImageFromCurrentImageContext();
// clean up context
UIGraphicsEndImageContext();
[self setBackgroundImage:overlaidImage forState:state];
}
The resulting overlaidImage looks mostly correct, it is the correct size, the alpha is blended correctly, etc. however it has vertical artifacts/noise.
UIImage artifacts example http://acaciatreesoftware.com/img/UIImage-artifacts.png
(example at http://acaciatreesoftware.com/img/UIImage-artifacts.png)
I tried clearing the context first and then turning off PNG compression--which reduces the artifacting some (completely on non stretched images I think).
Does anyone know a method for drawing stretchable UIImages with out this sort of artifacting happening?
So the answer is: Don't do this. Instead you can paint your overlay procedurally. Like so:
- (void)overlayWithColor:(UIColor *)overlayColor forState:(UIControlState)state {
UIImage *baseImage = [self backgroundImageForState:state];
CGRect frame = CGRectZero;
frame.size = baseImage.size;
// create a new image context
UIGraphicsBeginImageContext(baseImage.size);
// get context
CGContextRef context = UIGraphicsGetCurrentContext();
// draw background image
[baseImage drawInRect:frame];
// overlay color
CGContextSetFillColorWithColor(context, [overlayColor CGColor]);
CGContextSetBlendMode(context, kCGBlendModeSourceAtop);
CGContextFillRect(context, frame);
// get UIImage
UIImage *overlaidImage = UIGraphicsGetImageFromCurrentImageContext();
// clean up context
UIGraphicsEndImageContext();
[self setBackgroundImage:overlaidImage forState:state];
}
Are you being too miserly with your original image, and forcing it to stretch rather than shrink? I've found best results out of images that fit the same aspect ratio and were reduced in size. Might not solve your problem tho.