Creating an animated GIF in Cocoa - defining frame type - objective-c

I've been able to adapt some code found on SO to produce an animated GIF from the "screenshots" of my view, but the results are unpredictable. GIF frames are sometimes full images, full frames ("replace" mode, as GIMP marks it), other times are just a "diff" from previous layer ("combine" mode).
From what I've seen, when there are fewer and/or smaller frames involved, the CG writes the GIF in "combine" mode, but failing to get the colors right. Actually, the moving parts are colored correctly, the background is wrong.
When CG saves the GIF as full frames, the colors are ok. The file size is larger, but hey, obviously you cannot have the best of both worlds. :)
Is there a way to either:
a) force CG to create "full frames" when saving the GIF
b) fix the colors (color table?)
What I do is (ARC mode):
capture the visible part of the view with
[[scrollView contentView] dataWithPDFInsideRect:[[scrollView contentView] visibleRect]];
convert and resize it to NSImageBitmapRep of PNG type
-(NSMutableDictionary*) pngImageProps:(int)quality {
NSMutableDictionary *pngImageProps;
pngImageProps = [[NSMutableDictionary alloc] init];
[pngImageProps setValue:[NSNumber numberWithBool:NO] forKey:NSImageInterlaced];
double compressionF = 1;
[pngImageProps setValue:[NSNumber numberWithFloat:compressionF] forKey:NSImageCompressionFactor];
return pngImageProps;
}
-(NSData*) resizeImageToData:(NSData*)data toDimX:(int)xdim andDimY:(int)ydim withQuality:(int)quality{
NSImage *image = [[NSImage alloc] initWithData:data];
NSRect inRect = NSZeroRect;
inRect.size = [image size];
NSRect outRect = NSMakeRect(0, 0, xdim, ydim);
NSImage *outImage = [[NSImage alloc] initWithSize:outRect.size];
[outImage lockFocus];
[image drawInRect:outRect fromRect:inRect operation:NSCompositeCopy fraction:1];
NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:outRect];
[outImage unlockFocus];
NSMutableDictionary *imageProps = [self pngImageProps:quality];
NSData* imageData = [bitmapRep representationUsingType:NSPNGFileType properties:imageProps];
return [imageData copy];
}
get the array of BitmapReps and create the GIF
-(CGImageRef) pngRepDataToCgImageRef:(NSData*)data {
CFDataRef imgData = (__bridge CFDataRef)data;
CGDataProviderRef imgDataProvider = CGDataProviderCreateWithCFData (imgData);
CGImageRef image = CGImageCreateWithPNGDataProvider(imgDataProvider, NULL, true, kCGRenderingIntentDefault);
return image;
}
////////// create GIF from
NSArray *images; // holds all BitmapReps
CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:pot],
kUTTypeGIF,
allImages,
NULL);
// set frame delay
NSDictionary *frameProperties = [NSDictionary
dictionaryWithObject:[NSDictionary
dictionaryWithObject:[NSNumber numberWithFloat:0.2f]
forKey:(NSString *) kCGImagePropertyGIFDelayTime]
forKey:(NSString *) kCGImagePropertyGIFDictionary];
// set gif color properties
NSMutableDictionary *gifPropsDict = [[NSMutableDictionary alloc] init];
[gifPropsDict setObject:(NSString *)kCGImagePropertyColorModelRGB forKey:(NSString *)kCGImagePropertyColorModel];
[gifPropsDict setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCGImagePropertyGIFHasGlobalColorMap];
// set gif loop
NSDictionary *gifProperties = [NSDictionary
dictionaryWithObject:gifPropsDict
forKey:(NSString *) kCGImagePropertyGIFDictionary];
// loop through frames and add them to GIF
for (int i=0; i < [images count]; i++) {
NSData *imageData = [images objectAtIndex:i];
CGImageRef imageRef = [self pngRepDataToCgImageRef:imageData];
CGImageDestinationAddImage(destination, imageRef, (__bridge CFDictionaryRef) (frameProperties));
}
// save the GIF
CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)(gifProperties));
CGImageDestinationFinalize(destination);
CFRelease(destination);
I've checked the ImageBitmapReps, when saved as PNG individually, they are just fine.
As I understood, the color tables should be handled by CG or am I responsible to produce the dithered colors? How to do that?
Even when doing the same animation repeatedly, the GIFs produced may vary.
This is a single BitmapRep
(source: andraz.eu)
And this is the GIF with the invalid colors ("combine" mode)
(source: andraz.eu)

