Reviewed by Ken.
[WebKit-https.git] / WebKit / WebCoreSupport.subproj / WebImageData.m
1 /*      
2         WebImageData.m
3         Copyright (c) 2004 Apple, Inc. All rights reserved.
4 */
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>
12
13 #import <WebCore/WebCoreImageRenderer.h>
14
15 #import <CoreGraphics/CGContextPrivate.h>
16 #import <CoreGraphics/CGContextGState.h>
17 #import <CoreGraphics/CGColorSpacePrivate.h>
18
19 #ifdef USE_CGIMAGEREF
20
21 static CFDictionaryRef imageSourceOptions;
22
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;
31 - (void)_nextFrame;
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;
37 @end
38
39
40 @implementation WebImageData
41
42 + (void)initialize
43 {
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
46     // machines.
47     //[WebImageRendererFactory setShouldUseThreadedDecoding:(WebNumberOfCPUs() >= 2 ? YES : NO)];
48     [WebImageRendererFactory setShouldUseThreadedDecoding:NO];
49 }
50
51 - init
52 {
53     self = [super init];
54     
55     if ([WebImageRendererFactory shouldUseThreadedDecoding])
56         decodeLock = [[NSLock alloc] init];
57
58     imageSource = CGImageSourceCreateIncremental ([self _imageSourceOptions]);
59     
60     return self;
61 }
62
63 - (void)setIsPDF:(BOOL)f
64 {
65     isPDF = f;
66 }
67
68 - (BOOL)isPDF
69 {
70     return isPDF;
71 }
72
73 - (void)_commonTermination
74 {
75     ASSERT (!frameTimer);
76     
77     [self _invalidateImages];
78     [self _invalidateImageProperties];
79         
80     if (fileProperties)
81         CFRelease (fileProperties);
82         
83     if (imageSource)
84         CFRelease (imageSource); 
85         
86     if (animatingRenderers)
87         CFRelease (animatingRenderers);
88
89     free (frameDurations);
90 }
91
92 - (void)dealloc
93 {
94     [_PDFDoc release];
95     [decodeLock release];
96
97     [self _commonTermination];
98
99     [super dealloc];
100 }
101
102 - (void)finalize
103 {
104     [self _commonTermination];
105     [super finalize];
106 }
107
108 - copyWithZone:(NSZone *)zone
109 {
110     WebImageData *copy;
111
112     copy = [[WebImageData alloc] init];
113     CFRetain (imageSource);
114     copy->imageSource = imageSource;
115     
116     return copy;
117 }
118
119
120 - (size_t)numberOfImages
121 {
122     if (imageSource)
123         return CGImageSourceGetCount(imageSource);
124     return 0;
125 }
126
127 - (size_t)currentFrame
128 {
129     return currentFrame;
130 }
131
132 - (void)_invalidateImages
133 {
134     if (images) {
135         size_t i;
136         for (i = 0; i < imagesSize; i++) {
137             if (images[i])
138                 CFRelease (images[i]);
139         }
140         free (images);
141         images = 0;
142     }
143 }
144
145 - (void)_invalidateImageProperties
146 {
147     size_t i;
148     for (i = 0; i < imagePropertiesSize; i++) {
149         if (imageProperties[i])
150             CFRelease (imageProperties[i]);
151     }
152     free (imageProperties);
153     imageProperties = 0;
154     imagePropertiesSize = 0;
155 }
156
157 - (CFDictionaryRef)_imageSourceOptions
158 {
159     if (!imageSourceOptions) {
160         const void * keys[1] = { kCGImageSourceShouldCache };
161         const void * values[1] = { kCFBooleanTrue };
162         imageSourceOptions = CFDictionaryCreate (NULL, keys, values, 1, 
163                 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
164     }
165     return imageSourceOptions;
166 }
167
168 - (CGImageRef)_noColorCorrectionImage:(CGImageRef)image withProperties:(CFDictionaryRef)props;
169 {
170     CGColorSpaceRef uncorrectedColorSpace = 0;
171     CGImageRef noColorCorrectionImage = 0;
172
173     if (!CFDictionaryGetValue (props, kCGImagePropertyProfileName)) {
174         CFStringRef colorModel = CFDictionaryGetValue (props, kCGImagePropertyColorModel);
175         
176         if (colorModel) {
177             if (CFStringCompare (colorModel, CFSTR("RGB"), 0) == kCFCompareEqualTo)
178                 uncorrectedColorSpace = CGColorSpaceCreateDisplayRGB();
179             else if (CFStringCompare (colorModel, CFSTR("Gray"), 0) == kCFCompareEqualTo)
180                 uncorrectedColorSpace = CGColorSpaceCreateDisplayGray();
181         }
182         
183         if (uncorrectedColorSpace) {
184             noColorCorrectionImage = CGImageCreateCopyWithColorSpace (image, uncorrectedColorSpace);
185             CFRelease (uncorrectedColorSpace);
186         }
187     }
188     return noColorCorrectionImage;
189 }
190             
191 - (CGImageRef)imageAtIndex:(size_t)index
192 {
193     if (index >= [self numberOfImages])
194         return 0;
195
196     if (!images || images[index] == 0){
197         [self _cacheImages:index allImages:NO];
198     }
199     
200     return images[index];
201 }
202
203 - (CFDictionaryRef)fileProperties
204 {
205     if (!fileProperties) {
206         fileProperties = CGImageSourceCopyProperties (imageSource, 0);
207     }
208     
209     return fileProperties;
210 }
211
212 - (CFDictionaryRef)propertiesAtIndex:(size_t)index
213 {
214     size_t num = [self numberOfImages];
215     
216     // Number of images changed!
217     if (imagePropertiesSize && num > imagePropertiesSize) {
218         // Clear cache.
219         [self _invalidateImageProperties];
220     }
221     
222     if (imageProperties == 0 && num) {
223         imageProperties = (CFDictionaryRef *)malloc (num * sizeof(CFDictionaryRef));
224         size_t i;
225         for (i = 0; i < num; i++) {
226 #if USE_DEPRECATED_IMAGESOURCE_API      
227             imageProperties[i] = CGImageSourceGetPropertiesAtIndex (imageSource, i, 0);
228             if (imageProperties[i])
229                 CFRetain (imageProperties[i]);
230 #else
231             imageProperties[i] = CGImageSourceCopyPropertiesAtIndex (imageSource, i, 0);
232 #endif
233         }
234         imagePropertiesSize = num;
235     }
236     
237     if (index < num) {
238         // If image properties are nil, try to get them again.  May have attempted to
239         // get them before enough data was available in the header.
240         if (imageProperties[index] == 0) {
241 #if USE_DEPRECATED_IMAGESOURCE_API      
242             imageProperties[index] = CGImageSourceGetPropertiesAtIndex (imageSource, index, 0);
243             if (imageProperties[index])
244                 CFRetain (imageProperties[index]);
245 #else
246             imageProperties[index] = CGImageSourceCopyPropertiesAtIndex (imageSource, index, 0);
247 #endif
248         }
249         
250         return imageProperties[index];
251     }
252         
253     return 0;
254 }
255
256 - (void)_cacheImages:(size_t)optionalIndex allImages:(BOOL)allImages
257 {
258     size_t i;
259
260     imagesSize = [self numberOfImages];
261     size_t from, to;
262     
263     if (allImages) {
264         from = 0;
265         to = imagesSize;
266     }
267     else {
268         from = optionalIndex;
269         to = optionalIndex+1;
270     }
271     for (i = from; i < to; i++) {
272         if (!images) {
273             images = (CGImageRef *)calloc (imagesSize, sizeof(CGImageRef *));
274         }
275
276         images[i] = CGImageSourceCreateImageAtIndex (imageSource, i, [self _imageSourceOptions]);
277     }
278 }
279
280 // Called from decoder thread.
281 - (void)decodeData:(CFDataRef)data isComplete:(BOOL)isComplete callback:(id)callback
282 {
283     [decodeLock lock];
284     
285     if (isPDF) {
286         if (isComplete) {
287             [self _createPDFWithData:(NSData *)data];
288         }
289     }
290     else {
291         // The work of decoding is actually triggered by image creation.
292         CGImageSourceUpdateData (imageSource, data, isComplete);
293         [self _invalidateImages];
294         [self _cacheImages:0 allImages:YES];
295     }
296         
297     [decodeLock unlock];
298
299     if (isPDF) {
300         if (isComplete && callback) {
301             if ([self _PDFDocumentRef]) {
302                 [WebImageDecoder decodeComplete:callback status:kCGImageStatusComplete];
303             }
304             else {
305                 [WebImageDecoder decodeComplete:callback status:kCGImageStatusInvalidData];
306             }
307         }
308     }
309     else {
310         // Use status from first image to trigger appropriate notification back to WebCore
311         // on main thread.
312         if (callback) {
313             CGImageSourceStatus imageStatus = CGImageSourceGetStatusAtIndex(imageSource, 0);
314             
315             // Lie about status.  If we have all the data, go ahead and say we're complete
316             // as long we are have at least some valid bands (i.e. >= kCGImageStatusIncomplete).
317             // We have to lie because CG incorrectly reports the status.
318             if (isComplete && imageStatus >= kCGImageStatusIncomplete)
319                 imageStatus = kCGImageStatusComplete;
320             
321             // Only send bad status if we've read the whole image.
322             if (isComplete || (!isComplete && imageStatus >= kCGImageStatusIncomplete))
323                 [WebImageDecoder decodeComplete:callback status:imageStatus];
324         }
325     }
326 }
327
328 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)callback
329 {
330     CFDataRef data = CFDataCreate (NULL, bytes, length);
331     
332     if (callback) {
333         [WebImageDecoder performDecodeWithImage:self data:data isComplete:isComplete callback:callback];
334     }
335     else {
336         if (isPDF) {
337             if (isComplete) {
338                 [self _createPDFWithData:(NSData *)data];
339             }
340         }
341         else {
342             [self _invalidateImages];
343             CGImageSourceUpdateData (imageSource, data, isComplete);
344         }
345     }
346     
347     CFRelease (data);
348
349     return YES;
350 }
351
352 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr adjustedSize:(CGSize)adjustedSize compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
353 {
354     if (isPDF) {
355         [self _PDFDrawFromRect:NSMakeRect(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height)
356                 toRect:NSMakeRect(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height)
357                 operation:op 
358                 alpha:1.0 
359                 flipped:YES
360                 context:aContext];
361     }
362     else {
363         [decodeLock lock];
364         
365         CGImageRef image = [self imageAtIndex:index];
366         
367         if (!image) {
368             [decodeLock unlock];
369             return;
370         }
371
372         CGContextSaveGState (aContext);
373
374         // Get the height of the portion of the image that is currently decoded.  This
375         // could be less that the actual height.
376         float h = CGImageGetHeight(image);
377
378         // Is the amount of available bands less than what we need to draw?  If so,
379         // clip.
380         BOOL clipped = NO;
381         CGSize actualSize = [self size];
382         if (h != actualSize.height) {
383             float proportionLoaded = h/actualSize.height;
384             fr.size.height = fr.size.height * proportionLoaded;
385             ir.size.height = ir.size.height * proportionLoaded;
386             clipped = YES;
387         }
388         
389         // Flip the coords.
390         CGContextSetCompositeOperation (aContext, op);
391         CGContextTranslateCTM (aContext, ir.origin.x, ir.origin.y);
392         CGContextScaleCTM (aContext, 1, -1);
393         CGContextTranslateCTM (aContext, 0, -ir.size.height);
394         
395         // Translated to origin, now draw at 0,0.
396         ir.origin.x = ir.origin.y = 0;
397         
398         // If we're drawing a sub portion of the image then create
399         // a image for the sub portion and draw that.
400         // Test using example site at http://www.meyerweb.com/eric/css/edge/complexspiral/demo.html
401         if (clipped == NO && (fr.size.width != adjustedSize.width || fr.size.height != adjustedSize.height)) {
402             image = CGImageCreateWithImageInRect (image, fr);
403             if (image) {
404             CGContextDrawImage (aContext, ir, image);
405             CFRelease (image);
406             }
407         }
408         // otherwise draw the whole image.
409         else { 
410             CGContextDrawImage (aContext, ir, image);
411         }
412
413         CGContextRestoreGState (aContext);
414
415         [decodeLock unlock];
416     }
417 }
418
419 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
420 {    
421     [self drawImageAtIndex:index inRect:ir fromRect:fr adjustedSize:[self size] compositeOperation:op context:aContext];
422 }
423
424 static void drawPattern (void * info, CGContextRef context)
425 {
426     WebImageData *data = (WebImageData *)info;
427     
428     CGImageRef image = (CGImageRef)[data imageAtIndex:[data currentFrame]];
429     float w = CGImageGetWidth(image);
430     float h = CGImageGetHeight(image);
431     CGContextDrawImage (context, CGRectMake(0, 0, w, h), image);    
432 }
433
434 CGPatternCallbacks patternCallbacks = { 0, drawPattern, NULL };
435
436 - (void)tileInRect:(CGRect)rect fromPoint:(CGPoint)point context:(CGContextRef)aContext
437 {
438     ASSERT (aContext);
439
440     [decodeLock lock];
441     
442     CGImageRef image = [self imageAtIndex:[self currentFrame]];
443     if (!image) {
444         ERROR ("unable to find image");
445         [decodeLock unlock];
446         return;
447     }
448
449     float w = CGImageGetWidth(image);
450     float h = CGImageGetHeight(image);
451
452     // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
453     NSRect oneTileRect;
454     oneTileRect.origin.x = rect.origin.x + fmodf(fmodf(-point.x, w) - w, w);
455     oneTileRect.origin.y = rect.origin.y + fmodf(fmodf(-point.y, h) - h, h);
456     oneTileRect.size.width = w;
457     oneTileRect.size.height = h;
458
459     // If the single image draw covers the whole area, then just draw once.
460     if (NSContainsRect(oneTileRect, NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height))) {
461         CGRect fromRect;
462
463         fromRect.origin.x = rect.origin.x - oneTileRect.origin.x;
464         fromRect.origin.y = rect.origin.y - oneTileRect.origin.y;
465         fromRect.size = rect.size;
466
467         [decodeLock unlock];
468         
469         [self drawImageAtIndex:[self currentFrame] inRect:rect fromRect:fromRect compositeOperation:kCGCompositeSover context:aContext];
470
471         return;
472     }
473
474     // Compute the appropriate phase relative to the top level view in the window.
475     // Conveniently, the oneTileRect we computed above has the appropriate origin.
476     NSPoint originInWindow = [[NSView focusView] convertPoint:oneTileRect.origin toView:nil];
477
478     // WebCore may translate the focus, and thus need an extra phase correction
479     NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
480     originInWindow.x += extraPhase.x;
481     originInWindow.y += extraPhase.y;
482     CGSize phase = CGSizeMake(fmodf(originInWindow.x, w), fmodf(originInWindow.y, h));
483
484     // Possible optimization:  We may want to cache the CGPatternRef    
485     CGPatternRef pattern = CGPatternCreate(self, CGRectMake (0, 0, w, h), CGAffineTransformIdentity, w, h, 
486         kCGPatternTilingConstantSpacing, true, &patternCallbacks);
487     if (pattern) {
488         CGContextSaveGState (aContext);
489
490         CGContextSetPatternPhase(aContext, phase);
491
492         CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
493         CGContextSetFillColorSpace(aContext, patternSpace);
494         CGColorSpaceRelease(patternSpace);
495
496         float patternAlpha = 1;
497         CGContextSetFillPattern(aContext, pattern, &patternAlpha);
498
499         CGContextSetCompositeOperation (aContext, kCGCompositeSover);
500
501         CGContextFillRect (aContext, rect);
502
503         CGPatternRelease (pattern);
504
505         CGContextRestoreGState (aContext);
506     }
507     else {
508         ERROR ("unable to create pattern");
509     }
510
511     [decodeLock unlock];
512 }
513
514 - (BOOL)isNull
515 {
516     if (imageSource)
517         return CGImageSourceGetStatus(imageSource) < kCGImageStatusReadingHeader;
518     return YES;
519 }
520
521 - (CGSize)size
522 {
523     float w = 0.f, h = 0.f;
524
525     if (isPDF) {
526         if (_PDFDoc) {
527             CGRect mediaBox = [_PDFDoc mediaBox];
528             return mediaBox.size;
529         }
530     }
531     else {
532         if (!haveSize) {
533             [decodeLock lock];
534             CFDictionaryRef properties = [self propertiesAtIndex:0];
535             if (properties) {
536             CFNumberRef num = CFDictionaryGetValue (properties, kCGImagePropertyPixelWidth);
537             if (num)
538                 CFNumberGetValue (num, kCFNumberFloat32Type, &w);
539             num = CFDictionaryGetValue (properties, kCGImagePropertyPixelHeight);
540             if (num)
541                 CFNumberGetValue (num, kCFNumberFloat32Type, &h);
542
543             size.width = w;
544             size.height = h;
545             
546             haveSize = YES;
547             }
548             [decodeLock unlock];
549         }
550     }
551     
552     return size;
553 }
554
555 - (float)_floatProperty:(CFStringRef)property type:(CFStringRef)type at:(size_t)i
556 {
557     [decodeLock lock];
558     
559     CFDictionaryRef properties = [self propertiesAtIndex:i];
560     if (!properties) {
561         [decodeLock unlock];
562         return 0.f;
563     }
564     
565     CFDictionaryRef typeProperties = CFDictionaryGetValue (properties, type);
566     if (!typeProperties) {
567         [decodeLock unlock];
568         return 0.f;
569     }
570     
571     CFNumberRef num = CFDictionaryGetValue (typeProperties, property);
572     if (!num) {
573         [decodeLock unlock];
574         return 0.f;
575     }
576     
577     float value = 0.f;
578     CFNumberGetValue (num, kCFNumberFloat32Type, &value);
579
580     [decodeLock unlock];
581     
582     return value;
583 }
584
585 - (float)_floatFileProperty:(CFStringRef)property type:(CFStringRef)type 
586 {
587     [decodeLock lock];
588     
589     CFDictionaryRef properties = [self fileProperties];
590     if (!properties) {
591         [decodeLock unlock];
592         return 0.f;
593     }
594     
595     if (type) {
596         properties = CFDictionaryGetValue (properties, type);
597         if (!properties) {
598             [decodeLock unlock];
599             return 0.f;
600         }
601     }
602     
603     CFNumberRef num = CFDictionaryGetValue (properties, property);
604     if (!num) {
605         [decodeLock unlock];
606         return 0.f;
607     }
608     
609     float value = 0.f;
610     CFNumberGetValue (num, kCFNumberFloat32Type, &value);
611
612     [decodeLock unlock];
613     
614     return value;
615 }
616
617 #define MINIMUM_DURATION (1.0/30.0)
618
619 - (float)_frameDurationAt:(size_t)i
620 {
621     float duration = [self _floatProperty:kCGImagePropertyGIFDelayTime type:kCGImagePropertyGIFDictionary at:i];
622     if (duration < MINIMUM_DURATION) {
623         /*
624             Many annoying ads specify a 0 duration to make an image flash
625             as quickly as possible.  However a zero duration is faster than
626             the refresh rate.  We need to pick a minimum duration.
627             
628             Browsers handle the minimum time case differently.  IE seems to use something
629             close to 1/30th of a second.  Konqueror uses 0.  The ImageMagick library
630             uses 1/100th.  The units in the GIF specification are 1/100th of second.
631             We will use 1/30th of second as the minimum time.
632         */
633         duration = MINIMUM_DURATION;
634     }
635     return duration;
636 }
637
638 - (float)_frameDuration
639 {
640     size_t num = [self numberOfImages];
641     if (frameDurationsSize && num > frameDurationsSize) {
642         free (frameDurations);
643         frameDurations = 0;
644         frameDurationsSize = 0;
645     }
646     
647     if (!frameDurations) {
648         size_t i;
649
650         frameDurations = (float *)malloc (sizeof(float) * num);
651         for (i = 0; i < num; i++) {
652             frameDurations[i] = [self _frameDurationAt:i];
653         }
654         frameDurationsSize = num;
655     }
656     else if (frameDurations[currentFrame] == 0.f) {
657         frameDurations[currentFrame] = [self _frameDurationAt:currentFrame];
658     }
659
660     return frameDurations[currentFrame];
661 }
662
663 - (int)_repetitionCount
664 {
665     return [self _floatFileProperty:kCGImagePropertyGIFLoopCount type:kCGImagePropertyGIFDictionary];
666 }
667
668 - (BOOL)isAnimationFinished
669 {
670     return animationFinished;
671 }
672
673 static NSMutableSet *activeAnimations;
674
675 + (void)stopAnimationsInView:(NSView *)aView
676 {
677     NSEnumerator *objectEnumerator = [activeAnimations objectEnumerator];
678     WebImageData *animation;
679     NSMutableSet *renderersToStop = nil;
680
681     // Determine all the renderers that are drawing animations in the view.
682     // A set of sets, one set of renderers for each image being animated
683     // in the view.  It is necessary to gather the all renderers to stop
684     // before actually stopping them because the process of stopping them
685     // will modify the active animations and animating renderer collections.
686     while ((animation = [objectEnumerator nextObject])) {
687         NSSet *renderersInView = (NSSet *)CFDictionaryGetValue (animation->animatingRenderers, aView);
688         if (renderersInView) {
689             if (!renderersToStop)
690                 renderersToStop = [[NSMutableSet alloc] init];
691             [renderersToStop unionSet:renderersInView];
692         }
693     }
694
695     // Now tell them all to stop drawing.
696     [renderersToStop makeObjectsPerformSelector:@selector(stopAnimation)];
697 }
698
699 - (void)addAnimatingRenderer:(WebImageRenderer *)r inView:(NSView *)view
700 {
701     if (!animatingRenderers)
702         animatingRenderers = CFDictionaryCreateMutable (NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
703     
704     NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
705     if (!renderers) {
706         renderers = [[NSMutableSet alloc] init];
707         CFDictionaryAddValue(animatingRenderers, view, renderers);
708         [renderers release];
709     }
710             
711     [renderers addObject:r];
712
713     if (!activeAnimations)
714         activeAnimations = [[NSMutableSet alloc] init];
715     
716     [activeAnimations addObject:self];
717 }
718
719 - (void)removeAnimatingRenderer:(WebImageRenderer *)r
720 {
721     NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
722     NSView *view;
723     while ((view = [viewEnumerator nextObject])) {
724         NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
725         [renderers removeObject:r];
726         if ([renderers count] == 0) {
727             CFDictionaryRemoveValue (animatingRenderers, view);
728         }
729     }
730     
731     if (animatingRenderers && CFDictionaryGetCount(animatingRenderers) == 0) {
732         [activeAnimations removeObject:self];
733         [self _stopAnimation];
734     }
735 }
736
737 - (void)_stopAnimation
738 {
739     // This timer is used to animate all occurences of this image.  Don't invalidate
740     // the timer unless all renderers have stopped drawing.
741     [frameTimer invalidate];
742     [frameTimer release];
743     frameTimer = nil;
744 }
745
746 - (void)_nextFrame:(id)context
747 {
748     // Release the timer that just fired.
749     [frameTimer release];
750     frameTimer = nil;
751     
752     currentFrame++;
753     if (currentFrame >= [self numberOfImages]) {
754         repetitionsComplete += 1;
755         if ([self _repetitionCount] && repetitionsComplete >= [self _repetitionCount]) {
756             animationFinished = YES;
757             return;
758         }
759         currentFrame = 0;
760     }
761     
762     NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
763     NSView *view;
764     while ((view = [viewEnumerator nextObject])) {
765         NSMutableSet *renderers = [(NSMutableDictionary *)animatingRenderers objectForKey:view];
766         WebImageRenderer *renderer;
767         NSEnumerator *rendererEnumerator = [renderers objectEnumerator];
768         while ((renderer = [rendererEnumerator nextObject])) {
769             [view setNeedsDisplayInRect:[renderer targetAnimationRect]];
770         }
771     }
772 }
773
774 - (BOOL)shouldAnimate
775 {
776     return [self numberOfImages] > 1 && ![self isAnimationFinished];
777 }
778
779 - (void)animate
780 {
781     if (frameTimer && [frameTimer isValid])
782         return;
783     frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self _frameDuration]
784                                                     target:self
785                                                   selector:@selector(_nextFrame:)
786                                                   userInfo:nil
787                                                    repeats:NO] retain];
788 }
789
790 -(void)_createPDFWithData:(NSData *)data
791 {
792     if (!_PDFDoc) {
793         _PDFDoc = [[WebPDFDocument alloc] initWithData:data];
794     }
795 }
796
797 - (CGPDFDocumentRef)_PDFDocumentRef
798 {
799     return [_PDFDoc documentRef];
800 }
801
802 - (void)_PDFDrawInContext:(CGContextRef)context
803 {
804     CGPDFDocumentRef document = [self _PDFDocumentRef];
805     if (document != NULL) {
806         CGRect       mediaBox = [_PDFDoc mediaBox];
807         
808         CGContextSaveGState(context);
809         // Rotate translate image into position according to doc properties.
810         [_PDFDoc adjustCTM:context];    
811
812         // Media box may have non-zero origin which we ignore. CGPDFDocumentShowPage pages start
813         // at 1, not 0.
814         CGContextDrawPDFDocument(context, CGRectMake(0, 0, mediaBox.size.width, mediaBox.size.height), document, 1);
815
816         CGContextRestoreGState(context);
817     }
818 }
819
820 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(CGCompositeOperation)op alpha:(float)alpha flipped:(BOOL)flipped context:(CGContextRef)context
821 {
822     float hScale, vScale;
823
824     CGContextSaveGState(context);
825
826     CGContextSetCompositeOperation (context, op);
827
828     // Scale and translate so the document is rendered in the correct location.
829     hScale = dstRect.size.width  / srcRect.size.width;
830     vScale = dstRect.size.height / srcRect.size.height;
831
832     CGContextTranslateCTM (context, dstRect.origin.x - srcRect.origin.x * hScale, dstRect.origin.y - srcRect.origin.y * vScale);
833     CGContextScaleCTM (context, hScale, vScale);
834
835     // Reverse if flipped image.
836     if (flipped) {
837         CGContextScaleCTM(context, 1, -1);
838         CGContextTranslateCTM (context, 0, -(dstRect.origin.y + dstRect.size.height));
839     }
840
841     // Clip to destination in case we are imaging part of the source only
842     CGContextClipToRect(context, CGRectIntegral(*(CGRect*)&srcRect));
843
844     // and draw
845     [self _PDFDrawInContext:context];
846
847     // done with our fancy transforms
848     CGContextRestoreGState(context);
849
850     return YES;
851 }
852
853 @end
854
855 #endif