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