3 Copyright (c) 2004 Apple, Inc. All rights reserved.
5 #import <WebKit/WebAssertions.h>
6 #import <WebKit/WebGraphicsBridge.h>
7 #import <WebKit/WebImageData.h>
8 #import <WebKit/WebImageDecoder.h>
9 #import <WebKit/WebImageRenderer.h>
10 #import <WebKit/WebImageRendererFactory.h>
11 #import <WebKit/WebKitSystemBits.h>
13 #import <WebCore/WebCoreImageRenderer.h>
15 #import <CoreGraphics/CGContextPrivate.h>
16 #import <CoreGraphics/CGContextGState.h>
17 #import <CoreGraphics/CGColorSpacePrivate.h>
21 #import <ImageIO/CGImageSourcePrivate.h>
23 // Forward declarations of internal methods.
24 @interface WebImageData (WebInternal)
25 - (void)_commonTermination;
26 - (void)_invalidateImages;
27 - (void)_invalidateImageProperties;
28 - (int)_repetitionCount;
29 - (float)_frameDuration;
30 - (void)_stopAnimation;
32 - (CFDictionaryRef)_imageSourceOptions;
33 -(void)_createPDFWithData:(NSData *)data;
34 - (CGPDFDocumentRef)_PDFDocumentRef;
35 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(CGCompositeOperation)op alpha:(float)alpha flipped:(BOOL)flipped context:(CGContextRef)context;
36 - (void)_cacheImages:(size_t)optionalIndex allImages:(BOOL)allImages;
40 @implementation WebImageData
44 // Currently threaded decoding doesn't play well with the WebCore cache. Until
45 // those issues are resolved threaded decoding is OFF by default, even on dual CPU
47 //[WebImageRendererFactory setShouldUseThreadedDecoding:(WebNumberOfCPUs() >= 2 ? YES : NO)];
48 [WebImageRendererFactory setShouldUseThreadedDecoding:NO];
55 if ([WebImageRendererFactory shouldUseThreadedDecoding])
56 decodeLock = [[NSLock alloc] init];
58 imageSource = CGImageSourceCreateIncremental ([self _imageSourceOptions]);
64 - (void)setIsPDF:(BOOL)f
74 - (void)_commonTermination
78 [self _invalidateImages];
79 [self _invalidateImageProperties];
82 CFRelease (fileProperties);
85 CFRelease (imageSource);
87 if (animatingRenderers)
88 CFRelease (animatingRenderers);
90 free (frameDurations);
98 [self _commonTermination];
105 [self _commonTermination];
109 - copyWithZone:(NSZone *)zone
113 copy = [[WebImageData alloc] init];
114 CFRetain (imageSource);
115 copy->imageSource = imageSource;
121 - (size_t)numberOfImages
124 return CGImageSourceGetCount(imageSource);
128 - (size_t)currentFrame
133 - (void)_invalidateImages
137 for (i = 0; i < imagesSize; i++) {
139 CFRelease (images[i]);
146 CFRelease(solidColor);
152 - (void)_invalidateImageProperties
155 for (i = 0; i < imagePropertiesSize; i++) {
156 if (imageProperties[i])
157 CFRelease (imageProperties[i]);
159 free (imageProperties);
161 imagePropertiesSize = 0;
164 - (CFDictionaryRef)_imageSourceOptions
166 static CFDictionaryRef imageSourceOptions;
167 if (!imageSourceOptions) {
168 const void * keys[2] = { kCGImageSourceShouldCache, kCGImageSourceShouldPreferRGB32 };
169 const void * values[2] = { kCFBooleanTrue, kCFBooleanTrue };
170 imageSourceOptions = CFDictionaryCreate (NULL, keys, values, 2,
171 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
173 return imageSourceOptions;
176 - (CGImageRef)_noColorCorrectionImage:(CGImageRef)image withProperties:(CFDictionaryRef)props;
178 CGColorSpaceRef uncorrectedColorSpace = 0;
179 CGImageRef noColorCorrectionImage = 0;
181 if (!CFDictionaryGetValue (props, kCGImagePropertyProfileName)) {
182 CFStringRef colorModel = CFDictionaryGetValue (props, kCGImagePropertyColorModel);
185 if (CFStringCompare (colorModel, CFSTR("RGB"), 0) == kCFCompareEqualTo)
186 uncorrectedColorSpace = CGColorSpaceCreateDisplayRGB();
187 else if (CFStringCompare (colorModel, CFSTR("Gray"), 0) == kCFCompareEqualTo)
188 uncorrectedColorSpace = CGColorSpaceCreateDisplayGray();
191 if (uncorrectedColorSpace) {
192 noColorCorrectionImage = CGImageCreateCopyWithColorSpace (image, uncorrectedColorSpace);
193 CFRelease (uncorrectedColorSpace);
196 return noColorCorrectionImage;
199 - (CGImageRef)imageAtIndex:(size_t)index
201 if (index >= [self numberOfImages])
204 if (!images || images[index] == 0){
205 [self _cacheImages:index allImages:NO];
208 return images[index];
211 - (CFDictionaryRef)fileProperties
213 if (!fileProperties) {
214 fileProperties = CGImageSourceCopyProperties (imageSource, [self _imageSourceOptions]);
217 return fileProperties;
220 - (CFDictionaryRef)propertiesAtIndex:(size_t)index
222 size_t num = [self numberOfImages];
224 // Number of images changed!
225 if (imagePropertiesSize && num > imagePropertiesSize) {
227 [self _invalidateImageProperties];
230 if (imageProperties == 0 && num) {
231 imageProperties = (CFDictionaryRef *)malloc (num * sizeof(CFDictionaryRef));
233 for (i = 0; i < num; i++) {
234 #if USE_DEPRECATED_IMAGESOURCE_API
235 imageProperties[i] = CGImageSourceGetPropertiesAtIndex (imageSource, i, [self _imageSourceOptions]);
236 if (imageProperties[i])
237 CFRetain (imageProperties[i]);
239 imageProperties[i] = CGImageSourceCopyPropertiesAtIndex (imageSource, i, [self _imageSourceOptions]);
242 imagePropertiesSize = num;
246 // If image properties are nil, try to get them again. May have attempted to
247 // get them before enough data was available in the header.
248 if (imageProperties[index] == 0) {
249 #if USE_DEPRECATED_IMAGESOURCE_API
250 imageProperties[index] = CGImageSourceGetPropertiesAtIndex (imageSource, index, [self _imageSourceOptions]);
251 if (imageProperties[index])
252 CFRetain (imageProperties[index]);
254 imageProperties[index] = CGImageSourceCopyPropertiesAtIndex (imageSource, index, [self _imageSourceOptions]);
258 return imageProperties[index];
264 - (void)_checkSolidColor: (CGImageRef)image
268 CFRelease(solidColor);
272 // Currently we only check for solid color in the important special case of a 1x1 image
273 if( image && CGImageGetWidth(image)==1 && CGImageGetHeight(image)==1 ) {
274 float pixel[4]; // RGBA
275 CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
276 CGContextRef bmap = CGBitmapContextCreate(&pixel,1,1,8*sizeof(float),sizeof(pixel),space,
277 kCGImageAlphaPremultipliedLast | kCGBitmapFloatComponents);
279 CGContextSetCompositeOperation(bmap, kCGCompositeCopy);
280 CGRect dst = {{0,0},{1,1}};
281 CGContextDrawImage(bmap,dst,image);
283 solidColor = CGColorCreate(space,pixel);
286 /*NSLog(@"WebImageData %p: 1x1 image has color {%f,%f,%f,%f} => %p",
287 self,pixel[0],pixel[1],pixel[2],pixel[3],solidColor);*/
289 ERROR("Couldn't create CGBitmapContext");
295 - (void)_cacheImages:(size_t)optionalIndex allImages:(BOOL)allImages
299 imagesSize = [self numberOfImages];
307 from = optionalIndex;
308 to = optionalIndex+1;
310 for (i = from; i < to; i++) {
312 images = (CGImageRef *)calloc (imagesSize, sizeof(CGImageRef));
315 images[i] = CGImageSourceCreateImageAtIndex (imageSource, i, [self _imageSourceOptions]);
318 if (from==0 && to>0) {
319 // Loaded image 0. Check whether it's a solid color:
320 [self _checkSolidColor: images[0]];
324 // Called from decoder thread.
325 - (void)decodeData:(CFDataRef)data isComplete:(BOOL)isComplete callback:(id)callback
331 [self _createPDFWithData:(NSData *)data];
335 // The work of decoding is actually triggered by image creation.
336 CGImageSourceUpdateData (imageSource, data, isComplete);
337 [self _invalidateImages];
338 [self _cacheImages:0 allImages:YES];
344 if (isComplete && callback) {
345 if ([self _PDFDocumentRef]) {
346 [WebImageDecoder decodeComplete:callback status:kCGImageStatusComplete];
349 [WebImageDecoder decodeComplete:callback status:kCGImageStatusInvalidData];
354 // Use status from first image to trigger appropriate notification back to WebCore
357 CGImageSourceStatus imageStatus = CGImageSourceGetStatusAtIndex(imageSource, 0);
359 // Lie about status. If we have all the data, go ahead and say we're complete
360 // as long we are have at least some valid bands (i.e. >= kCGImageStatusIncomplete).
361 // We have to lie because CG incorrectly reports the status.
362 if (isComplete && imageStatus >= kCGImageStatusIncomplete)
363 imageStatus = kCGImageStatusComplete;
365 // Only send bad status if we've read the whole image.
366 if (isComplete || (!isComplete && imageStatus >= kCGImageStatusIncomplete))
367 [WebImageDecoder decodeComplete:callback status:imageStatus];
372 - (BOOL)_isSizeAvailable
374 CGImageSourceStatus imageSourceStatus = CGImageSourceGetStatus(imageSource);
379 // With ImageIO-55 the meta data for an image is updated progressively. We don't want
380 // to indicate that the image is 'ready' for layout until we know the size. So, we
381 // have to try getting the meta until we have a valid width and height property.
382 if (imageSourceStatus >= kCGImageStatusIncomplete) {
383 CFDictionaryRef image0Properties = CGImageSourceCopyPropertiesAtIndex (imageSource, 0, [self _imageSourceOptions]);
384 if (image0Properties) {
385 CFNumberRef widthNumber = CFDictionaryGetValue (image0Properties, kCGImagePropertyPixelWidth);
386 CFNumberRef heightNumber = CFDictionaryGetValue (image0Properties, kCGImagePropertyPixelHeight);
387 sizeAvailable = widthNumber && heightNumber;
389 if (imageProperties) {
390 if (imageProperties[0])
391 CFRelease(imageProperties[0]);
394 imagePropertiesSize = [self numberOfImages];
395 imageProperties = (CFDictionaryRef *)calloc (imagePropertiesSize, sizeof(CFDictionaryRef));
398 imageProperties[0] = image0Properties;
402 return sizeAvailable;
405 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)callback
407 #ifdef kImageBytesCutoff
408 // This is a hack to help with testing display of partially-loaded images.
409 // To enable it, define kImageBytesCutoff to be a size smaller than that of the image files
410 // being loaded. They'll never finish loading.
411 if( length > kImageBytesCutoff ) {
412 length = kImageBytesCutoff;
417 CFDataRef data = CFDataCreate (NULL, bytes, length);
420 [WebImageDecoder performDecodeWithImage:self data:data isComplete:isComplete callback:callback];
425 [self _createPDFWithData:(NSData *)data];
429 [self _invalidateImages];
430 CGImageSourceUpdateData (imageSource, data, isComplete);
436 // Image properties will not be available until the first frame of the file
437 // reaches kCGImageStatusIncomplete. New as of ImageIO-55, see 4031602.
439 return [self _isSizeAvailable];
445 - (void)_fillSolidColorInRect:(CGRect)rect compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext
447 /*NSLog(@"WebImageData %p: filling with color %p, in {%.0f,%.0f, %.0f x %.0f}",
448 self,solidColor,rect.origin.x,rect.origin.y,rect.size.width,rect.size.height);*/
450 CGContextSaveGState (aContext);
451 CGContextSetFillColorWithColor(aContext, solidColor);
452 CGContextSetCompositeOperation (aContext, op);
453 CGContextFillRect (aContext, rect);
454 CGContextRestoreGState (aContext);
458 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr adjustedSize:(CGSize)adjustedSize compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
461 [self _PDFDrawFromRect:NSMakeRect(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height)
462 toRect:NSMakeRect(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height)
471 CGImageRef image = [self imageAtIndex:index];
478 if( isSolidColor && index==0 ) {
479 [self _fillSolidColorInRect: ir compositeOperation: op context: aContext];
482 CGContextSaveGState (aContext);
484 // Get the height (in adjusted, i.e. scaled, coords) of the portion of the image
485 // that is currently decoded. This could be less that the actual height.
486 CGSize selfSize = [self size]; // full image size, in pixels
487 float curHeight = CGImageGetHeight(image); // height of loaded portion, in pixels
489 if( curHeight < selfSize.height ) {
490 adjustedSize.height *= curHeight / selfSize.height;
492 // Is the amount of available bands less than what we need to draw? If so,
493 // we may have to clip 'fr' if it goes outside the available bounds.
494 if( CGRectGetMaxY(fr) > adjustedSize.height ) {
495 float frHeight = adjustedSize.height - fr.origin.y; // clip fr to available bounds
496 if( frHeight <= 0 ) {
498 return; // clipped out entirely
500 ir.size.height *= (frHeight / fr.size.height); // scale ir proportionally to fr
501 fr.size.height = frHeight;
506 CGContextSetCompositeOperation (aContext, op);
507 CGContextTranslateCTM (aContext, ir.origin.x, ir.origin.y);
508 CGContextScaleCTM (aContext, 1, -1);
509 CGContextTranslateCTM (aContext, 0, -ir.size.height);
511 // Translated to origin, now draw at 0,0.
512 ir.origin.x = ir.origin.y = 0;
514 // If we're drawing a sub portion of the image then create
515 // a image for the sub portion and draw that.
516 // Test using example site at http://www.meyerweb.com/eric/css/edge/complexspiral/demo.html
517 if (fr.size.width != adjustedSize.width || fr.size.height != adjustedSize.height) {
518 // Convert ft to image pixel coords:
519 float xscale = adjustedSize.width / selfSize.width;
520 float yscale = adjustedSize.height / curHeight; // yes, curHeight, not selfSize.height!
521 fr.origin.x /= xscale;
522 fr.origin.y /= yscale;
523 fr.size.width /= xscale;
524 fr.size.height /= yscale;
526 image = CGImageCreateWithImageInRect (image, fr);
528 CGContextDrawImage (aContext, ir, image);
532 // otherwise draw the whole image.
534 CGContextDrawImage (aContext, ir, image);
537 CGContextRestoreGState (aContext);
544 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
546 [self drawImageAtIndex:index inRect:ir fromRect:fr adjustedSize:[self size] compositeOperation:op context:aContext];
549 static void drawPattern (void * info, CGContextRef context)
551 WebImageData *data = (WebImageData *)info;
553 CGImageRef image = [data imageAtIndex:[data currentFrame]];
554 float w = CGImageGetWidth(image);
555 float h = CGImageGetHeight(image);
556 CGContextDrawImage (context, CGRectMake(0, [data size].height-h, w, h), image);
559 static const CGPatternCallbacks patternCallbacks = { 0, drawPattern, NULL };
561 - (void)tileInRect:(CGRect)rect fromPoint:(CGPoint)point context:(CGContextRef)aContext
567 size_t frame = [self currentFrame];
568 CGImageRef image = [self imageAtIndex:frame];
574 if( frame == 0 && isSolidColor ) {
575 [self _fillSolidColorInRect: rect compositeOperation: kCGCompositeSover context: aContext];
578 CGSize tileSize = [self size];
580 // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
582 oneTileRect.origin.x = rect.origin.x + fmodf(fmodf(-point.x, tileSize.width) - tileSize.width, tileSize.width);
583 oneTileRect.origin.y = rect.origin.y + fmodf(fmodf(-point.y, tileSize.height) - tileSize.height, tileSize.height);
584 oneTileRect.size.height = tileSize.height;
585 oneTileRect.size.width = tileSize.width;
587 // If the single image draw covers the whole area, then just draw once.
588 if (NSContainsRect(oneTileRect, NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height))) {
591 fromRect.origin.x = rect.origin.x - oneTileRect.origin.x;
592 fromRect.origin.y = rect.origin.y - oneTileRect.origin.y;
593 fromRect.size = rect.size;
597 [self drawImageAtIndex:[self currentFrame] inRect:rect fromRect:fromRect compositeOperation:kCGCompositeSover context:aContext];
602 // Compute the appropriate phase relative to the top level view in the window.
603 // Conveniently, the oneTileRect we computed above has the appropriate origin.
604 NSPoint originInWindow = [[NSView focusView] convertPoint:oneTileRect.origin toView:nil];
606 // WebCore may translate the focus, and thus need an extra phase correction
607 NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
608 originInWindow.x += extraPhase.x;
609 originInWindow.y += extraPhase.y;
610 CGSize phase = CGSizeMake(fmodf(originInWindow.x, tileSize.width), fmodf(originInWindow.y, tileSize.height));
612 // Possible optimization: We may want to cache the CGPatternRef
613 CGPatternRef pattern = CGPatternCreate(self, CGRectMake (0, 0, tileSize.width, tileSize.height), CGAffineTransformIdentity, tileSize.width, tileSize.height,
614 kCGPatternTilingConstantSpacing, true, &patternCallbacks);
616 CGContextSaveGState (aContext);
618 CGContextSetPatternPhase(aContext, phase);
620 CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
621 CGContextSetFillColorSpace(aContext, patternSpace);
622 CGColorSpaceRelease(patternSpace);
624 float patternAlpha = 1;
625 CGContextSetFillPattern(aContext, pattern, &patternAlpha);
627 CGContextSetCompositeOperation (aContext, kCGCompositeSover);
629 CGContextFillRect (aContext, rect);
631 CGPatternRelease (pattern);
633 CGContextRestoreGState (aContext);
636 ERROR ("unable to create pattern");
646 return CGImageSourceGetStatus(imageSource) < kCGImageStatusReadingHeader;
652 float w = 0.f, h = 0.f;
656 CGRect mediaBox = [_PDFDoc mediaBox];
657 return mediaBox.size;
661 if (sizeAvailable && !haveSize) {
663 CFDictionaryRef properties = [self propertiesAtIndex:0];
665 CFNumberRef num = CFDictionaryGetValue (properties, kCGImagePropertyPixelWidth);
667 CFNumberGetValue (num, kCFNumberFloat32Type, &w);
668 num = CFDictionaryGetValue (properties, kCGImagePropertyPixelHeight);
670 CFNumberGetValue (num, kCFNumberFloat32Type, &h);
684 - (float)_floatProperty:(CFStringRef)property type:(CFStringRef)type at:(size_t)i
688 CFDictionaryRef properties = [self propertiesAtIndex:i];
694 CFDictionaryRef typeProperties = CFDictionaryGetValue (properties, type);
695 if (!typeProperties) {
700 CFNumberRef num = CFDictionaryGetValue (typeProperties, property);
707 CFNumberGetValue (num, kCFNumberFloat32Type, &value);
714 - (float)_floatFileProperty:(CFStringRef)property type:(CFStringRef)type hasProperty:(BOOL *)hasProperty;
720 CFDictionaryRef properties = [self fileProperties];
727 properties = CFDictionaryGetValue (properties, type);
734 CFNumberRef num = CFDictionaryGetValue (properties, property);
741 CFNumberGetValue (num, kCFNumberFloat32Type, &value);
750 #define MINIMUM_DURATION (.1)
752 - (float)_frameDurationAt:(size_t)i
754 float duration = [self _floatProperty:kCGImagePropertyGIFDelayTime type:kCGImagePropertyGIFDictionary at:i];
755 if (duration <= 0.01) {
757 Many annoying ads specify a 0 duration to make an image flash
758 as quickly as possible.
760 We follow mozilla's behavior and set the minimum duration to
761 100 ms. See 4051389 for more details.
763 duration = MINIMUM_DURATION;
768 - (float)_frameDuration
770 size_t num = [self numberOfImages];
771 if (frameDurationsSize && num > frameDurationsSize) {
772 free (frameDurations);
774 frameDurationsSize = 0;
777 if (!frameDurations) {
780 frameDurations = (float *)malloc (sizeof(float) * num);
781 for (i = 0; i < num; i++) {
782 frameDurations[i] = [self _frameDurationAt:i];
784 frameDurationsSize = num;
786 else if (frameDurations[currentFrame] == 0.f) {
787 frameDurations[currentFrame] = [self _frameDurationAt:currentFrame];
790 return frameDurations[currentFrame];
793 - (int)_repetitionCount
798 // No property means loop once.
799 // A property with value 0 means loops forever.
800 count = [self _floatFileProperty:kCGImagePropertyGIFLoopCount type:kCGImagePropertyGIFDictionary hasProperty:&hasProperty];
807 - (BOOL)isAnimationFinished
809 return animationFinished;
812 static NSMutableSet *activeAnimations;
814 + (void)stopAnimationsInView:(NSView *)aView
816 NSEnumerator *objectEnumerator = [activeAnimations objectEnumerator];
817 WebImageData *animation;
818 NSMutableSet *renderersToStop = nil;
820 // Determine all the renderers that are drawing animations in the view.
821 // A set of sets, one set of renderers for each image being animated
822 // in the view. It is necessary to gather the all renderers to stop
823 // before actually stopping them because the process of stopping them
824 // will modify the active animations and animating renderer collections.
825 while ((animation = [objectEnumerator nextObject])) {
826 NSSet *renderersInView = (NSSet *)CFDictionaryGetValue (animation->animatingRenderers, aView);
827 if (renderersInView) {
828 if (!renderersToStop)
829 renderersToStop = [[NSMutableSet alloc] init];
830 [renderersToStop unionSet:renderersInView];
834 // Now tell them all to stop drawing.
835 [renderersToStop makeObjectsPerformSelector:@selector(stopAnimation)];
836 [renderersToStop release];
839 - (void)addAnimatingRenderer:(WebImageRenderer *)r inView:(NSView *)view
841 if (!animatingRenderers)
842 animatingRenderers = CFDictionaryCreateMutable (NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
844 NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
846 renderers = [[NSMutableSet alloc] init];
847 CFDictionaryAddValue(animatingRenderers, view, renderers);
851 [renderers addObject:r];
853 if (!activeAnimations)
854 activeAnimations = [[NSMutableSet alloc] init];
856 [activeAnimations addObject:self];
859 - (void)removeAnimatingRenderer:(WebImageRenderer *)r
861 NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
863 while ((view = [viewEnumerator nextObject])) {
864 NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
865 [renderers removeObject:r];
866 if ([renderers count] == 0) {
867 CFDictionaryRemoveValue (animatingRenderers, view);
871 if (animatingRenderers && CFDictionaryGetCount(animatingRenderers) == 0) {
872 [activeAnimations removeObject:self];
873 [self _stopAnimation];
877 - (void)resetAnimation
879 [self _stopAnimation];
881 repetitionsComplete = 0;
882 animationFinished = NO;
885 - (void)_stopAnimation
887 // This timer is used to animate all occurences of this image. Don't invalidate
888 // the timer unless all renderers have stopped drawing.
889 [frameTimer invalidate];
890 [frameTimer release];
894 - (void)_nextFrame:(id)context
896 // Release the timer that just fired.
897 [frameTimer release];
901 if (currentFrame >= [self numberOfImages]) {
902 repetitionsComplete += 1;
903 if ([self _repetitionCount] && repetitionsComplete >= [self _repetitionCount]) {
904 animationFinished = YES;
911 NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
913 while ((view = [viewEnumerator nextObject])) {
914 NSMutableSet *renderers = [(NSMutableDictionary *)animatingRenderers objectForKey:view];
915 WebImageRenderer *renderer;
916 NSEnumerator *rendererEnumerator = [renderers objectEnumerator];
917 while ((renderer = [rendererEnumerator nextObject])) {
918 [view setNeedsDisplayInRect:[renderer targetAnimationRect]];
923 - (BOOL)shouldAnimate
925 return [self numberOfImages] > 1 && ![self isAnimationFinished];
930 if (frameTimer && [frameTimer isValid])
932 frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self _frameDuration]
934 selector:@selector(_nextFrame:)
939 -(void)_createPDFWithData:(NSData *)data
942 _PDFDoc = [[WebPDFDocument alloc] initWithData:data];
946 - (CGPDFDocumentRef)_PDFDocumentRef
948 return [_PDFDoc documentRef];
951 - (void)_PDFDrawInContext:(CGContextRef)context
953 CGPDFDocumentRef document = [self _PDFDocumentRef];
954 if (document != NULL) {
955 CGRect mediaBox = [_PDFDoc mediaBox];
957 CGContextSaveGState(context);
958 // Rotate translate image into position according to doc properties.
959 [_PDFDoc adjustCTM:context];
961 // Media box may have non-zero origin which we ignore. CGPDFDocumentShowPage pages start
963 CGContextDrawPDFDocument(context, CGRectMake(0, 0, mediaBox.size.width, mediaBox.size.height), document, 1);
965 CGContextRestoreGState(context);
969 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(CGCompositeOperation)op alpha:(float)alpha flipped:(BOOL)flipped context:(CGContextRef)context
971 float hScale, vScale;
973 CGContextSaveGState(context);
975 CGContextSetCompositeOperation (context, op);
977 // Scale and translate so the document is rendered in the correct location.
978 hScale = dstRect.size.width / srcRect.size.width;
979 vScale = dstRect.size.height / srcRect.size.height;
981 CGContextTranslateCTM (context, dstRect.origin.x - srcRect.origin.x * hScale, dstRect.origin.y - srcRect.origin.y * vScale);
982 CGContextScaleCTM (context, hScale, vScale);
984 // Reverse if flipped image.
986 CGContextScaleCTM(context, 1, -1);
987 CGContextTranslateCTM (context, 0, -dstRect.size.height);
990 // Clip to destination in case we are imaging part of the source only
991 CGContextClipToRect(context, CGRectIntegral(*(CGRect*)&srcRect));
994 [self _PDFDrawInContext:context];
996 // done with our fancy transforms
997 CGContextRestoreGState(context);