Report actual size (not partial size) but use partial size
[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/WebImageRenderer.h>
9 #import <WebKit/WebImageRendererFactory.h>
10
11 #import <WebCore/WebCoreImageRenderer.h>
12
13 #import <CoreGraphics/CGContextPrivate.h>
14 #import <CoreGraphics/CGContextGState.h>
15
16 #ifdef USE_CGIMAGEREF
17
18 static CFDictionaryRef imageSourceOptions;
19
20 // Forward declarations of internal methods.
21 @interface WebImageData (WebInternal)
22 - (void)_commonTermination;
23 - (void)_invalidateImages;
24 - (int)_repetitionCount;
25 - (float)_frameDuration;
26 - (void)_stopAnimation;
27 - (void)_nextFrame;
28 @end
29
30
31 @implementation WebImageData
32
33 - (void)_commonTermination
34 {
35     ASSERT (!frameTimer);
36     
37     [self _invalidateImages];
38     
39     if (imageSource)
40         CFRelease (imageSource); 
41         
42     if (animatingRenderers)
43         CFRelease (animatingRenderers);
44 }
45
46 - (void)dealloc
47 {
48     [self _commonTermination];
49
50     [super dealloc];
51 }
52
53 - (void)finalize
54 {
55     [self _commonTermination];
56     [super finalize];
57 }
58
59 - copyWithZone:(NSZone *)zone
60 {
61     WebImageData *copy;
62
63     copy = [[WebImageData alloc] init];
64     CFRetain (imageSource);
65     copy->imageSource = imageSource;
66     
67     return copy;
68 }
69
70
71 - (size_t)numberOfImages
72 {
73     if (imageSource)
74         return CGImageSourceGetCount(imageSource);
75     return 0;
76 }
77
78 - (size_t)currentFrame
79 {
80     return currentFrame;
81 }
82
83 - (void)_invalidateImages
84 {
85     if (images) {
86         size_t i, count = [self numberOfImages];
87         for (i = 0; i < count; i++) {
88             if (images[i])
89                 CFRelease (images[i]);
90         }
91         free (images);
92         images = 0;
93     }
94 }
95
96 - (CGImageRef)imageAtIndex:(size_t)index
97 {
98     if (imageSource) {
99         if (index > [self numberOfImages])
100             return 0;
101
102 #ifndef NDEBUG
103         CGImageSourceStatus containerStatus = CGImageSourceGetStatus(imageSource);
104 #endif
105         // Ignore status, just try to create the image!  Status reported from ImageIO 
106         // is bogus until the image is created.  See 3827851
107         //if (containerStatus < kCGImageStatusIncomplete)
108         //    return 0;
109
110 #ifndef NDEBUG
111         CGImageSourceStatus imageStatus = CGImageSourceGetStatusAtIndex(imageSource, index);
112 #endif
113         // Ignore status.  Status is invalid until we create the image (and eventually try to display it).
114         // See 3827851
115         //if (imageStatus < kCGImageStatusIncomplete)
116         //    return 0;
117
118         if (!images) {
119             images = (CGImageRef *)calloc ([self numberOfImages], sizeof(CGImageRef *));
120         }
121             
122         if (!images[index]) {
123             if (!imageSourceOptions) {
124                 CFStringRef keys[1] = { kCGImageSourceShouldCache };
125                 CFBooleanRef values[1] = { kCFBooleanTrue };
126                 imageSourceOptions = CFDictionaryCreate (NULL, (const void **)&keys, (const void **)&values, 1, 
127                             &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
128             }
129             images[index] = CGImageSourceCreateImageAtIndex (imageSource, index, imageSourceOptions);
130             if (images[index] == 0)
131                 ERROR ("unable to create image at index %d, containerStatus %d, image status %d", (int)index, containerStatus, imageStatus);
132         }
133         return images[index];
134     }
135     return 0;
136 }
137
138 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete
139 {
140     if (!imageSource)
141         imageSource = CGImageSourceCreateIncremental (imageSourceOptions);
142
143     [self _invalidateImages];
144
145     CFDataRef data = CFDataCreate (NULL, bytes, length);
146     CGImageSourceUpdateData (imageSource, data, isComplete);
147     CFRelease (data);
148     
149     // Always returns YES because we can't rely on status.  See 3827851
150     //CGImageSourceStatus status = CGImageSourceGetStatus(imageSource);
151     //
152     //return status >= kCGImageStatusReadingHeader;
153     return YES;
154 }
155
156 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
157 {
158     CGImageRef image = [self imageAtIndex:index];
159     
160     if (!image)
161         return;
162
163     CGContextSaveGState (aContext);
164
165     float w = CGImageGetWidth(image);
166     float h = CGImageGetHeight(image);
167
168     // Is the amount of available bands less than what we need to draw?  If so,
169     // clip.
170     if (h < fr.size.height) {
171         fr.size.height = h;
172         ir.size.height = h;
173     }
174     
175     // Flip the coords.
176     CGContextSetCompositeOperation (aContext, op);
177     CGContextTranslateCTM (aContext, ir.origin.x, ir.origin.y);
178     CGContextScaleCTM (aContext, 1, -1);
179     CGContextTranslateCTM (aContext, 0, -fr.size.height);
180     
181     // Translated to origin, now draw at 0,0.
182     ir.origin.x = ir.origin.y = 0;
183     
184     // If we're drawing a sub portion of the image then create
185     // a image for the sub portion and draw that.
186     if (fr.size.width != w || fr.size.height != h) {
187         image = CGImageCreateWithImageInRect (image, fr);
188         if (image) {
189             CGContextDrawImage (aContext, ir, image);
190             CFRelease (image);
191         }
192     }
193     // otherwise draw the whole image.
194     else { 
195         CGContextDrawImage (aContext, ir, image);
196     }
197
198     CGContextRestoreGState (aContext);
199 }
200
201 static void drawPattern (void * info, CGContextRef context)
202 {
203     CGImageRef image = (CGImageRef)info;
204     float w = CGImageGetWidth(image);
205     float h = CGImageGetHeight(image);
206     CGContextDrawImage (context, CGRectMake(0, 0, w, h), image);    
207 }
208
209 CGPatternCallbacks patternCallbacks = { 0, drawPattern, NULL };
210
211 - (void)tileInRect:(CGRect)rect fromPoint:(CGPoint)point context:(CGContextRef)aContext
212 {
213     ASSERT (aContext);
214     
215     CGImageRef image = [self imageAtIndex:[self currentFrame]];
216     if (!image) {
217         ERROR ("unable to find image");
218         return;
219     }
220
221     float w = CGImageGetWidth(image);
222     float h = CGImageGetHeight(image);
223
224     // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
225     NSRect oneTileRect;
226     oneTileRect.origin.x = rect.origin.x + fmodf(fmodf(-point.x, w) - w, w);
227     oneTileRect.origin.y = rect.origin.y + fmodf(fmodf(-point.y, h) - h, h);
228     oneTileRect.size.width = w;
229     oneTileRect.size.height = h;
230
231     // If the single image draw covers the whole area, then just draw once.
232     if (NSContainsRect(oneTileRect, NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height))) {
233         CGRect fromRect;
234
235         fromRect.origin.x = rect.origin.x - oneTileRect.origin.x;
236         fromRect.origin.y = rect.origin.y - oneTileRect.origin.y;
237         fromRect.size = rect.size;
238         
239         [self drawImageAtIndex:[self currentFrame] inRect:rect fromRect:fromRect compositeOperation:kCGCompositeSover context:aContext];
240         return;
241     }
242
243     // Compute the appropriate phase relative to the top level view in the window.
244     // Conveniently, the oneTileRect we computed above has the appropriate origin.
245     NSPoint originInWindow = [[NSView focusView] convertPoint:oneTileRect.origin toView:nil];
246
247     // WebCore may translate the focus, and thus need an extra phase correction
248     NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
249     originInWindow.x += extraPhase.x;
250     originInWindow.y += extraPhase.y;
251     CGSize phase = CGSizeMake(fmodf(originInWindow.x, w), fmodf(originInWindow.y, h));
252
253     // Possible optimization:  We may want to cache the CGPatternRef    
254     CGPatternRef pattern = CGPatternCreate(image, CGRectMake (0, 0, w, h), CGAffineTransformIdentity, w, h, 
255         kCGPatternTilingConstantSpacing, true, &patternCallbacks);
256     if (pattern) {
257         CGContextSaveGState (aContext);
258
259         CGContextSetPatternPhase(aContext, phase);
260
261         CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
262         CGContextSetFillColorSpace(aContext, patternSpace);
263         CGColorSpaceRelease(patternSpace);
264
265         float patternAlpha = 1;
266         CGContextSetFillPattern(aContext, pattern, &patternAlpha);
267
268         CGContextSetCompositeOperation (aContext, kCGCompositeSover);
269
270         CGContextFillRect (aContext, rect);
271
272         CGPatternRelease (pattern);
273
274         CGContextRestoreGState (aContext);
275     }
276     else {
277         ERROR ("unable to create pattern");
278     }
279 }
280
281 - (BOOL)isNull
282 {
283     if (imageSource)
284         return CGImageSourceGetStatus(imageSource) < kCGImageStatusReadingHeader;
285     return YES;
286 }
287
288 - (CGSize)size
289 {
290     float w = 0.f, h = 0.f;
291
292     if (!haveSize) {
293         CFDictionaryRef properties = CGImageSourceGetPropertiesAtIndex (imageSource, 0, 0);
294         if (properties) {
295             CFNumberRef num = CFDictionaryGetValue (properties, kCGImagePropertyPixelWidth);
296             if (num)
297                 CFNumberGetValue (num, kCFNumberFloat32Type, &w);
298             num = CFDictionaryGetValue (properties, kCGImagePropertyPixelHeight);
299             if (num)
300                 CFNumberGetValue (num, kCFNumberFloat32Type, &h);
301
302             size.width = w;
303             size.height = h;
304             
305             haveSize = YES;
306         }
307     }
308     
309     return size;
310 }
311
312 #define MINIMUM_DURATION (1.0/30.0)
313
314 - (float)_frameDuration
315 {
316     if (!frameDurations) {
317         frameDurations = (float *)malloc (sizeof(float) * [self numberOfImages]);
318         
319         size_t i;
320         for (i = 0; i < [self numberOfImages]; i++) {
321             CFDictionaryRef properties = CGImageSourceGetPropertiesAtIndex (imageSource, i, 0);
322             if (!properties) {
323                 frameDurations[i] = 0.f;
324                 continue;
325             }
326                 
327             // FIXME:  Use constant instead of {GIF}
328             CFDictionaryRef GIFProperties = CFDictionaryGetValue (properties, @"{GIF}");
329             if (!GIFProperties) {
330                 frameDurations[i] = 0.f;
331                 continue;
332             }
333             
334             // FIXME:  Use constant instead of DelayTime
335             CFNumberRef num = CFDictionaryGetValue (GIFProperties, @"DelayTime");
336             if (!num) {
337                 frameDurations[i] = 0.f;
338                 continue;
339             }
340             
341             float duration = 0.f;
342             CFNumberGetValue (num, kCFNumberFloat32Type, &duration);
343             if (duration < MINIMUM_DURATION) {
344                 /*
345                     Many annoying ads specify a 0 duration to make an image flash
346                     as quickly as possible.  However a zero duration is faster than
347                     the refresh rate.  We need to pick a minimum duration.
348                     
349                     Browsers handle the minimum time case differently.  IE seems to use something
350                     close to 1/30th of a second.  Konqueror uses 0.  The ImageMagick library
351                     uses 1/100th.  The units in the GIF specification are 1/100th of second.
352                     We will use 1/30th of second as the minimum time.
353                 */
354                 duration = MINIMUM_DURATION;
355             }
356             frameDurations[i] = duration;
357         }
358     }
359     
360     return frameDurations[currentFrame];
361 }
362
363 - (int)_repetitionCount
364 {
365     // FIXME:  Need to get this from CG folks.
366     return 0;
367 }
368
369 - (BOOL)isAnimationFinished
370 {
371     return animationFinished;
372 }
373
374 static NSMutableSet *activeAnimations;
375
376 + (void)stopAnimationsInView:(NSView *)aView
377 {
378     NSEnumerator *objectEnumerator = [activeAnimations objectEnumerator];
379     WebImageData *animation;
380     NSMutableSet *renderersToStop = nil;
381
382     // Determine all the renderers that are drawing animations in the view.
383     // A set of sets, one set of renderers for each image being animated
384     // in the view.  It is necessary to gather the all renderers to stop
385     // before actually stopping them because the process of stopping them
386     // will modify the active animations and animating renderer collections.
387     NSSet *renderersInView;
388     while ((animation = [objectEnumerator nextObject])) {
389         renderersInView = (NSSet *)CFDictionaryGetValue (animation->animatingRenderers, aView);
390         if (renderersInView) {
391             if (!renderersToStop)
392                 renderersToStop = [[NSMutableSet alloc] init];
393             [renderersToStop addObject: renderersInView];
394         }
395     }
396
397     // Now tell them all to stop drawing.
398     if (renderersToStop) {
399         objectEnumerator = [renderersToStop objectEnumerator];
400         while ((renderersInView = [objectEnumerator nextObject])) {
401             [renderersInView makeObjectsPerformSelector:@selector(stopAnimation)];
402         }
403         
404         [renderersToStop release];
405     }
406 }
407
408 - (void)addAnimatingRenderer:(WebImageRenderer *)r inView:(NSView *)view
409 {
410     if (!animatingRenderers)
411         animatingRenderers = CFDictionaryCreateMutable (NULL, 0, NULL, NULL);
412     
413     NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
414     if (!renderers) {
415         renderers = [[NSMutableSet alloc] init];
416         CFDictionaryAddValue(animatingRenderers, view, renderers);
417     }
418             
419     [renderers addObject:r];
420
421     if (!activeAnimations)
422         activeAnimations = [[NSMutableSet alloc] init];
423     
424     [activeAnimations addObject:self];
425 }
426
427 - (void)removeAnimatingRenderer:(WebImageRenderer *)r
428 {
429     NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
430     NSView *view;
431     while ((view = [viewEnumerator nextObject])) {
432         NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
433         [renderers removeObject:r];
434         if ([renderers count] == 0) {
435             CFDictionaryRemoveValue (animatingRenderers, view);
436         }
437     }
438     
439     if (animatingRenderers && CFDictionaryGetCount(animatingRenderers) == 0) {
440         [activeAnimations removeObject:self];
441         [self _stopAnimation];
442     }
443 }
444
445 - (void)_stopAnimation
446 {
447     // This timer is used to animate all occurences of this image.  Don't invalidate
448     // the timer unless all renderers have stopped drawing.
449     [frameTimer invalidate];
450     [frameTimer release];
451     frameTimer = nil;
452 }
453
454 - (void)_nextFrame:(id)context
455 {
456     // Release the timer that just fired.
457     [frameTimer release];
458     frameTimer = nil;
459     
460     currentFrame++;
461     if (currentFrame >= [self numberOfImages]) {
462         repetitionsComplete += 1;
463         if ([self _repetitionCount] && repetitionsComplete >= [self _repetitionCount]) {
464             animationFinished = YES;
465             return;
466         }
467         currentFrame = 0;
468     }
469     
470     NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
471     NSView *view;
472     while ((view = [viewEnumerator nextObject])) {
473         NSMutableSet *renderers = [(NSMutableDictionary *)animatingRenderers objectForKey:view];
474         WebImageRenderer *renderer;
475         NSEnumerator *rendererEnumerator = [renderers objectEnumerator];
476         while ((renderer = [rendererEnumerator nextObject])) {
477             [view setNeedsDisplayInRect:[renderer targetAnimationRect]];
478         }
479     }
480 }
481
482 - (void)animate
483 {
484     if (frameTimer && [frameTimer isValid])
485         return;
486     frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self _frameDuration]
487                                                     target:self
488                                                   selector:@selector(_nextFrame:)
489                                                   userInfo:nil
490                                                    repeats:NO] retain];
491 }
492
493 @end
494
495 #endif