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