Andraz
Andraz

Reputation: 152

Creating an animated GIF in Cocoa - defining frame type

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

single frame
(source: andraz.eu)

And this is the GIF with the invalid colors ("combine" mode) combined frames
(source: andraz.eu)

Upvotes: 4

Views: 5139

Answers (1)

Jiulong Zhao
Jiulong Zhao

Reputation: 1363

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);

Upvotes: 2

Related Questions