b28719e114f949d3763d85ef6be95e3802b87d5c
[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         CFStringRef keys[1] = { kCGImageSourceShouldCache };
161         CFBooleanRef values[1] = { kCFBooleanTrue };
162         imageSourceOptions = CFDictionaryCreate (NULL, (const void **)&keys, (const void **)&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, (CFStringRef)@"RGB", 0) == kCFCompareEqualTo)
178                 uncorrectedColorSpace = CGColorSpaceCreateDisplayRGB();
179             else if (CFStringCompare (colorModel, (CFStringRef)@"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     NSSet *renderersInView;
687     while ((animation = [objectEnumerator nextObject])) {
688         renderersInView = (NSSet *)CFDictionaryGetValue (animation->animatingRenderers, aView);
689         if (renderersInView) {
690             if (!renderersToStop)
691                 renderersToStop = [[NSMutableSet alloc] init];
692             [renderersToStop addObject: renderersInView];
693         }
694     }
695
696     // Now tell them all to stop drawing.
697     if (renderersToStop) {
698         objectEnumerator = [renderersToStop objectEnumerator];
699         while ((renderersInView = [objectEnumerator nextObject])) {
700             [renderersInView makeObjectsPerformSelector:@selector(stopAnimation)];
701         }
702         
703         [renderersToStop release];
704     }
705 }
706
707 - (void)addAnimatingRenderer:(WebImageRenderer *)r inView:(NSView *)view
708 {
709     if (!animatingRenderers)
710         animatingRenderers = CFDictionaryCreateMutable (NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
711     
712     NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
713     if (!renderers) {
714         renderers = [[NSMutableSet alloc] init];
715         CFDictionaryAddValue(animatingRenderers, view, renderers);
716         [renderers release];
717     }
718             
719     [renderers addObject:r];
720
721     if (!activeAnimations)
722         activeAnimations = [[NSMutableSet alloc] init];
723     
724     [activeAnimations addObject:self];
725 }
726
727 - (void)removeAnimatingRenderer:(WebImageRenderer *)r
728 {
729     NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
730     NSView *view;
731     while ((view = [viewEnumerator nextObject])) {
732         NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
733         [renderers removeObject:r];
734         if ([renderers count] == 0) {
735             CFDictionaryRemoveValue (animatingRenderers, view);
736         }
737     }
738     
739     if (animatingRenderers && CFDictionaryGetCount(animatingRenderers) == 0) {
740         [activeAnimations removeObject:self];
741         [self _stopAnimation];
742     }
743 }
744
745 - (void)_stopAnimation
746 {
747     // This timer is used to animate all occurences of this image.  Don't invalidate
748     // the timer unless all renderers have stopped drawing.
749     [frameTimer invalidate];
750     [frameTimer release];
751     frameTimer = nil;
752 }
753
754 - (void)_nextFrame:(id)context
755 {
756     // Release the timer that just fired.
757     [frameTimer release];
758     frameTimer = nil;
759     
760     currentFrame++;
761     if (currentFrame >= [self numberOfImages]) {
762         repetitionsComplete += 1;
763         if ([self _repetitionCount] && repetitionsComplete >= [self _repetitionCount]) {
764             animationFinished = YES;
765             return;
766         }
767         currentFrame = 0;
768     }
769     
770     NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
771     NSView *view;
772     while ((view = [viewEnumerator nextObject])) {
773         NSMutableSet *renderers = [(NSMutableDictionary *)animatingRenderers objectForKey:view];
774         WebImageRenderer *renderer;
775         NSEnumerator *rendererEnumerator = [renderers objectEnumerator];
776         while ((renderer = [rendererEnumerator nextObject])) {
777             [view setNeedsDisplayInRect:[renderer targetAnimationRect]];
778         }
779     }
780 }
781
782 - (BOOL)shouldAnimate
783 {
784     return [self numberOfImages] > 1 && ![self isAnimationFinished];
785 }
786
787 - (void)animate
788 {
789     if (frameTimer && [frameTimer isValid])
790         return;
791     frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self _frameDuration]
792                                                     target:self
793                                                   selector:@selector(_nextFrame:)
794                                                   userInfo:nil
795                                                    repeats:NO] retain];
796 }
797
798 -(void)_createPDFWithData:(NSData *)data
799 {
800     if (!_PDFDoc) {
801         _PDFDoc = [[WebPDFDocument alloc] initWithData:data];
802     }
803 }
804
805 - (CGPDFDocumentRef)_PDFDocumentRef
806 {
807     return [_PDFDoc documentRef];
808 }
809
810 - (void)_PDFDrawInContext:(CGContextRef)context
811 {
812     CGPDFDocumentRef document = [self _PDFDocumentRef];
813     if (document != NULL) {
814         CGRect       mediaBox = [_PDFDoc mediaBox];
815         
816         CGContextSaveGState(context);
817         // Rotate translate image into position according to doc properties.
818         [_PDFDoc adjustCTM:context];    
819
820         // Media box may have non-zero origin which we ignore. CGPDFDocumentShowPage pages start
821         // at 1, not 0.
822         CGContextDrawPDFDocument(context, CGRectMake(0, 0, mediaBox.size.width, mediaBox.size.height), document, 1);
823
824         CGContextRestoreGState(context);
825     }
826 }
827
828 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(CGCompositeOperation)op alpha:(float)alpha flipped:(BOOL)flipped context:(CGContextRef)context
829 {
830     float hScale, vScale;
831
832     CGContextSaveGState(context);
833
834     CGContextSetCompositeOperation (context, op);
835
836     // Scale and translate so the document is rendered in the correct location.
837     hScale = dstRect.size.width  / srcRect.size.width;
838     vScale = dstRect.size.height / srcRect.size.height;
839
840     CGContextTranslateCTM (context, dstRect.origin.x - srcRect.origin.x * hScale, dstRect.origin.y - srcRect.origin.y * vScale);
841     CGContextScaleCTM (context, hScale, vScale);
842
843     // Reverse if flipped image.
844     if (flipped) {
845         CGContextScaleCTM(context, 1, -1);
846         CGContextTranslateCTM (context, 0, -(dstRect.origin.y + dstRect.size.height));
847     }
848
849     // Clip to destination in case we are imaging part of the source only
850     CGContextClipToRect(context, CGRectIntegral(*(CGRect*)&srcRect));
851
852     // and draw
853     [self _PDFDrawInContext:context];
854
855     // done with our fancy transforms
856     CGContextRestoreGState(context);
857
858     return YES;
859 }
860
861 @end
862
863 #endif