3 Copyright (c) 2004 Apple, Inc. All rights reserved.
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>
13 #import <WebCore/WebCoreImageRenderer.h>
15 #import <CoreGraphics/CGContextPrivate.h>
16 #import <CoreGraphics/CGContextGState.h>
17 #import <CoreGraphics/CGColorSpacePrivate.h>
21 static CFDictionaryRef imageSourceOptions;
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;
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;
40 @implementation WebImageData
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
47 //[WebImageRendererFactory setShouldUseThreadedDecoding:(WebNumberOfCPUs() >= 2 ? YES : NO)];
48 [WebImageRendererFactory setShouldUseThreadedDecoding:NO];
55 if ([WebImageRendererFactory shouldUseThreadedDecoding])
56 decodeLock = [[NSLock alloc] init];
58 imageSource = CGImageSourceCreateIncremental ([self _imageSourceOptions]);
63 - (void)setIsPDF:(BOOL)f
73 - (void)_commonTermination
77 [self _invalidateImages];
78 [self _invalidateImageProperties];
81 CFRelease (fileProperties);
84 CFRelease (imageSource);
86 if (animatingRenderers)
87 CFRelease (animatingRenderers);
89 free (frameDurations);
97 [self _commonTermination];
104 [self _commonTermination];
108 - copyWithZone:(NSZone *)zone
112 copy = [[WebImageData alloc] init];
113 CFRetain (imageSource);
114 copy->imageSource = imageSource;
120 - (size_t)numberOfImages
123 return CGImageSourceGetCount(imageSource);
127 - (size_t)currentFrame
132 - (void)_invalidateImages
136 for (i = 0; i < imagesSize; i++) {
138 CFRelease (images[i]);
145 - (void)_invalidateImageProperties
148 for (i = 0; i < imagePropertiesSize; i++) {
149 if (imageProperties[i])
150 CFRelease (imageProperties[i]);
152 free (imageProperties);
154 imagePropertiesSize = 0;
157 - (CFDictionaryRef)_imageSourceOptions
159 if (!imageSourceOptions) {
160 CFStringRef keys[1] = { kCGImageSourceShouldCache };
161 CFBooleanRef values[1] = { kCFBooleanTrue };
162 imageSourceOptions = CFDictionaryCreate (NULL, (const void **)&keys, (const void **)&values, 1,
163 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
165 return imageSourceOptions;
168 - (CGImageRef)_noColorCorrectionImage:(CGImageRef)image withProperties:(CFDictionaryRef)props;
170 CGColorSpaceRef uncorrectedColorSpace = 0;
171 CGImageRef noColorCorrectionImage = 0;
173 if (!CFDictionaryGetValue (props, kCGImagePropertyProfileName)) {
174 CFStringRef colorModel = CFDictionaryGetValue (props, kCGImagePropertyColorModel);
177 if (CFStringCompare (colorModel, (CFStringRef)@"RGB", 0) == kCFCompareEqualTo)
178 uncorrectedColorSpace = CGColorSpaceCreateDisplayRGB();
179 else if (CFStringCompare (colorModel, (CFStringRef)@"Gray", 0) == kCFCompareEqualTo)
180 uncorrectedColorSpace = CGColorSpaceCreateDisplayGray();
183 if (uncorrectedColorSpace) {
184 noColorCorrectionImage = CGImageCreateCopyWithColorSpace (image, uncorrectedColorSpace);
185 CFRelease (uncorrectedColorSpace);
188 return noColorCorrectionImage;
191 - (CGImageRef)imageAtIndex:(size_t)index
193 if (index >= [self numberOfImages])
196 if (!images || images[index] == 0){
197 [self _cacheImages:index allImages:NO];
200 return images[index];
203 - (CFDictionaryRef)fileProperties
205 if (!fileProperties) {
206 fileProperties = CGImageSourceCopyProperties (imageSource, 0);
209 return fileProperties;
212 - (CFDictionaryRef)propertiesAtIndex:(size_t)index
214 size_t num = [self numberOfImages];
216 // Number of images changed!
217 if (imagePropertiesSize && num > imagePropertiesSize) {
219 [self _invalidateImageProperties];
222 if (imageProperties == 0 && num) {
223 imageProperties = (CFDictionaryRef *)malloc (num * sizeof(CFDictionaryRef));
225 for (i = 0; i < num; i++) {
226 #if USE_DEPRECATED_IMAGESOURCE_API
227 imageProperties[i] = CGImageSourceGetPropertiesAtIndex (imageSource, i, 0);
228 if (imageProperties[i])
229 CFRetain (imageProperties[i]);
231 imageProperties[i] = CGImageSourceCopyPropertiesAtIndex (imageSource, i, 0);
234 imagePropertiesSize = num;
238 // If image properties are nil, try to get them again. May have attempted to
239 // get them before enough data was available in the header.
240 if (imageProperties[index] == 0) {
241 #if USE_DEPRECATED_IMAGESOURCE_API
242 imageProperties[index] = CGImageSourceGetPropertiesAtIndex (imageSource, index, 0);
243 if (imageProperties[index])
244 CFRetain (imageProperties[index]);
246 imageProperties[index] = CGImageSourceCopyPropertiesAtIndex (imageSource, index, 0);
250 return imageProperties[index];
256 - (void)_cacheImages:(size_t)optionalIndex allImages:(BOOL)allImages
260 imagesSize = [self numberOfImages];
268 from = optionalIndex;
269 to = optionalIndex+1;
271 for (i = from; i < to; i++) {
273 images = (CGImageRef *)calloc (imagesSize, sizeof(CGImageRef *));
276 images[i] = CGImageSourceCreateImageAtIndex (imageSource, i, [self _imageSourceOptions]);
280 // Called from decoder thread.
281 - (void)decodeData:(CFDataRef)data isComplete:(BOOL)isComplete callback:(id)callback
287 [self _createPDFWithData:(NSData *)data];
291 // The work of decoding is actually triggered by image creation.
292 CGImageSourceUpdateData (imageSource, data, isComplete);
293 [self _invalidateImages];
294 [self _cacheImages:0 allImages:YES];
300 if (isComplete && callback) {
301 if ([self _PDFDocumentRef]) {
302 [WebImageDecoder decodeComplete:callback status:kCGImageStatusComplete];
305 [WebImageDecoder decodeComplete:callback status:kCGImageStatusInvalidData];
310 // Use status from first image to trigger appropriate notification back to WebCore
313 CGImageSourceStatus imageStatus = CGImageSourceGetStatusAtIndex(imageSource, 0);
315 // Lie about status. If we have all the data, go ahead and say we're complete
316 // as long we are have at least some valid bands (i.e. >= kCGImageStatusIncomplete).
317 // We have to lie because CG incorrectly reports the status.
318 if (isComplete && imageStatus >= kCGImageStatusIncomplete)
319 imageStatus = kCGImageStatusComplete;
321 // Only send bad status if we've read the whole image.
322 if (isComplete || (!isComplete && imageStatus >= kCGImageStatusIncomplete))
323 [WebImageDecoder decodeComplete:callback status:imageStatus];
328 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)callback
330 CFDataRef data = CFDataCreate (NULL, bytes, length);
333 [WebImageDecoder performDecodeWithImage:self data:data isComplete:isComplete callback:callback];
338 [self _createPDFWithData:(NSData *)data];
342 [self _invalidateImages];
343 CGImageSourceUpdateData (imageSource, data, isComplete);
352 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr adjustedSize:(CGSize)adjustedSize compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
355 [self _PDFDrawFromRect:NSMakeRect(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height)
356 toRect:NSMakeRect(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height)
365 CGImageRef image = [self imageAtIndex:index];
372 CGContextSaveGState (aContext);
374 // Get the height of the portion of the image that is currently decoded. This
375 // could be less that the actual height.
376 float h = CGImageGetHeight(image);
378 // Is the amount of available bands less than what we need to draw? If so,
381 CGSize actualSize = [self size];
382 if (h != actualSize.height) {
383 float proportionLoaded = h/actualSize.height;
384 fr.size.height = fr.size.height * proportionLoaded;
385 ir.size.height = ir.size.height * proportionLoaded;
390 CGContextSetCompositeOperation (aContext, op);
391 CGContextTranslateCTM (aContext, ir.origin.x, ir.origin.y);
392 CGContextScaleCTM (aContext, 1, -1);
393 CGContextTranslateCTM (aContext, 0, -ir.size.height);
395 // Translated to origin, now draw at 0,0.
396 ir.origin.x = ir.origin.y = 0;
398 // If we're drawing a sub portion of the image then create
399 // a image for the sub portion and draw that.
400 // Test using example site at http://www.meyerweb.com/eric/css/edge/complexspiral/demo.html
401 if (clipped == NO && (fr.size.width != adjustedSize.width || fr.size.height != adjustedSize.height)) {
402 image = CGImageCreateWithImageInRect (image, fr);
404 CGContextDrawImage (aContext, ir, image);
408 // otherwise draw the whole image.
410 CGContextDrawImage (aContext, ir, image);
413 CGContextRestoreGState (aContext);
419 - (void)drawImageAtIndex:(size_t)index inRect:(CGRect)ir fromRect:(CGRect)fr compositeOperation:(CGCompositeOperation)op context:(CGContextRef)aContext;
421 [self drawImageAtIndex:index inRect:ir fromRect:fr adjustedSize:[self size] compositeOperation:op context:aContext];
424 static void drawPattern (void * info, CGContextRef context)
426 WebImageData *data = (WebImageData *)info;
428 CGImageRef image = (CGImageRef)[data imageAtIndex:[data currentFrame]];
429 float w = CGImageGetWidth(image);
430 float h = CGImageGetHeight(image);
431 CGContextDrawImage (context, CGRectMake(0, 0, w, h), image);
434 CGPatternCallbacks patternCallbacks = { 0, drawPattern, NULL };
436 - (void)tileInRect:(CGRect)rect fromPoint:(CGPoint)point context:(CGContextRef)aContext
442 CGImageRef image = [self imageAtIndex:[self currentFrame]];
444 ERROR ("unable to find image");
449 float w = CGImageGetWidth(image);
450 float h = CGImageGetHeight(image);
452 // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
454 oneTileRect.origin.x = rect.origin.x + fmodf(fmodf(-point.x, w) - w, w);
455 oneTileRect.origin.y = rect.origin.y + fmodf(fmodf(-point.y, h) - h, h);
456 oneTileRect.size.width = w;
457 oneTileRect.size.height = h;
459 // If the single image draw covers the whole area, then just draw once.
460 if (NSContainsRect(oneTileRect, NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height))) {
463 fromRect.origin.x = rect.origin.x - oneTileRect.origin.x;
464 fromRect.origin.y = rect.origin.y - oneTileRect.origin.y;
465 fromRect.size = rect.size;
469 [self drawImageAtIndex:[self currentFrame] inRect:rect fromRect:fromRect compositeOperation:kCGCompositeSover context:aContext];
474 // Compute the appropriate phase relative to the top level view in the window.
475 // Conveniently, the oneTileRect we computed above has the appropriate origin.
476 NSPoint originInWindow = [[NSView focusView] convertPoint:oneTileRect.origin toView:nil];
478 // WebCore may translate the focus, and thus need an extra phase correction
479 NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
480 originInWindow.x += extraPhase.x;
481 originInWindow.y += extraPhase.y;
482 CGSize phase = CGSizeMake(fmodf(originInWindow.x, w), fmodf(originInWindow.y, h));
484 // Possible optimization: We may want to cache the CGPatternRef
485 CGPatternRef pattern = CGPatternCreate(self, CGRectMake (0, 0, w, h), CGAffineTransformIdentity, w, h,
486 kCGPatternTilingConstantSpacing, true, &patternCallbacks);
488 CGContextSaveGState (aContext);
490 CGContextSetPatternPhase(aContext, phase);
492 CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
493 CGContextSetFillColorSpace(aContext, patternSpace);
494 CGColorSpaceRelease(patternSpace);
496 float patternAlpha = 1;
497 CGContextSetFillPattern(aContext, pattern, &patternAlpha);
499 CGContextSetCompositeOperation (aContext, kCGCompositeSover);
501 CGContextFillRect (aContext, rect);
503 CGPatternRelease (pattern);
505 CGContextRestoreGState (aContext);
508 ERROR ("unable to create pattern");
517 return CGImageSourceGetStatus(imageSource) < kCGImageStatusReadingHeader;
523 float w = 0.f, h = 0.f;
527 CGRect mediaBox = [_PDFDoc mediaBox];
528 return mediaBox.size;
534 CFDictionaryRef properties = [self propertiesAtIndex:0];
536 CFNumberRef num = CFDictionaryGetValue (properties, kCGImagePropertyPixelWidth);
538 CFNumberGetValue (num, kCFNumberFloat32Type, &w);
539 num = CFDictionaryGetValue (properties, kCGImagePropertyPixelHeight);
541 CFNumberGetValue (num, kCFNumberFloat32Type, &h);
555 - (float)_floatProperty:(CFStringRef)property type:(CFStringRef)type at:(size_t)i
559 CFDictionaryRef properties = [self propertiesAtIndex:i];
565 CFDictionaryRef typeProperties = CFDictionaryGetValue (properties, type);
566 if (!typeProperties) {
571 CFNumberRef num = CFDictionaryGetValue (typeProperties, property);
578 CFNumberGetValue (num, kCFNumberFloat32Type, &value);
585 - (float)_floatFileProperty:(CFStringRef)property type:(CFStringRef)type
589 CFDictionaryRef properties = [self fileProperties];
596 properties = CFDictionaryGetValue (properties, type);
603 CFNumberRef num = CFDictionaryGetValue (properties, property);
610 CFNumberGetValue (num, kCFNumberFloat32Type, &value);
617 #define MINIMUM_DURATION (1.0/30.0)
619 - (float)_frameDurationAt:(size_t)i
621 float duration = [self _floatProperty:kCGImagePropertyGIFDelayTime type:kCGImagePropertyGIFDictionary at:i];
622 if (duration < MINIMUM_DURATION) {
624 Many annoying ads specify a 0 duration to make an image flash
625 as quickly as possible. However a zero duration is faster than
626 the refresh rate. We need to pick a minimum duration.
628 Browsers handle the minimum time case differently. IE seems to use something
629 close to 1/30th of a second. Konqueror uses 0. The ImageMagick library
630 uses 1/100th. The units in the GIF specification are 1/100th of second.
631 We will use 1/30th of second as the minimum time.
633 duration = MINIMUM_DURATION;
638 - (float)_frameDuration
640 size_t num = [self numberOfImages];
641 if (frameDurationsSize && num > frameDurationsSize) {
642 free (frameDurations);
644 frameDurationsSize = 0;
647 if (!frameDurations) {
650 frameDurations = (float *)malloc (sizeof(float) * num);
651 for (i = 0; i < num; i++) {
652 frameDurations[i] = [self _frameDurationAt:i];
654 frameDurationsSize = num;
656 else if (frameDurations[currentFrame] == 0.f) {
657 frameDurations[currentFrame] = [self _frameDurationAt:currentFrame];
660 return frameDurations[currentFrame];
663 - (int)_repetitionCount
665 return [self _floatFileProperty:kCGImagePropertyGIFLoopCount type:kCGImagePropertyGIFDictionary];
668 - (BOOL)isAnimationFinished
670 return animationFinished;
673 static NSMutableSet *activeAnimations;
675 + (void)stopAnimationsInView:(NSView *)aView
677 NSEnumerator *objectEnumerator = [activeAnimations objectEnumerator];
678 WebImageData *animation;
679 NSMutableSet *renderersToStop = nil;
681 // Determine all the renderers that are drawing animations in the view.
682 // A set of sets, one set of renderers for each image being animated
683 // in the view. It is necessary to gather the all renderers to stop
684 // before actually stopping them because the process of stopping them
685 // will modify the active animations and animating renderer collections.
686 NSSet *renderersInView;
687 while ((animation = [objectEnumerator nextObject])) {
688 renderersInView = (NSSet *)CFDictionaryGetValue (animation->animatingRenderers, aView);
689 if (renderersInView) {
690 if (!renderersToStop)
691 renderersToStop = [[NSMutableSet alloc] init];
692 [renderersToStop addObject: renderersInView];
696 // Now tell them all to stop drawing.
697 if (renderersToStop) {
698 objectEnumerator = [renderersToStop objectEnumerator];
699 while ((renderersInView = [objectEnumerator nextObject])) {
700 [renderersInView makeObjectsPerformSelector:@selector(stopAnimation)];
703 [renderersToStop release];
707 - (void)addAnimatingRenderer:(WebImageRenderer *)r inView:(NSView *)view
709 if (!animatingRenderers)
710 animatingRenderers = CFDictionaryCreateMutable (NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
712 NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
714 renderers = [[NSMutableSet alloc] init];
715 CFDictionaryAddValue(animatingRenderers, view, renderers);
719 [renderers addObject:r];
721 if (!activeAnimations)
722 activeAnimations = [[NSMutableSet alloc] init];
724 [activeAnimations addObject:self];
727 - (void)removeAnimatingRenderer:(WebImageRenderer *)r
729 NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
731 while ((view = [viewEnumerator nextObject])) {
732 NSMutableSet *renderers = (NSMutableSet *)CFDictionaryGetValue (animatingRenderers, view);
733 [renderers removeObject:r];
734 if ([renderers count] == 0) {
735 CFDictionaryRemoveValue (animatingRenderers, view);
739 if (animatingRenderers && CFDictionaryGetCount(animatingRenderers) == 0) {
740 [activeAnimations removeObject:self];
741 [self _stopAnimation];
745 - (void)_stopAnimation
747 // This timer is used to animate all occurences of this image. Don't invalidate
748 // the timer unless all renderers have stopped drawing.
749 [frameTimer invalidate];
750 [frameTimer release];
754 - (void)_nextFrame:(id)context
756 // Release the timer that just fired.
757 [frameTimer release];
761 if (currentFrame >= [self numberOfImages]) {
762 repetitionsComplete += 1;
763 if ([self _repetitionCount] && repetitionsComplete >= [self _repetitionCount]) {
764 animationFinished = YES;
770 NSEnumerator *viewEnumerator = [(NSMutableDictionary *)animatingRenderers keyEnumerator];
772 while ((view = [viewEnumerator nextObject])) {
773 NSMutableSet *renderers = [(NSMutableDictionary *)animatingRenderers objectForKey:view];
774 WebImageRenderer *renderer;
775 NSEnumerator *rendererEnumerator = [renderers objectEnumerator];
776 while ((renderer = [rendererEnumerator nextObject])) {
777 [view setNeedsDisplayInRect:[renderer targetAnimationRect]];
782 - (BOOL)shouldAnimate
784 return [self numberOfImages] > 1 && ![self isAnimationFinished];
789 if (frameTimer && [frameTimer isValid])
791 frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self _frameDuration]
793 selector:@selector(_nextFrame:)
798 -(void)_createPDFWithData:(NSData *)data
801 _PDFDoc = [[WebPDFDocument alloc] initWithData:data];
805 - (CGPDFDocumentRef)_PDFDocumentRef
807 return [_PDFDoc documentRef];
810 - (void)_PDFDrawInContext:(CGContextRef)context
812 CGPDFDocumentRef document = [self _PDFDocumentRef];
813 if (document != NULL) {
814 CGRect mediaBox = [_PDFDoc mediaBox];
816 CGContextSaveGState(context);
817 // Rotate translate image into position according to doc properties.
818 [_PDFDoc adjustCTM:context];
820 // Media box may have non-zero origin which we ignore. CGPDFDocumentShowPage pages start
822 CGContextDrawPDFDocument(context, CGRectMake(0, 0, mediaBox.size.width, mediaBox.size.height), document, 1);
824 CGContextRestoreGState(context);
828 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(CGCompositeOperation)op alpha:(float)alpha flipped:(BOOL)flipped context:(CGContextRef)context
830 float hScale, vScale;
832 CGContextSaveGState(context);
834 CGContextSetCompositeOperation (context, op);
836 // Scale and translate so the document is rendered in the correct location.
837 hScale = dstRect.size.width / srcRect.size.width;
838 vScale = dstRect.size.height / srcRect.size.height;
840 CGContextTranslateCTM (context, dstRect.origin.x - srcRect.origin.x * hScale, dstRect.origin.y - srcRect.origin.y * vScale);
841 CGContextScaleCTM (context, hScale, vScale);
843 // Reverse if flipped image.
845 CGContextScaleCTM(context, 1, -1);
846 CGContextTranslateCTM (context, 0, -(dstRect.origin.y + dstRect.size.height));
849 // Clip to destination in case we are imaging part of the source only
850 CGContextClipToRect(context, CGRectIntegral(*(CGRect*)&srcRect));
853 [self _PDFDrawInContext:context];
855 // done with our fancy transforms
856 CGContextRestoreGState(context);