ae62d0dab66574d701b9a318bb83b90bb3b7ddbf
[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 // 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     sizeAvailable = NO;
60     
61     return self;
62 }
63
64 - (void)setIsPDF:(BOOL)f
65 {
66     isPDF = f;
67 }
68
69 - (BOOL)isPDF
70 {
71     return isPDF;
72 }
73
74 - (void)_commonTermination
75 {
76     ASSERT (!frameTimer);
77     
78     [self _invalidateImages];
79     [self _invalidateImageProperties];
80         
81     if (fileProperties)
82         CFRelease (fileProperties);
83         
84     if (imageSource)
85         CFRelease (imageSource); 
86         
87     if (animatingRenderers)
88         CFRelease (animatingRenderers);
89
90     free (frameDurations);
91 }
92
93 - (void)dealloc
94 {
95     [_PDFDoc release];
96     [decodeLock release];
97
98     [self _commonTermination];
99
100     [super dealloc];
101 }
102
103 - (void)finalize
104 {
105     [self _commonTermination];
106     [super finalize];
107 }
108
109 - copyWithZone:(NSZone *)zone
110 {
111     WebImageData *copy;
112
113     copy = [[WebImageData alloc] init];
114     CFRetain (imageSource);
115     copy->imageSource = imageSource;
116     
117     return copy;
118 }
119
120
121 - (size_t)numberOfImages
122 {
123     if (imageSource)
124         return CGImageSourceGetCount(imageSource);
125     return 0;
126 }
127
128 - (size_t)currentFrame
129 {
130     return currentFrame;
131 }
132
133 - (void)_invalidateImages
134 {
135     if (images) {
136         size_t i;
137         for (i = 0; i < imagesSize; i++) {
138             if (images[i])
139                 CFRelease (images[i]);
140         }
141         free (images);
142         images = 0;
143         
144         isSolidColor = NO;
145         if( solidColor ) {
146             CFRelease(solidColor);
147             solidColor = NULL;
148         }
149     }
150 }
151
152 - (void)_invalidateImageProperties
153 {
154     size_t i;
155     for (i = 0; i < imagePropertiesSize; i++) {
156         if (imageProperties[i])
157             CFRelease (imageProperties[i]);
158     }
159     free (imageProperties);
160     imageProperties = 0;
161     imagePropertiesSize = 0;
162 }
163
164 - (CFDictionaryRef)_imageSourceOptions
165 {
166     static CFDictionaryRef imageSourceOptions;
167     if (!imageSourceOptions) {
168         const void * keys[2] = { kCGImageSourceShouldCache, kCGImageSourceShouldPreferRGB32 };
169         const void * values[2] = { kCFBooleanTrue, kCFBooleanTrue };
170         imageSourceOptions = CFDictionaryCreate (NULL, keys, values, 2, 
171                 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
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, [self _imageSourceOptions]);
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, [self _imageSourceOptions]);
236             if (imageProperties[i])
237                 CFRetain (imageProperties[i]);
238 #else
239             imageProperties[i] = CGImageSourceCopyPropertiesAtIndex (imageSource, i, [self _imageSourceOptions]);
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, [self _imageSourceOptions]);
251             if (imageProperties[index])
252                 CFRetain (imageProperties[index]);
253 #else
254             imageProperties[index] = CGImageSourceCopyPropertiesAtIndex (imageSource, index, [self _imageSourceOptions]);
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)_isSizeAvailable
373 {
374     CGImageSourceStatus imageSourceStatus = CGImageSourceGetStatus(imageSource);
375     
376     if (sizeAvailable)
377         return YES;
378         
379     // With ImageIO-55 the meta data for an image is updated progressively.  We don't want
380     // to indicate that the image is 'ready' for layout until we know the size.  So, we
381     // have to try getting the meta until we have a valid width and height property.
382     if (imageSourceStatus >= kCGImageStatusIncomplete) {
383         CFDictionaryRef image0Properties = CGImageSourceCopyPropertiesAtIndex (imageSource, 0, [self _imageSourceOptions]);
384         if  (image0Properties) {
385             CFNumberRef widthNumber = CFDictionaryGetValue (image0Properties, kCGImagePropertyPixelWidth);
386             CFNumberRef heightNumber = CFDictionaryGetValue (image0Properties, kCGImagePropertyPixelHeight);
387             sizeAvailable = widthNumber && heightNumber;
388             
389             if (imageProperties) {
390                 if (imageProperties[0])
391                     CFRelease(imageProperties[0]);
392             }
393             else {
394                 imagePropertiesSize = [self numberOfImages];
395                 imageProperties = (CFDictionaryRef *)calloc (imagePropertiesSize, sizeof(CFDictionaryRef));
396             }
397                 
398             imageProperties[0] = image0Properties;
399         }
400     }
401     
402     return sizeAvailable;
403 }
404
405 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)callback
406 {
407 #ifdef kImageBytesCutoff
408     // This is a hack to help with testing display of partially-loaded images.
409     // To enable it, define kImageBytesCutoff to be a size smaller than that of the image files
410     // being loaded. They'll never finish loading.
411     if( length > kImageBytesCutoff ) {
412         length = kImageBytesCutoff;
413         isComplete = NO;
414     }
415 #endif
416     
417     CFDataRef data = CFDataCreate (NULL, bytes, length);
418     
419     if (callback) {
420         [WebImageDecoder performDecodeWithImage:self data:data isComplete:isComplete callback:callback];
421     }
422     else {
423         if (isPDF) {
424             if (isComplete) {
425                 [self _createPDFWithData:(NSData *)data];
426             }
427         }
428         else {
429             [self _invalidateImages];
430             CGImageSourceUpdateData (imageSource, data, isComplete);
431         }
432     }
433     
434     CFRelease (data);
435
436     // Image properties will not be available until the first frame of the file
437     // reaches kCGImageStatusIncomplete.  New as of ImageIO-55, see 4031602. 
438     if (imageSource) {
439         return [self _isSizeAvailable];
440     }
441     
442     return YES;
443 }
444
445 - (void)_fillSolidColorInRect:(CGRect)rect compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext
446 {
447     /*NSLog(@"WebImageData %p: filling with color %p, in {%.0f,%.0f, %.0f x %.0f}",
448           self,solidColor,rect.origin.x,rect.origin.y,rect.size.width,rect.size.height);*/
449     if( solidColor ) {
450         CGContextSaveGState (aContext);
451         CGContextSetFillColorWithColor(aContext, solidColor);
452         CGContextSetCompositeOperation (aContext, op);
453         CGContextFillRect (aContext, rect);
454         CGContextRestoreGState (aContext);
455     }
456 }
457
458 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr adjustedSize:(CGSize)adjustedSize compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
459 {
460     if (isPDF) {
461         [self _PDFDrawFromRect:NSMakeRect(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height)
462                 toRect:NSMakeRect(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height)
463                 operation:op 
464                 alpha:1.0 
465                 flipped:YES
466                 context:aContext];
467     }
468     else {
469         [decodeLock lock];
470         
471         CGImageRef image = [self imageAtIndex:index];
472         
473         if (!image) {
474             [decodeLock unlock];
475             return;
476         }
477         
478         if( isSolidColor && index==0 ) {
479             [self _fillSolidColorInRect: ir compositeOperation: op context: aContext];
480
481         } else {
482             CGContextSaveGState (aContext);
483             
484             // Get the height (in adjusted, i.e. scaled, coords) of the portion of the image
485             // that is currently decoded.  This could be less that the actual height.
486             CGSize selfSize = [self size];                          // full image size, in pixels
487             float curHeight = CGImageGetHeight(image);              // height of loaded portion, in pixels
488             
489             if( curHeight < selfSize.height ) {
490                 adjustedSize.height *= curHeight / selfSize.height;
491
492                 // Is the amount of available bands less than what we need to draw?  If so,
493                 // we may have to clip 'fr' if it goes outside the available bounds.
494                 if( CGRectGetMaxY(fr) > adjustedSize.height ) {
495                     float frHeight = adjustedSize.height - fr.origin.y; // clip fr to available bounds
496                     if( frHeight <= 0 ) {
497                         [decodeLock unlock];
498                         return;                                             // clipped out entirely
499                     }
500                     ir.size.height *= (frHeight / fr.size.height);    // scale ir proportionally to fr
501                     fr.size.height = frHeight;
502                 }
503             }
504             
505             // Flip the coords.
506             CGContextSetCompositeOperation (aContext, op);
507             CGContextTranslateCTM (aContext, ir.origin.x, ir.origin.y);
508             CGContextScaleCTM (aContext, 1, -1);
509             CGContextTranslateCTM (aContext, 0, -ir.size.height);
510             
511             // Translated to origin, now draw at 0,0.
512             ir.origin.x = ir.origin.y = 0;
513             
514             // If we're drawing a sub portion of the image then create
515             // a image for the sub portion and draw that.
516             // Test using example site at http://www.meyerweb.com/eric/css/edge/complexspiral/demo.html
517             if (fr.size.width != adjustedSize.width || fr.size.height != adjustedSize.height) {
518                 // Convert ft to image pixel coords:
519                 float xscale = adjustedSize.width / selfSize.width;
520                 float yscale = adjustedSize.height / curHeight;     // yes, curHeight, not selfSize.height!
521                 fr.origin.x /= xscale;
522                 fr.origin.y /= yscale;
523                 fr.size.width /= xscale;
524                 fr.size.height /= yscale;
525                 
526                 image = CGImageCreateWithImageInRect (image, fr);
527                 if (image) {
528                     CGContextDrawImage (aContext, ir, image);
529                     CFRelease (image);
530                 }
531             }
532             // otherwise draw the whole image.
533             else { 
534                 CGContextDrawImage (aContext, ir, image);
535             }
536
537             CGContextRestoreGState (aContext);
538         }
539         
540         [decodeLock unlock];
541     }
542 }
543
544 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
545 {    
546     [self drawImageAtIndex:index inRect:ir fromRect:fr adjustedSize:[self size] compositeOperation:op context:aContext];
547 }
548
549 static void drawPattern (void * info, CGContextRef context)
550 {
551     WebImageData *data = (WebImageData *)info;
552     
553     CGImageRef image = [data imageAtIndex:[data currentFrame]];
554     float w = CGImageGetWidth(image);
555     float h = CGImageGetHeight(image);
556     CGContextDrawImage (context, CGRectMake(0, [data size].height-h, w, h), image);    
557 }
558
559 static const CGPatternCallbacks patternCallbacks = { 0, drawPattern, NULL };
560
561 - (void)tileInRect:(CGRect)rect fromPoint:(CGPoint)point context:(CGContextRef)aContext
562 {
563     ASSERT (aContext);
564
565     [decodeLock lock];
566     
567     size_t frame = [self currentFrame];
568     CGImageRef image = [self imageAtIndex:frame];
569     if (!image) {
570         [decodeLock unlock];
571         return;
572     }
573
574     if( frame == 0 && isSolidColor ) {
575         [self _fillSolidColorInRect: rect compositeOperation: kCGCompositeSover context: aContext];
576         
577     } else {
578         CGSize tileSize = [self size];
579         
580         // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
581         NSRect oneTileRect;
582         oneTileRect.origin.x = rect.origin.x + fmodf(fmodf(-point.x, tileSize.width) - tileSize.width, tileSize.width);
583         oneTileRect.origin.y = rect.origin.y + fmodf(fmodf(-point.y, tileSize.height) - tileSize.height, tileSize.height);
584         oneTileRect.size.height = tileSize.height;
585         oneTileRect.size.width = tileSize.width;
586
587         // If the single image draw covers the whole area, then just draw once.
588         if (NSContainsRect(oneTileRect, NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height))) {
589             CGRect fromRect;
590
591             fromRect.origin.x = rect.origin.x - oneTileRect.origin.x;
592             fromRect.origin.y = rect.origin.y - oneTileRect.origin.y;
593             fromRect.size = rect.size;
594
595             [decodeLock unlock];
596             
597             [self drawImageAtIndex:[self currentFrame] inRect:rect fromRect:fromRect compositeOperation:kCGCompositeSover context:aContext];
598
599             return;
600         }
601
602         // Compute the appropriate phase relative to the top level view in the window.
603         // Conveniently, the oneTileRect we computed above has the appropriate origin.
604         NSPoint originInWindow = [[NSView focusView] convertPoint:oneTileRect.origin toView:nil];
605
606         // WebCore may translate the focus, and thus need an extra phase correction
607         NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
608         originInWindow.x += extraPhase.x;
609         originInWindow.y += extraPhase.y;
610         CGSize phase = CGSizeMake(fmodf(originInWindow.x, tileSize.width), fmodf(originInWindow.y, tileSize.height));
611
612         // Possible optimization:  We may want to cache the CGPatternRef    
613         CGPatternRef pattern = CGPatternCreate(self, CGRectMake (0, 0, tileSize.width, tileSize.height), CGAffineTransformIdentity, tileSize.width, tileSize.height, 
614             kCGPatternTilingConstantSpacing, true, &patternCallbacks);
615         if (pattern) {
616             CGContextSaveGState (aContext);
617
618             CGContextSetPatternPhase(aContext, phase);
619
620             CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
621             CGContextSetFillColorSpace(aContext, patternSpace);
622             CGColorSpaceRelease(patternSpace);
623
624             float patternAlpha = 1;
625             CGContextSetFillPattern(aContext, pattern, &patternAlpha);
626
627             CGContextSetCompositeOperation (aContext, kCGCompositeSover);
628
629             CGContextFillRect (aContext, rect);
630
631             CGPatternRelease (pattern);
632
633             CGContextRestoreGState (aContext);
634         }
635         else {
636             ERROR ("unable to create pattern");
637         }
638     }
639     
640     [decodeLock unlock];
641 }
642
643 - (BOOL)isNull
644 {
645     if (imageSource)
646         return CGImageSourceGetStatus(imageSource) < kCGImageStatusReadingHeader;
647     return YES;
648 }
649
650 - (CGSize)size
651 {
652     float w = 0.f, h = 0.f;
653
654     if (isPDF) {
655         if (_PDFDoc) {
656             CGRect mediaBox = [_PDFDoc mediaBox];
657             return mediaBox.size;
658         }
659     }
660     else {
661         if (sizeAvailable && !haveSize) {
662             [decodeLock lock];
663             CFDictionaryRef properties = [self propertiesAtIndex:0];
664             if (properties) {
665                 CFNumberRef num = CFDictionaryGetValue (properties, kCGImagePropertyPixelWidth);
666                 if (num)
667                     CFNumberGetValue (num, kCFNumberFloat32Type, &w);
668                 num = CFDictionaryGetValue (properties, kCGImagePropertyPixelHeight);
669                 if (num)
670                     CFNumberGetValue (num, kCFNumberFloat32Type, &h);
671
672                 size.width = w;
673                 size.height = h;
674
675                 haveSize = YES;
676             }
677             [decodeLock unlock];
678         }
679     }
680     
681     return size;
682 }
683
684 - (float)_floatProperty:(CFStringRef)property type:(CFStringRef)type at:(size_t)i
685 {
686     [decodeLock lock];
687     
688     CFDictionaryRef properties = [self propertiesAtIndex:i];
689     if (!properties) {
690         [decodeLock unlock];
691         return 0.f;
692     }
693     
694     CFDictionaryRef typeProperties = CFDictionaryGetValue (properties, type);
695     if (!typeProperties) {
696         [decodeLock unlock];
697         return 0.f;
698     }
699     
700     CFNumberRef num = CFDictionaryGetValue (typeProperties, property);
701     if (!num) {
702         [decodeLock unlock];
703         return 0.f;
704     }
705     
706     float value = 0.f;
707     CFNumberGetValue (num, kCFNumberFloat32Type, &value);
708
709     [decodeLock unlock];
710     
711     return value;
712 }
713
714 - (float)_floatFileProperty:(CFStringRef)property type:(CFStringRef)type hasProperty:(BOOL *)hasProperty;
715 {
716     [decodeLock lock];
717     
718     *hasProperty = NO;
719     
720     CFDictionaryRef properties = [self fileProperties];
721     if (!properties) {
722         [decodeLock unlock];
723         return 0.f;
724     }
725     
726     if (type) {
727         properties = CFDictionaryGetValue (properties, type);
728         if (!properties) {
729             [decodeLock unlock];
730             return 0.f;
731         }
732     }
733     
734     CFNumberRef num = CFDictionaryGetValue (properties, property);
735     if (!num) {
736         [decodeLock unlock];
737         return 0.f;
738     }
739     
740     float value = 0.f;
741     CFNumberGetValue (num, kCFNumberFloat32Type, &value);
742
743     [decodeLock unlock];
744
745     *hasProperty = YES;
746     
747     return value;
748 }
749
750 #define MINIMUM_DURATION (.1)
751
752 - (float)_frameDurationAt:(size_t)i
753 {
754     float duration = [self _floatProperty:kCGImagePropertyGIFDelayTime type:kCGImagePropertyGIFDictionary at:i];
755     if (duration <= 0.01) {
756         /*
757             Many annoying ads specify a 0 duration to make an image flash
758             as quickly as possible.
759             
760             We follow mozilla's behavior and set the minimum duration to 
761             100 ms.  See 4051389 for more details.
762         */
763         duration = MINIMUM_DURATION;
764     }
765     return duration;
766 }
767
768 - (float)_frameDuration
769 {
770     size_t num = [self numberOfImages];
771     if (frameDurationsSize && num > frameDurationsSize) {
772         free (frameDurations);
773         frameDurations = 0;
774         frameDurationsSize = 0;
775     }
776     
777     if (!frameDurations) {
778         size_t i;
779
780         frameDurations = (float *)malloc (sizeof(float) * num);
781         for (i = 0; i < num; i++) {
782             frameDurations[i] = [self _frameDurationAt:i];
783         }
784         frameDurationsSize = num;
785     }
786     else if (frameDurations[currentFrame] == 0.f) {
787         frameDurations[currentFrame] = [self _frameDurationAt:currentFrame];
788     }
789
790     return frameDurations[currentFrame];
791 }
792
793 - (int)_repetitionCount
794 {
795     int count;
796     BOOL hasProperty;
797
798     // No property means loop once.
799     // A property with value 0 means loops forever.
800     count = [self _floatFileProperty:kCGImagePropertyGIFLoopCount type:kCGImagePropertyGIFDictionary hasProperty:&hasProperty];
801     if (!hasProperty)
802         count = -1;
803         
804     return count;
805 }
806
807 - (BOOL)isAnimationFinished
808 {
809     return animationFinished;
810 }
811
812 static NSMutableSet *activeAnimations;
813
814 + (void)stopAnimationsInView:(NSView *)aView
815 {
816     NSEnumerator *objectEnumerator = [activeAnimations objectEnumerator];
817     WebImageData *animation;
818     NSMutableSet *renderersToStop = nil;
819
820     // Determine all the renderers that are drawing animations in the view.
821     // A set of sets, one set of renderers for each image being animated
822     // in the view.  It is necessary to gather the all renderers to stop
823     // before actually stopping them because the process of stopping them
824     // will modify the active animations and animating renderer collections.
825     while ((animation = [objectEnumerator nextObject])) {
826         NSSet *renderersInView = (NSSet *)CFDictionaryGetValue (animation->animatingRenderers, aView);
827         if (renderersInView) {
828                         if (!renderersToStop)
829                                 renderersToStop = [[NSMutableSet alloc] init];
830             [renderersToStop unionSet:renderersInView];
831         }
832     }
833
834     // Now tell them all to stop drawing.
835     [renderersToStop makeObjectsPerformSelector:@selector(stopAnimation)];
836         [renderersToStop release];
837 }
838
839 - (void)addAnimatingRenderer:(WebImageRenderer *)r inView:(NSView *)view
840 {
841     if (!animatingRenderers)
842         animatingRenderers = CFDictionaryCreateMutable (NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
843     
844     NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
845     if (!renderers) {
846         renderers = [[NSMutableSet alloc] init];
847         CFDictionaryAddValue(animatingRenderers, view, renderers);
848         [renderers release];
849     }
850             
851     [renderers addObject:r];
852
853     if (!activeAnimations)
854         activeAnimations = [[NSMutableSet alloc] init];
855     
856     [activeAnimations addObject:self];
857 }
858
859 - (void)removeAnimatingRenderer:(WebImageRenderer *)r
860 {
861     NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
862     NSView *view;
863     while ((view = [viewEnumerator nextObject])) {
864         NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
865         [renderers removeObject:r];
866         if ([renderers count] == 0) {
867             CFDictionaryRemoveValue (animatingRenderers, view);
868         }
869     }
870     
871     if (animatingRenderers && CFDictionaryGetCount(animatingRenderers) == 0) {
872         [activeAnimations removeObject:self];
873         [self _stopAnimation];
874     }
875 }
876
877 - (void)resetAnimation
878 {
879     [self _stopAnimation];
880     currentFrame = 0;
881     repetitionsComplete = 0;
882     animationFinished = NO;
883 }
884
885 - (void)_stopAnimation
886 {
887     // This timer is used to animate all occurences of this image.  Don't invalidate
888     // the timer unless all renderers have stopped drawing.
889     [frameTimer invalidate];
890     [frameTimer release];
891     frameTimer = nil;
892 }
893
894 - (void)_nextFrame:(id)context
895 {
896     // Release the timer that just fired.
897     [frameTimer release];
898     frameTimer = nil;
899     
900     currentFrame++;
901     if (currentFrame >= [self numberOfImages]) {
902         repetitionsComplete += 1;
903         if ([self _repetitionCount] && repetitionsComplete >= [self _repetitionCount]) {
904             animationFinished = YES;
905             currentFrame--;
906             return;
907         }
908         currentFrame = 0;
909     }
910     
911     NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
912     NSView *view;
913     while ((view = [viewEnumerator nextObject])) {
914         NSMutableSet *renderers = [(NSMutableDictionary *)animatingRenderers objectForKey:view];
915         WebImageRenderer *renderer;
916         NSEnumerator *rendererEnumerator = [renderers objectEnumerator];
917         while ((renderer = [rendererEnumerator nextObject])) {
918             [view setNeedsDisplayInRect:[renderer targetAnimationRect]];
919         }
920     }
921 }
922
923 - (BOOL)shouldAnimate
924 {
925     return [self numberOfImages] > 1 && ![self isAnimationFinished];
926 }
927
928 - (void)animate
929 {
930     if (frameTimer && [frameTimer isValid])
931         return;
932     frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self _frameDuration]
933                                                     target:self
934                                                   selector:@selector(_nextFrame:)
935                                                   userInfo:nil
936                                                    repeats:NO] retain];
937 }
938
939 -(void)_createPDFWithData:(NSData *)data
940 {
941     if (!_PDFDoc) {
942         _PDFDoc = [[WebPDFDocument alloc] initWithData:data];
943     }
944 }
945
946 - (CGPDFDocumentRef)_PDFDocumentRef
947 {
948     return [_PDFDoc documentRef];
949 }
950
951 - (void)_PDFDrawInContext:(CGContextRef)context
952 {
953     CGPDFDocumentRef document = [self _PDFDocumentRef];
954     if (document != NULL) {
955         CGRect       mediaBox = [_PDFDoc mediaBox];
956         
957         CGContextSaveGState(context);
958         // Rotate translate image into position according to doc properties.
959         [_PDFDoc adjustCTM:context];    
960
961         // Media box may have non-zero origin which we ignore. CGPDFDocumentShowPage pages start
962         // at 1, not 0.
963         CGContextDrawPDFDocument(context, CGRectMake(0, 0, mediaBox.size.width, mediaBox.size.height), document, 1);
964
965         CGContextRestoreGState(context);
966     }
967 }
968
969 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(CGCompositeOperation)op alpha:(float)alpha flipped:(BOOL)flipped context:(CGContextRef)context
970 {
971     float hScale, vScale;
972
973     CGContextSaveGState(context);
974
975     CGContextSetCompositeOperation (context, op);
976
977     // Scale and translate so the document is rendered in the correct location.
978     hScale = dstRect.size.width  / srcRect.size.width;
979     vScale = dstRect.size.height / srcRect.size.height;
980
981     CGContextTranslateCTM (context, dstRect.origin.x - srcRect.origin.x * hScale, dstRect.origin.y - srcRect.origin.y * vScale);
982     CGContextScaleCTM (context, hScale, vScale);
983
984     // Reverse if flipped image.
985     if (flipped) {
986         CGContextScaleCTM(context, 1, -1);
987         CGContextTranslateCTM (context, 0, -dstRect.size.height);
988     }
989
990     // Clip to destination in case we are imaging part of the source only
991     CGContextClipToRect(context, CGRectIntegral(*(CGRect*)&srcRect));
992
993     // and draw
994     [self _PDFDrawInContext:context];
995
996     // done with our fancy transforms
997     CGContextRestoreGState(context);
998
999     return YES;
1000 }
1001
1002 @end
1003
1004 #endif