I read your code. Please double check the "allImages" while you are creating the CGImageDestinationRef, and the "[images count]".
the follow test code works fine:
NSDictionary *prep = [NSDictionary dictionaryWithObject:[NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.2f] forKey:(NSString *) kCGImagePropertyGIFDelayTime] forKey:(NSString *) kCGImagePropertyGIFDictionary];
CGImageDestinationRef dst = CGImageDestinationCreateWithURL((__bridge CFURLRef)(fileURL), kUTTypeGIF, [filesArray count], nil);
for (int i=0;i<[filesArray count];i++)
{
//load anImage from array
...
CGImageRef imageRef=[anImage CGImageForProposedRect:nil context:nil hints:nil];
CGImageDestinationAddImage(dst, imageRef,(__bridge CFDictionaryRef)(prep));
}
bool fileSave = CGImageDestinationFinalize(dst);
CFRelease(dst);

Related

Generating thumbnails causes leak (MacOS, Obj C)

I'm working on a MacOS program in Objective C that needs to produce in-memory thumbnails to send to a server. The following code is used to perform this operation. As the program runs, a leak of about 40mb is induced each time this method is called. I'm missing something really basic, I suspect, but I don't see the source of the problem.
I should add that I've also tried creating one context to use over the life of the program and the problem, if anything, seems somewhat worse.
When I run Instruments, the allocations for the category "VM: ImageIO_JPEG_Data" are growing by one allocation of 40mb each time it's called. The responsible library is "ImageIO" and the responsible caller is "ImageIO_Malloc".
- (void) createPhotoThumbnail
{
NSURL* fileURL = [NSURL fileURLWithPath : _imagePath];
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGContextRef bitmapContext = CGBitmapContextCreate(NULL, MAX_THUMB_DIM, MAX_THUMB_DIM, 8, 0,
colorspace, (CGBitmapInfo)kCGImageAlphaNoneSkipLast);
CIContext *ciContext = [CIContext contextWithCGContext: bitmapContext options: #{}];
if (fileURL)
{
CIImage *image = [[CIImage alloc] initWithContentsOfURL: fileURL];
if (image)
{
// scale the image
CIFilter *scaleFilter = [CIFilter filterWithName: #"CILanczosScaleTransform"];
[scaleFilter setValue: image forKey: #"inputImage"];
NSNumber *scaleFactor = [[NSNumber alloc] initWithFloat: ((float) MAX_THUMB_DIM) /
((float)MAX(_processedWidth, _processedHeight))];
[scaleFilter setValue: scaleFactor forKey: #"inputScale"];
[scaleFilter setValue: #1.0 forKey: #"inputAspectRatio"];
CIImage *scaledImage = [scaleFilter valueForKey: #"outputImage"];
NSMutableData* thumbJpegData = [[NSMutableData alloc] init];
CGImageDestinationRef dest = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)thumbJpegData,
(__bridge CFStringRef)#"public.jpeg",
1,
NULL);
if (dest)
{
CGImageRef img = [ciContext createCGImage:scaledImage
fromRect:[scaledImage extent]];
CGImageDestinationAddImage(dest, img, nil);
if (CGImageDestinationFinalize(dest))
{
// encode it as a string for later
_thumbnail = [thumbJpegData base64EncodedStringWithOptions: 0];
}
else
{
DDLogError(#"Failed to generate photo thumbnail");
}
CGImageRelease(img);
CFRelease(dest);
}
else
{
DDLogError(#"Failed to finalize photo thumbnail image");
}
thumbJpegData = nil;
}
}
CGContextRelease(bitmapContext);
CGColorSpaceRelease(colorspace);
ciContext = nil;
}
UPDATE: I switched the code to use a CGAffineTransform instead of the filter with "CILanczosScaleTransform" and the symptom did not change. Next I used a completely new method (snippet below) and yet the problem persists.
NSImage *thumbnail = [[NSImage alloc] initWithSize: newSize];
[thumbnail lockFocus];
[sourceImage setSize: newSize];
[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
[sourceImage compositeToPoint: NSZeroPoint operation: NSCompositeCopy];
[thumbnail unlockFocus];
NSData *tiff = [thumbnail TIFFRepresentation];
NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData: tiff];
NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.9] forKey:NSImageCompressionFactor];
NSData *thumbJpegData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps];
This is making me think the problem is perhaps related to something inherent in the way I'm doing this. I find it hard to believe two different methods of image scaling are going to exhibit the same sort of leak.
Thanks to this answer I was able to identify the need for an autorelease pool, something I was completely unaware of. The code in the question is one of a series of methods that are called repeatedly from inside a tight loop. This apparently prevents the OS from having a chance to do some cleanup. The block now looks like this:
#autoreleasepool {
[self findRelevantAdjustments];
[self adjustForStraightenCrop];
[self moveFacesRelativeToTopLeftOrigin];
[self createPhotoThumbnail];
[self sendPhotoToServer];
}
Moral of the story: even with ARC there are more things to pay attention to when it comes to the memory lifecycle.
The problem is not in the CGImageDestinationRef logic, because it still leaks even if you replace that with something far simple, such as:
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCIImage:scaledImage];
NSData *data = [rep representationUsingType:NSJPEGFileType properties:nil];
Digging a little further, it would appear that the problem appears to be an issue within CILanczosScaleTransform. If you use an inputScale of #1.0, then the leak disappears. But use something less than #1.0 (even #0.5) and it leaks.
I'd suggest you consider finding a different method for resizing the image.

Colored Artifacts while transforming PDF to NSImageRep (png)

I want to convert different PDF pages to an png. After that I iterate through all pixels to search colored pixel. The main goal is to get the colored pages of the PDF.
For the most pages it runs great. But on some pages I have colored artifacts in red (left beside a letter) and blue (right beside a letter).
This is the original from the PDF:
(source: brebook.de)
And this is the converted letter in the png:
How can I prevent this ugly artifacts. I can't use my whole idea with this colored pixel.
This is the code which is converting the single page to a png:
//Path for saving colored Pages
NSURL *savePath = [self.homePath URLByAppendingPathComponent:#"brebook_temp/"];
//PDFDocument *thePDF = [[PDFDocument alloc] initWithURL:self.filename];
NSPDFImageRep *img = [NSPDFImageRep imageRepWithContentsOfURL:self.filename];
int coloredPages = 0;
for(int i = 0; i < self.getNumberOfPages; i++){
#autoreleasepool {
//set current page label to current page
self.currentPage = i + 1;
//set current page to i
[img setCurrentPage:i];
//create a new NSImage instance
NSImage *singlePage = [NSImage new];
//set actuall page
[singlePage addRepresentation:img];
//get "old" size
NSSize oldSize = [singlePage size];
//edit the size to 72dpi
NSSize newSize;
newSize.width = oldSize.width * 150/72;
newSize.height = oldSize.height * 150/72;
//make new image
NSImage *resizedImage = [[NSImage alloc] initWithSize: NSMakeSize(newSize.width, newSize.height)];
//write into new image
[resizedImage lockFocus];
[singlePage drawInRect: NSMakeRect(0, 0, newSize.width, newSize.height) fromRect: NSMakeRect(0, 0, oldSize.width, oldSize.height) operation: NSCompositeSourceOver fraction: 1.0];
[resizedImage unlockFocus];
//Set URL for single pages
NSURL *pageFilename = [savePath URLByAppendingPathComponent: [NSString stringWithFormat:#"Seite_%d.png", i+1]];
[[NSFileManager defaultManager] createFileAtPath: [pageFilename path]
contents:[[NSBitmapImageRep imageRepWithData:[singlePage TIFFRepresentation]]
representationUsingType:NSPNGFileType properties:nil]
attributes:nil];
if([self getColoredPixelOfImageFromURL:pageFilename] > 0){
coloredPages++;
NSLog(#"SEITE %d -----------------------------------------------------------------------------------------------", i+1);
_coloredPages = coloredPages;
[self.coloredPagesList appendString:[NSString stringWithFormat:#"%d,",i+1]];
}else{
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtURL:pageFilename error:&error];
if(error){
NSLog(#"%#", error);
}
}
resizedImage = nil;
singlePage = nil;
}
}
[self.appTimer invalidate];
Thank you so much for helping!!!

defaultRepresentation fullScreenImage on ALAsset does not return full screen image

In my application I save images to an album as assets. I want also to retrieve them and display them in full screen. I use the following code :
ALAsset *lastPicture = [scrollArray objectAtIndex:iAsset];
ALAssetRepresentation *defaultRep = [lastPicture defaultRepresentation];
UIImage *image = [UIImage imageWithCGImage:[defaultRep fullScreenImage]
scale:[defaultRep scale] orientation:
(UIImageOrientation)[defaultRep orientation]];
The problem is that the image returned is nil. I have read at the ALAssetRepresentation reference that when the image does not fit it is returned nil.
I put this image to an UIImageView which has the size of the iPad screen. I was wondering if you could help me with this issue?
Thank you in advance.
I'm not a fan of fullScreenImage or fullResolutionImage. I found that when you do this on multiple assets in a queue, even if you release the UIImage immediately, memory usage will increase dramatically while it shouldn't. Also when using fullScreenImage or fullResolutionImage, the UIImage returned is still compressed, meaning that it will be decompressed before being drawn for the first time, thus on the main thread which will block your UI.
I prefer to use this method.
-(UIImage *)fullSizeImageForAssetRepresentation:(ALAssetRepresentation *)assetRepresentation
{
UIImage *result = nil;
NSData *data = nil;
uint8_t *buffer = (uint8_t *)malloc(sizeof(uint8_t)*[assetRepresentation size]);
if (buffer != NULL) {
NSError *error = nil;
NSUInteger bytesRead = [assetRepresentation getBytes:buffer fromOffset:0 length:[assetRepresentation size] error:&error];
data = [NSData dataWithBytes:buffer length:bytesRead];
free(buffer);
}
if ([data length])
{
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceShouldAllowFloat];
[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailFromImageAlways];
[options setObject:(id)[NSNumber numberWithFloat:640.0f] forKey:(id)kCGImageSourceThumbnailMaxPixelSize];
//[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailWithTransform];
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
if (imageRef) {
result = [UIImage imageWithCGImage:imageRef scale:[assetRepresentation scale] orientation:(UIImageOrientation)[assetRepresentation orientation]];
CGImageRelease(imageRef);
}
if (sourceRef)
CFRelease(sourceRef);
}
return result;
}
You can use it like this:
// Get the full image in a background thread
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage* image = [self fullSizeImageForAssetRepresentation:asset.defaultRepresentation];
dispatch_async(dispatch_get_main_queue(), ^{
// Do something with the UIImage
});
});

Array with UIImageView elements - setImageWithURL

I have such code
arrayWithImages = [[NSMutableArray alloc] init];
NSEnumerator *enumForNames = [arrayWithNames objectEnumerator];
NSEnumerator *enumForURLs = [arrayWithURLs objectEnumerator];
id objName, objURL;
while(objName = [enumForNames nextObject]) {
objURL = [enumForURLs nextObject];
UIImageView *anImage = nil;
[anImage setImageWithURL:[NSURL URLWithString:objURL]];
(...)
[arrayWithImages addObject:anImage];
}
And each time I got SIGABRT in line with "[arrayWithImages addObject:anImage];"
What's here wrong?
I don’t see an setImageWithURL method on UIImageView. Where is this from?
Is there any output from the SIGABRT crash?
Try this code:
// Download the image
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:objURL]];
// Make an UIImage out of it
UIImage *anImage = [UIImage imageWithData:imageData];
// Create an image view with the image
UIImageView *imageView = [[UIImageView alloc] initWithImage:anImage];
// Check to make sure it exists
if (imageView != nil) {
// Add it to your array
[arrayWithImages addObject:imageView];
else {
NSLog(#"Image view is nil");
}
Note that you should download the images asynchronously to avoid hanging the loop. This blog post discussing asynchronous image downloading.
Also if you know that [enumForURLs nextObject]; will return an NSString (or even better, NSURL) you should assign objURL to type NSString (or NSURL).

How do I set the pixels per inch for an exported JPEG image in a Cocoa app?

I have a Cocoa Mac image editing app which lets users export JPEG images. I'm currently using the following code to export these images as JPEG files:
//this is user specified
NSInteger resolution;
NSImage* savedImage = [[NSImage alloc] initWithSize:NSMakeSize(600, 600)];
[savedImage lockFocus];
//draw here
[savedImage unlockFocus];
NSBitmapImageRep* savedImageBitmapRep = [NSBitmapImageRep imageRepWithData:[savedImage TIFFRepresentationUsingCompression:NSTIFFCompressionNone factor:1.0]];
NSDictionary* properties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithFloat:1.0], NSImageCompressionFactor, nil];
//holds the jpeg file
NSData * imageData = nil;
imageData = [savedImageBitmapRep representationUsingType:NSJPEGFileType properties:properties];
However, I would like for the user to be able to provide the pixels per inch for this JPEG image (like you can in Photoshop's export options). What would I need to modify in the above code to adjust this value for the exported JPEG?
I couldn't find a way to do it with the NSImage APIs but CGImage can by setting kCGImagePropertyDPIHeight/Width.
I also set kCGImageDestinationLossyCompressionQuality which I think is the same as NSImageCompressionFactor.
//this is user specified
NSInteger resolution = 100;
NSImage* savedImage = [[NSImage alloc] initWithSize:NSMakeSize(600, 600)];
[savedImage lockFocus];
//draw here
[savedImage unlockFocus];
NSBitmapImageRep* savedImageBitmapRep = [NSBitmapImageRep imageRepWithData:[savedImage TIFFRepresentationUsingCompression:NSTIFFCompressionNone factor:1.0]];
NSDictionary* properties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:1.0], kCGImageDestinationLossyCompressionQuality,
[NSNumber numberWithInteger:resolution], kCGImagePropertyDPIHeight,
[NSNumber numberWithInteger:resolution], kCGImagePropertyDPIWidth,
nil];
NSMutableData* imageData = [NSMutableData data];
CGImageDestinationRef imageDest = CGImageDestinationCreateWithData((CFMutableDataRef) imageData, kUTTypeJPEG, 1, NULL);
CGImageDestinationAddImage(imageDest, [savedImageBitmapRep CGImage], (CFDictionaryRef) properties);
CGImageDestinationFinalize(imageDest);
// Do something with imageData
if (![imageData writeToFile:[#"~/Desktop/test.jpg" stringByExpandingTildeInPath] atomically:NO])
NSLog(#"Failed to write imageData");
For NSImage or NSImageRep you do not set the resolution directly but set the size instead.
For size, numberOfPixels and resolution the following equation holds:
size = numberOfPixels * 72.0 / resolution
size is a length and is expressed in dots with the unit inch/72.
(size and resolution are floats). You can see that for an image with dpi=72 size and numberOfPixels are numerally the same (but the meaning is very different).
After creating an NSBitmapImageRep the size with the desired resolution can be set:
NSBitmapImageRep* savedImageBitmapRep = . . . ; // create the new rep
NSSize newSize;
newSize.width = [savedImageBitmapRep pixelsWide] * 72.0 / resolution; // x-resolution
newSize.height = [savedImageBitmapRep pixelsHigh] * 72.0 / resolution; // y-resolution
[savedImageBitmapRep setSize:newSize];
// save the rep
Two remarks: do you really need the lockFocus / unlockFocus way? The preferred way to build a new NSBitmapImageRep is to use NSGraphicsContext. see : http://www.mail-archive.com/cocoa-dev#lists.apple.com/msg74857.html
And: to use TIFFRepresentation for an NSBitmapImageRep is very time and space consuming. Since 10.6 another
way exists and costs nothing, because lockFocus and unlockFocus create an object of class NSCGImageSnapshotRep which under the hood is a CGImage. (In OS versions before 10.6 it was an NSCachedImageRep.) The following does it:
[anImage lockFocus];
// draw something
[anImage unlockFocus];
// now anImage contains an NSCGImageSnapshotRep
CGImageRef cg = [anImage CGImageForProposedRect:NULL context:nil hints:nil];
NSBitmapImageRep *newRep = [[NSBitmapImageRep alloc] initWithCGImage:cg];
// set the resolution
// here you may NSLog anImage, cg and newRep
// save the newRep
// release the newRep if needed