Reviewed by John.
[WebKit-https.git] / WebKit / WebCoreSupport.subproj / WebImageRenderer.m
1 /*      
2         WebImageRenderer.m
3         Copyright (c) 2002, 2003, Apple, Inc. All rights reserved.
4 */
5 #import <WebKit/WebImageRenderer.h>
6
7 #import <WebKit/WebAssertions.h>
8 #import <WebKit/WebImageRendererFactory.h>
9 #import <WebKit/WebGraphicsBridge.h>
10 #import <WebKit/WebNSObjectExtras.h>
11
12 #import <WebCore/WebCoreImageRenderer.h>
13
14 #import <CoreGraphics/CGContextPrivate.h>
15 #import <CoreGraphics/CGContextGState.h>
16 #import <CoreGraphics/CGColorSpacePrivate.h>
17
18
19 #ifdef USE_CGIMAGEREF
20
21 #import <WebKit/WebImageData.h>
22
23 // Forward declarations of internal methods.
24 @interface WebImageRenderer (WebInternal)
25 - (void)_startOrContinueAnimationIfNecessary;
26 @end
27
28 @implementation WebImageRenderer
29
30 - (id)initWithMIMEType:(NSString *)MIME
31 {
32     self = [super init];
33     if (self != nil) {
34         MIMEType = [MIME copy];
35     }
36     return self;
37 }
38
39 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
40 {
41     self = [super init];
42     if (self != nil) {
43         MIMEType = [MIME copy];
44         imageData = [[WebImageData alloc] init];
45         [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES];
46     }
47     return self;
48 }
49
50 - (id)initWithContentsOfFile:(NSString *)filename
51 {
52     self = [super init];
53
54     NSBundle *bundle = [NSBundle bundleForClass:[self class]];
55     NSString *imagePath = [bundle pathForResource:filename ofType:@"tiff"];
56
57     imageData = [[WebImageData alloc] init];
58     NSData *data = [NSData dataWithContentsOfFile:imagePath];
59     [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES];
60         
61
62     return self;
63 }
64
65 - (void)dealloc
66 {
67     [MIMEType release];
68     [imageData release];
69     [super dealloc];
70 }
71
72 - copyWithZone:(NSZone *)zone
73 {
74     WebImageRenderer *copy;
75
76     copy = [[WebImageRenderer alloc] init];
77     copy->MIMEType = [MIMEType copy];
78     copy->adjustedSize = adjustedSize;
79     copy->isSizeAdjusted = isSizeAdjusted;
80     copy->imageData = [imageData retain];
81         
82     return copy;
83 }
84
85 - (id <WebCoreImageRenderer>)retainOrCopyIfNeeded
86 {
87     return [self copyWithZone:0];
88 }
89
90 - (void)resize:(NSSize)s
91 {
92     isSizeAdjusted = YES;
93     adjustedSize = s;
94 }
95
96 - (NSSize)size
97 {
98     if (isSizeAdjusted)
99         return adjustedSize;
100         
101     CGSize sz = [imageData size];
102     return NSMakeSize(sz.width, sz.height);
103 }
104
105 - (NSString *)MIMEType
106 {
107     return MIMEType;
108 }
109
110 - (int)frameCount
111 {
112     return [imageData numberOfImages];
113 }
114
115
116 - (BOOL)isNull
117 {
118     return [imageData isNull];
119 }
120
121 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete
122 {
123     if (!imageData)
124         imageData = [[WebImageData alloc] init];
125     return [imageData incrementalLoadWithBytes:bytes length:length complete:isComplete];
126 }
127
128 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
129 {
130     CGContextRef aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
131     CGCompositeOperation op = kCGCompositeSover;
132
133     [self drawImageInRect:ir fromRect:fr compositeOperator:op context:aContext];
134 }
135
136 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)operator context:(CGContextRef)aContext
137 {
138     if (aContext == 0)
139         aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
140     
141     CGCompositeOperation op = (CGCompositeOperation)operator;
142     if (op == kCGCompositeUnknown)
143         op = kCGCompositeSover;
144         
145     [imageData drawImageAtIndex:[imageData currentFrame] inRect:CGRectMake(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height) 
146                     fromRect:CGRectMake(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height) 
147                     compositeOperation:op context:aContext];
148
149     targetAnimationRect = ir;
150     [self _startOrContinueAnimationIfNecessary];
151 }
152
153 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext
154 {
155     if (aContext == 0)
156         aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
157
158     [imageData tileInRect:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)
159             fromPoint:CGPointMake(point.x, point.y) context:aContext];
160             
161     targetAnimationRect = rect;
162     [self _startOrContinueAnimationIfNecessary];
163 }
164
165 - (void)_startOrContinueAnimationIfNecessary
166 {
167     if ([imageData numberOfImages] > 1 && ![imageData isAnimationFinished]) {
168         [imageData addAnimatingRenderer:self inView:[NSView focusView]];
169         [imageData animate];
170     }
171 }
172
173 + (void)stopAnimationsInView:(NSView *)aView
174 {
175     [WebImageData stopAnimationsInView:aView];
176 }
177
178
179 - (void)stopAnimation
180 {
181     [imageData removeAnimatingRenderer:self];
182 }
183
184 - (NSRect)targetAnimationRect
185 {
186     return targetAnimationRect;
187 }
188
189 - (void)increaseUseCount
190 {
191 }
192
193 - (void)decreaseUseCount
194 {
195 }
196
197 - (void)flushRasterCache
198 {
199 }
200
201 - (CGImageRef)imageRef
202 {
203     return [imageData imageAtIndex:0];
204 }
205
206 - (NSData *)TIFFRepresentation
207 {
208     CGImageRef image = [imageData imageAtIndex:0];
209     if (!image)
210         return 0;
211         
212     CFMutableDataRef data = 0;
213     CGImageDestinationRef destination = 0;
214     
215     data = CFDataCreateMutable(NULL, 0);
216     // FIXME:  Use type kCGImageTypeIdentifierTIFF constant once is becomes available in the API
217     destination = CGImageDestinationCreateWithData (data, CFSTR("public.tiff"), 1, NULL);
218     if (destination) {
219         CGImageDestinationAddImage (destination, image, NULL);
220         CGImageDestinationFinalize (destination);
221         CFRelease (destination);
222     }
223
224     return [(NSData *)data autorelease];
225 }
226
227 - (NSImage *)image
228 {
229     // FIXME:  Implement
230     return nil;
231 }
232
233 @end
234
235 #else
236
237 @interface WebInternalImage : NSImage <NSCopying>
238 {
239     NSTimer *frameTimer;
240     NSView *frameView;
241     NSRect imageRect;
242     NSRect targetRect;
243
244     int loadStatus;
245
246     NSColor *patternColor;
247     int patternColorLoadStatus;
248
249     int repetitionsComplete;
250     BOOL animationFinished;
251     
252     NSPoint tilePoint;
253     BOOL animatedTile;
254
255     int compositeOperator;
256     id redirectContext;
257     CGContextRef context;
258     BOOL needFlushRasterCache;
259     BOOL rasterFlushing;
260     NSImageCacheMode rasterFlushingOldMode;
261     
262     NSString *MIMEType;
263     BOOL isNull;
264     int useCount;
265
266     CGImageRef cachedImageRef;
267     
268     id _PDFDoc;
269         
270 @public    
271     NSData *originalData;
272 }
273
274 - (id)initWithMIMEType:(NSString *)MIME;
275 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME;
276
277 - (void)releasePatternColor;
278
279 - (NSString *)MIMEType;
280 - (int)frameCount;
281
282 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete;
283 - (void)resize:(NSSize)s;
284 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr;
285 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)compsiteOperator context:(CGContextRef)context;
286 - (void)stopAnimation;
287 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext;
288 - (BOOL)isNull;
289 - (void)increaseUseCount;
290 - (void)decreaseUseCount;
291 - (WebImageRenderer *)createRendererIfNeeded;
292 - (void)flushRasterCache;
293 - (CGImageRef)imageRef;
294
295 + (void)stopAnimationsInView:(NSView *)aView;
296
297 - (void)startAnimationIfNecessary;
298 - (NSGraphicsContext *)_beginRedirectContext:(CGContextRef)aContext;
299 - (void)_endRedirectContext:(NSGraphicsContext *)aContext;
300 - (void)_needsRasterFlush;
301 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(NSCompositingOperation)op alpha:(float)alpha flipped:(BOOL)flipped;
302
303 @end
304
305 extern NSString *NSImageLoopCount;
306
307 /*
308     We need to get the AppKit to redirect drawing to our
309     CGContext.  There is currently (on Panther) no public
310     way to make this happen.  So we create a NSCGSContext
311     subclass and twiddle it's _cgsContext ivar directly.
312     Very fragile, but the only way...
313 */
314 @interface NSCGSContext : NSGraphicsContext {
315     CGContextRef _cgsContext;
316 }
317 @end
318
319 static CGImageRef _createImageRef(NSBitmapImageRep *rep);
320
321 @interface NSBitmapImageRep (AppKitInternals)
322 - (CGImageRef)_CGImageRef;
323 @end
324
325 @interface NSFocusStack : NSObject
326 @end
327
328 @interface WebImageContext : NSCGSContext {
329   @private
330     NSFocusStack* _focusStack;
331     NSRect        _bounds;
332     BOOL          _isFlipped;
333 }
334 - (id)initWithBounds:(NSRect)b context:(CGContextRef)context;
335 - (NSRect)bounds;
336 @end
337
338 @interface WebPDFDocument : NSObject
339 {
340     CGPDFDocumentRef _document;
341     CGRect           _mediaBox;
342     NSRect           _cropBox;
343     float            _rotation;
344     int              _currentPage;
345 }
346 - (id)               initWithData:(NSData*)data;
347 - (CGPDFDocumentRef) documentRef;
348 - (CGRect)           mediaBox;
349 - (NSRect)           bounds;    // adjust for rotation
350 - (void)             setCurrentPage:(int)page;
351 - (int)              currentPage;
352 - (int)              pageCount;
353 - (void)             adjustCTM:(CGContextRef)context;
354 @end
355
356 @implementation WebImageContext
357
358 - (id)initWithBounds:(NSRect)b context:(CGContextRef)context {
359     
360     self = [super init];
361     if (self != nil) {
362         _bounds     = b;
363         _isFlipped  = YES;
364         if (context) {
365             _cgsContext = CGContextRetain(context);
366         }
367     }
368     
369     return self;
370 }
371
372 - (void)dealloc
373 {
374     [_focusStack release];
375     if (_cgsContext) {
376         CGContextRelease(_cgsContext);
377         _cgsContext = 0; // super dealloc may also release
378     }
379     [super dealloc];
380 }
381
382 - (void)finalize
383 {
384     if (_cgsContext) {
385         CGContextRelease(_cgsContext);
386         _cgsContext = 0; // super finalize may also release
387     }
388     [super finalize];
389 }
390
391 - (void)saveGraphicsState
392 {
393     if (_cgsContext) {
394         CGContextSaveGState(_cgsContext);
395     }
396 }
397
398 - (void)restoreGraphicsState
399 {
400     if (_cgsContext) {
401         CGContextRestoreGState(_cgsContext);
402     }
403 }
404
405 - (BOOL)isDrawingToScreen
406 {
407     return NO;
408 }
409
410 - (void *)focusStack
411 {
412     if (!_focusStack) _focusStack = [[NSFocusStack allocWithZone:NULL] init];
413     return _focusStack;
414 }
415
416 - (void)setFocusStack:(void *)stack
417 {
418     id oldstack = _focusStack;
419     _focusStack = [(id)stack retain];
420     [oldstack release];
421 }
422
423 - (NSRect)bounds
424 {
425     return _bounds;
426 }
427
428 - (BOOL)isFlipped
429 {
430     return _isFlipped;
431 }
432
433 @end
434
435 #define MINIMUM_DURATION (1.0/30.0)
436
437 @implementation WebInternalImage
438
439 static NSMutableSet *activeImageRenderers;
440
441 + (void)stopAnimationsInView:(NSView *)aView
442 {
443     NSEnumerator *objectEnumerator = [activeImageRenderers objectEnumerator];
444     WebInternalImage *renderer;
445     NSMutableSet *renderersToStop = [[NSMutableSet alloc] init];
446
447     while ((renderer = [objectEnumerator nextObject])) {
448         if (renderer->frameView == aView) {
449             [renderersToStop addObject: renderer];
450         }
451     }
452
453     objectEnumerator = [renderersToStop objectEnumerator];
454     while ((renderer = [objectEnumerator nextObject])) {
455         if (renderer->frameView == aView) {
456             [renderer stopAnimation];
457         }
458     }
459     [renderersToStop release];
460 }
461
462 - (id)initWithMIMEType:(NSString *)MIME
463 {
464     self = [super init];
465     if (self != nil) {
466         // Work around issue with flipped images and TIFF by never using the image cache.
467         // See bug 3344259 and related bugs.
468         [self setCacheMode:NSImageCacheNever];
469
470         loadStatus = NSImageRepLoadStatusUnknownType;
471         MIMEType = [MIME copy];
472         isNull = YES;
473         compositeOperator = (int)NSCompositeSourceOver;
474     }
475     
476     return self;
477 }
478
479 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
480 {
481     WebInternalImage *result = nil;
482
483     NS_DURING
484     
485         result = [super initWithData:data];
486         if (result != nil) {
487             // Work around issue with flipped images and TIFF by never using the image cache.
488             // See bug 3344259 and related bugs.
489             [result setCacheMode:NSImageCacheNever];
490     
491             result->loadStatus = NSImageRepLoadStatusUnknownType;
492             result->MIMEType = [MIME copy];
493             result->isNull = [data length] == 0;
494             result->compositeOperator = (int)NSCompositeSourceOver;
495         }
496
497     NS_HANDLER
498
499         result = nil;
500
501     NS_ENDHANDLER
502
503     return result;
504 }
505
506 - (id)initWithContentsOfFile:(NSString *)filename
507 {
508     NSBundle *bundle = [NSBundle bundleForClass:[self class]];
509     NSString *imagePath = [bundle pathForResource:filename ofType:@"tiff"];
510     WebInternalImage *result = nil;
511
512     NS_DURING
513
514         result = [super initWithContentsOfFile:imagePath];
515         if (result != nil) {
516             // Work around issue with flipped images and TIFF by never using the image cache.
517             // See bug 3344259 and related bugs.
518             [result setCacheMode:NSImageCacheNever];
519     
520             result->loadStatus = NSImageRepLoadStatusUnknownType;
521             result->compositeOperator = (int)NSCompositeSourceOver;
522         }
523         
524     NS_HANDLER
525
526         result = nil;
527
528     NS_ENDHANDLER
529
530     return result;
531 }
532
533 - (void)increaseUseCount
534 {
535     useCount++;
536 }
537
538 - (void)decreaseUseCount
539 {
540     useCount--;
541 }
542
543 - (WebImageRenderer *)createRendererIfNeeded
544 {
545     // If an animated image appears multiple times in a given page, we
546     // must create multiple WebCoreImageRenderers so that each copy
547     // animates. However, we don't want to incur the expense of
548     // re-decoding for the very first use on a page, since QPixmap
549     // assignment always calls this method, even when just fetching
550     // the image from the cache for the first time for a page.
551     if (originalData && useCount) {
552         return [[[WebImageRendererFactory sharedFactory] imageRendererWithData:originalData MIMEType:MIMEType] retain];
553     }
554     return nil;
555 }
556
557 - copyWithZone:(NSZone *)zone
558 {
559     // FIXME: If we copy while doing an incremental load, it won't work.
560     WebInternalImage *copy;
561
562     copy = [super copyWithZone:zone];
563     copy->MIMEType = [MIMEType copy];
564     copy->originalData = [originalData retain];
565     copy->frameTimer = nil;
566     copy->frameView = nil;
567     copy->patternColor = nil;
568     copy->compositeOperator = compositeOperator;
569     copy->context = 0;
570
571     return copy;
572 }
573
574 - (BOOL)isNull
575 {
576     return isNull;
577 }
578
579 - (void)_adjustSizeToPixelDimensions
580 {
581     // Force the image to use the pixel size and ignore the dpi.
582     // Ignore any absolute size in the image and always use pixel dimensions.
583     NSBitmapImageRep *imageRep = [[self representations] objectAtIndex:0];
584     NSSize size = NSMakeSize([imageRep pixelsWide], [imageRep pixelsHigh]);
585     [imageRep setSize:size];
586         
587     [self setScalesWhenResized:YES];
588     [self setSize:size];
589 }
590
591 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete
592 {
593     NSArray *reps = [self representations];
594     NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
595     
596     if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]]) {
597         NSData *data = [[NSData alloc] initWithBytes:bytes length:length];
598
599         NS_DURING
600             // Get rep again to avoid bogus compiler warning.
601             NSBitmapImageRep *aRep = [[self representations] objectAtIndex:0];
602
603             loadStatus = [aRep incrementalLoadFromData:data complete:isComplete];
604         NS_HANDLER
605             loadStatus = NSImageRepLoadStatusInvalidData; // Arbitrary choice; any error will do.
606         NS_ENDHANDLER
607
608         // Hold onto the original data in case we need to copy this image.  (Workaround for appkit NSImage
609         // copy flaw).
610         if (isComplete && [self frameCount] > 1)
611             originalData = data;
612         else
613             [data release];
614
615         switch (loadStatus) {
616         case NSImageRepLoadStatusUnknownType:       // not enough data to determine image format. please feed me more data
617             //printf ("NSImageRepLoadStatusUnknownType size %d, isComplete %d\n", length, isComplete);
618             return NO;
619         case NSImageRepLoadStatusReadingHeader:     // image format known, reading header. not yet valid. more data needed
620             //printf ("NSImageRepLoadStatusReadingHeader size %d, isComplete %d\n", length, isComplete);
621             return NO;
622         case NSImageRepLoadStatusWillNeedAllData:   // can't read incrementally. will wait for complete data to become avail.
623             //printf ("NSImageRepLoadStatusWillNeedAllData size %d, isComplete %d\n", length, isComplete);
624             return NO;
625         case NSImageRepLoadStatusInvalidData:       // image decompression encountered error.
626             //printf ("NSImageRepLoadStatusInvalidData size %d, isComplete %d\n", length, isComplete);
627             return NO;
628         case NSImageRepLoadStatusUnexpectedEOF:     // ran out of data before full image was decompressed.
629             //printf ("NSImageRepLoadStatusUnexpectedEOF size %d, isComplete %d\n", length, isComplete);
630             return NO;
631         case NSImageRepLoadStatusCompleted:         // all is well, the full pixelsHigh image is valid.
632             //printf ("NSImageRepLoadStatusCompleted size %d, isComplete %d\n", length, isComplete);
633             [self _adjustSizeToPixelDimensions];        
634             isNull = NO;
635             return YES;
636         default:
637             [self _adjustSizeToPixelDimensions];
638             //printf ("incrementalLoadWithBytes: size %d, isComplete %d\n", length, isComplete);
639             // We have some valid data.  Return YES so we can attempt to draw what we've got.
640             isNull = NO;
641             return YES;
642         }
643     }
644     else {
645         if (isComplete) {
646             originalData = [[NSData alloc] initWithBytes:bytes length:length];
647             if ([MIMEType isEqual:@"application/pdf"]) {
648                 Class repClass = [NSImageRep imageRepClassForData:originalData];
649                 if (repClass) {
650                     NSImageRep *rep = [[repClass alloc] initWithData:originalData];
651                     [self addRepresentation:rep];
652                 }
653             }
654             isNull = NO;
655             return YES;
656         }
657     }
658     return NO;
659 }
660
661 - (void)dealloc
662 {
663     ASSERT(frameTimer == nil);
664     ASSERT(frameView == nil);
665     [patternColor release];
666     [MIMEType release];
667     [originalData release];
668     
669     if (context) {
670         CGContextRelease(context);
671         context = 0;
672     }
673
674     if (cachedImageRef) {
675         CGImageRelease (cachedImageRef);
676         cachedImageRef = 0;
677     }
678     
679     [_PDFDoc release];
680
681     [super dealloc];
682 }
683
684 - (void)finalize
685 {
686     ASSERT(frameTimer == nil);
687     ASSERT(frameView == nil);
688
689     if (context) {
690         CGContextRelease(context);
691     }
692
693     if (cachedImageRef) {
694         CGImageRelease (cachedImageRef);
695         cachedImageRef = 0;
696     }
697     
698     [super finalize];
699 }
700
701 - (id)firstRepProperty:(NSString *)propertyName
702 {
703     id firstRep = [[self representations] objectAtIndex:0];
704     id property = nil;
705     if ([firstRep respondsToSelector:@selector(valueForProperty:)])
706         property = [firstRep valueForProperty:propertyName];
707     return property;
708 }
709
710 - (int)frameCount
711 {
712     id property = [self firstRepProperty:NSImageFrameCount];
713     return property ? [property intValue] : 1;
714 }
715
716 - (int)currentFrame
717 {
718     id property = [self firstRepProperty:NSImageCurrentFrame];
719     return property ? [property intValue] : 1;
720 }
721
722 - (void)setCurrentFrame:(int)frame
723 {
724     NSBitmapImageRep *imageRep = [[self representations] objectAtIndex:0];
725     [imageRep setProperty:NSImageCurrentFrame withValue:[NSNumber numberWithInt:frame]];
726 }
727
728 - (float)unadjustedFrameDuration
729 {
730     id property = [self firstRepProperty:NSImageCurrentFrameDuration];
731     return property ? [property floatValue] : 0.0;
732 }
733
734 - (float)frameDuration
735 {
736     float duration = [self unadjustedFrameDuration];
737     if (duration < MINIMUM_DURATION) {
738         /*
739             Many annoying ads specify a 0 duration to make an image flash
740             as quickly as possible.  However a zero duration is faster than
741             the refresh rate.  We need to pick a minimum duration.
742             
743             Browsers handle the minimum time case differently.  IE seems to use something
744             close to 1/30th of a second.  Konqueror uses 0.  The ImageMagick library
745             uses 1/100th.  The units in the GIF specification are 1/100th of second.
746             We will use 1/30th of second as the minimum time.
747         */
748         duration = MINIMUM_DURATION;
749     }
750     return duration;
751 }
752
753 - (int)repetitionCount
754 {
755     id property = [self firstRepProperty:NSImageLoopCount];
756     int count = property ? [property intValue] : 0;
757     return count;
758 }
759
760 - (void)scheduleFrame
761 {
762     if (frameTimer && [frameTimer isValid])
763         return;
764     frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self frameDuration]
765                                                     target:self
766                                                   selector:@selector(nextFrame:)
767                                                   userInfo:nil
768                                                    repeats:NO] retain];
769 }
770
771 - (NSGraphicsContext *)_beginRedirectContext:(CGContextRef)aContext
772 {
773     NSGraphicsContext *oldContext = 0;
774     if (aContext) {
775         oldContext = [NSGraphicsContext currentContext];
776         // Assumes that we are redirecting to a CGBitmapContext.
777         size_t w = CGBitmapContextGetWidth (aContext);
778         size_t h = CGBitmapContextGetHeight (aContext);
779         redirectContext = [[WebImageContext alloc] initWithBounds:NSMakeRect(0, 0, (float)w, (float)h) context:aContext];
780         [NSGraphicsContext setCurrentContext:redirectContext];
781     }
782     return oldContext; 
783 }
784
785 - (void)_endRedirectContext:(NSGraphicsContext *)aContext
786 {
787     if (aContext) {
788         [NSGraphicsContext setCurrentContext:aContext];
789         [redirectContext autorelease];
790         redirectContext = 0;
791     }
792 }
793
794 - (void)_needsRasterFlush
795 {
796 #if 0
797     if (needFlushRasterCache && [MIMEType isEqual: @"application/pdf"]) {
798         // FIXME:  At this point we need to flush the cached rasterized PDF.
799     }
800 #endif
801 }
802
803 - (void)_adjustColorSpace
804 {
805 #if COLORMATCH_EVERYTHING
806     NSArray *reps = [self representations];
807     NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
808     if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]] &&
809         [imageRep valueForProperty:NSImageColorSyncProfileData] == nil &&
810         [[imageRep colorSpaceName] isEqualToString:NSDeviceRGBColorSpace]) {
811         [imageRep setColorSpaceName:NSCalibratedRGBColorSpace];
812     }
813 #else
814     NSArray *reps = [self representations];
815     NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
816     if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]] &&
817         [imageRep valueForProperty:NSImageColorSyncProfileData] == nil &&
818         [[imageRep colorSpaceName] isEqualToString:NSCalibratedRGBColorSpace]) {
819         [imageRep setColorSpaceName:NSDeviceRGBColorSpace];
820     }
821 #endif
822 }
823
824
825 - (void)drawClippedToValidInRect:(NSRect)ir fromRect:(NSRect)fr
826 {
827     [self _adjustColorSpace];
828
829     if (loadStatus >= 0) {
830         // The last line might be a partial line, so the number of complete lines is the number
831         // we get from NSImage minus one.
832         int numCompleteLines = loadStatus - 1;
833         if (numCompleteLines <= 0) {
834             return;
835         }
836         int pixelsHigh = [[[self representations] objectAtIndex:0] pixelsHigh];
837         if (pixelsHigh > numCompleteLines) {
838             // Figure out how much of the image is OK to draw.  We can't simply
839             // use numCompleteLines because the image may be scaled.
840             float clippedImageHeight = floor([self size].height * numCompleteLines / pixelsHigh);
841             
842             // Figure out how much of the source is OK to draw from.
843             float clippedSourceHeight = clippedImageHeight - fr.origin.y;
844             if (clippedSourceHeight < 1) {
845                 return;
846             }
847             
848             // Figure out how much of the destination we are going to draw to.
849             float clippedDestinationHeight = ir.size.height * clippedSourceHeight / fr.size.height;
850
851             // Reduce heights of both rectangles without changing their positions.
852             // In the flipped case, just adjusting the height is sufficient.
853             ASSERT([self isFlipped]);
854             ASSERT([[NSView focusView] isFlipped]);
855             ir.size.height = clippedDestinationHeight;
856             fr.size.height = clippedSourceHeight;
857         }
858     }
859     
860     if (context) {
861         NSGraphicsContext *oldContext = [self _beginRedirectContext:context];
862         [self _needsRasterFlush];
863
864         // If we have PDF then draw the PDF ourselves, bypassing the NSImage caching mechanisms,
865         // but only do this when we're rendering to an offscreen context.  NSImage will always
866         // cache the PDF image at it's native resolution, thus, causing artifacts when the image
867         // is drawn into a scaled or rotated context.
868         if ([MIMEType isEqual:@"application/pdf"])
869             [self _PDFDrawFromRect:fr toRect:ir operation:compositeOperator alpha:1.0 flipped:YES];
870         else
871             [self drawInRect:ir fromRect:fr operation:compositeOperator fraction: 1.0];
872
873         [self _endRedirectContext:oldContext];
874     }
875     else {
876         [self drawInRect:ir fromRect:fr operation:compositeOperator fraction: 1.0];
877     }
878 }
879
880 - (CGPDFDocumentRef)_PDFDocumentRef
881 {
882     if (!_PDFDoc) {
883         _PDFDoc = [[WebPDFDocument alloc] initWithData:originalData];
884     }
885         
886     return [_PDFDoc documentRef];
887 }
888
889 - (void)_PDFDraw
890 {
891     CGPDFDocumentRef document = [self _PDFDocumentRef];
892     if (document != NULL) {
893         CGContextRef _context  = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
894         CGRect       mediaBox = [_PDFDoc mediaBox];
895         
896         CGContextSaveGState(_context);
897         // Rotate translate image into position according to doc properties.
898         [_PDFDoc adjustCTM:_context];   
899
900         // Media box may have non-zero origin which we ignore. CGPDFDocumentShowPage pages start
901         // at 1, not 0.
902         CGContextDrawPDFDocument(_context, CGRectMake(0, 0, mediaBox.size.width, mediaBox.size.height), document, 1);
903
904         CGContextRestoreGState(_context);
905     }
906 }
907
908 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(NSCompositingOperation)op alpha:(float)alpha flipped:(BOOL)flipped
909 {
910     // FIXME:  The rasterized PDF should be drawn into a cache, and the raster then composited.
911     
912     CGContextRef _context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
913     float hScale, vScale;
914
915     CGContextSaveGState(_context);
916
917     CGContextSetCompositeOperation (_context, op);
918
919     // Scale and translate so the document is rendered in the correct location.
920     hScale = dstRect.size.width  / srcRect.size.width;
921     vScale = dstRect.size.height / srcRect.size.height;
922
923     CGContextTranslateCTM (_context, dstRect.origin.x - srcRect.origin.x * hScale, dstRect.origin.y - srcRect.origin.y * vScale);
924     CGContextScaleCTM (_context, hScale, vScale);
925
926     // Reverse if flipped image.
927     if (flipped) {
928         CGContextScaleCTM(_context, 1, -1);
929         CGContextTranslateCTM (_context, 0, -(dstRect.origin.y + dstRect.size.height));
930     }
931
932     // Clip to destination in case we are imaging part of the source only
933     CGContextClipToRect(_context, CGRectIntegral(*(CGRect*)&srcRect));
934
935     // and draw
936     [self _PDFDraw];
937
938     // done with our fancy transforms
939     CGContextRestoreGState(_context);
940
941     return YES;
942 }
943
944 - (void)nextFrame:(id)context
945 {
946     int currentFrame;
947     
948     // Release the timer that just fired.
949     [frameTimer release];
950     frameTimer = nil;
951     
952     currentFrame = [self currentFrame] + 1;
953     if (currentFrame >= [self frameCount]) {
954         repetitionsComplete += 1;
955         if ([self repetitionCount] && repetitionsComplete >= [self repetitionCount]) {
956             animationFinished = YES;
957             return;
958         }
959         currentFrame = 0;
960     }
961     [self setCurrentFrame:currentFrame];
962     
963     // Release the tiling pattern so next frame will update the pattern if we're tiling.
964     [patternColor release];
965     patternColor = nil;
966     
967     [frameView setNeedsDisplayInRect:targetRect];
968 }
969
970 // Will be called when the containing view is displayed by WebCore RenderImage (via QPainter).
971 // If the image is an animated image it will begin animating.  If the image is already animating,
972 // it's frame will have been advanced by nextFrame:.
973 //
974 // Also used to draw the image by WebImageView.
975 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
976 {
977     if (animatedTile){
978         [self tileInRect:ir fromPoint:tilePoint context:context];
979     }
980     else {
981         [self drawClippedToValidInRect:ir fromRect:fr];
982         imageRect = fr;
983         targetRect = ir;
984         [self startAnimationIfNecessary];
985     }
986 }
987
988 - (void)flushRasterCache
989 {
990     needFlushRasterCache = YES;
991 }
992
993 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)operator context:(CGContextRef)aContext
994 {
995     compositeOperator = operator;
996     
997     if (aContext != context) {
998         if (aContext) {
999             CGContextRetain(aContext);
1000         }
1001         if (context) {
1002             CGContextRelease(context);
1003         }
1004         context = aContext;
1005     }
1006         
1007     [self drawImageInRect:ir fromRect:fr];
1008 }
1009
1010 - (void)startAnimationIfNecessary
1011 {
1012     if ([self frameCount] > 1 && !animationFinished) {
1013         NSView *newView = [NSView focusView];
1014         if (newView != frameView){
1015             [frameView release];
1016             frameView = [newView retain];
1017         }
1018         [self scheduleFrame];
1019         if (!activeImageRenderers) {
1020             activeImageRenderers = [[NSMutableSet alloc] init];
1021         }
1022         [activeImageRenderers addObject:self];
1023     }
1024 }
1025
1026 - (void)stopAnimation
1027 {
1028     [frameTimer invalidate];
1029     [frameTimer release];
1030     frameTimer = nil;
1031     
1032     [frameView release];
1033     frameView = nil;
1034
1035     [activeImageRenderers removeObject:self];
1036 }
1037
1038 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext
1039 {
1040     // These calculations are only correct for the flipped case.
1041     ASSERT([self isFlipped]);
1042     ASSERT([[NSView focusView] isFlipped]);
1043
1044     [self _adjustColorSpace];
1045
1046     NSSize size = [self size];
1047
1048     // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
1049     NSRect oneTileRect;
1050     oneTileRect.origin.x = rect.origin.x + fmodf(fmodf(-point.x, size.width) - size.width, size.width);
1051     oneTileRect.origin.y = rect.origin.y + fmodf(fmodf(-point.y, size.height) - size.height, size.height);
1052 // I think this is a simpler way to say the same thing.  Also, if either point.x or point.y is negative, both
1053 // methods would end up with the wrong answer.  For example, fmod(-22,5) is -2, which is the correct delta to
1054 // the start of the pattern, but fmod(-(-23), 5) is 3.  This is the delta to the *end* of the pattern
1055 // instead of the start, so onTileRect will be too far right.
1056 //    oneTileRect.origin.x = rect.origin.x - fmodf(point.x, size.width);
1057 //    oneTileRect.origin.y = rect.origin.y - fmodf(point.y, size.height);
1058     oneTileRect.size = size;
1059
1060     // Compute the appropriate phase relative to the top level view in the window.
1061     // Conveniently, the oneTileRect we computed above has the appropriate origin.
1062     NSPoint originInWindow = [[NSView focusView] convertPoint:oneTileRect.origin toView:nil];
1063     // WebCore may translate the focus, and thus need an extra phase correction
1064     NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
1065     originInWindow.x += extraPhase.x;
1066     originInWindow.y += extraPhase.y;
1067     CGSize phase = CGSizeMake(fmodf(originInWindow.x, size.width), fmodf(originInWindow.y, size.height));
1068     
1069     // If the single image draw covers the whole area, then just draw once.
1070     if (NSContainsRect(oneTileRect, rect)) {
1071         NSRect fromRect;
1072
1073         fromRect.origin.x = rect.origin.x - oneTileRect.origin.x;
1074         fromRect.origin.y = rect.origin.y - oneTileRect.origin.y;
1075         fromRect.size = rect.size;
1076         
1077         [self drawClippedToValidInRect:rect fromRect:fromRect];
1078         return;
1079     }
1080
1081     // If we only have a partial image, just do nothing, because CoreGraphics will not help us tile
1082     // with a partial image. But maybe later we can fix this by constructing a pattern image that's
1083     // transparent where needed.
1084     if (loadStatus != NSImageRepLoadStatusCompleted) {
1085         return;
1086     }
1087     
1088     // Since we need to tile, construct an NSColor so we can get CoreGraphics to do it for us.
1089     if (patternColorLoadStatus != loadStatus) {
1090         [patternColor release];
1091         patternColor = nil;
1092     }
1093     if (patternColor == nil) {
1094         patternColor = [[NSColor colorWithPatternImage:self] retain];
1095         patternColorLoadStatus = loadStatus;
1096     }
1097         
1098     NSGraphicsContext *oldContext = [self _beginRedirectContext:context];
1099     [self _needsRasterFlush];
1100     
1101     [NSGraphicsContext saveGraphicsState];
1102     
1103     CGContextSetPatternPhase((CGContextRef)[[NSGraphicsContext currentContext] graphicsPort], phase);    
1104     [patternColor set];
1105     [NSBezierPath fillRect:rect];
1106     
1107     [NSGraphicsContext restoreGraphicsState];
1108
1109     [self _endRedirectContext:oldContext];
1110
1111     animatedTile = YES;
1112     tilePoint = point;
1113     targetRect = rect;
1114     [self startAnimationIfNecessary];
1115 }
1116
1117 - (void)resize:(NSSize)s
1118 {
1119     [self setScalesWhenResized:YES];
1120     [self setSize:s];
1121 }
1122
1123 - (NSString *)MIMEType
1124 {
1125     return MIMEType;
1126 }
1127
1128 // Try hard to get a CGImageRef.  First try to snag one from the
1129 // NSBitmapImageRep, then try to create one.  In some cases we may
1130 // not be able to create one.
1131 - (CGImageRef)imageRef
1132 {
1133     CGImageRef ref = 0;
1134     
1135     if (cachedImageRef)
1136         return cachedImageRef;
1137         
1138     if ([[self representations] count] > 0) {
1139         NSBitmapImageRep *rep = [[self representations] objectAtIndex:0];
1140         
1141         if ([rep respondsToSelector:@selector(_CGImageRef)])
1142             ref =  [rep _CGImageRef];
1143             
1144         if (!ref) {
1145             cachedImageRef = _createImageRef(rep);
1146             ref = cachedImageRef;
1147         }
1148     }
1149     return ref;
1150 }
1151
1152 - (void)releasePatternColor
1153 {
1154     [patternColor release];
1155     patternColor = nil;
1156 }
1157
1158 @end
1159
1160 @implementation WebImageRenderer
1161
1162 - (id)initWithMIMEType:(NSString *)MIME
1163 {
1164     WebInternalImage *i = [[WebInternalImage alloc] initWithMIMEType:MIME];
1165     if (i == nil) {
1166         [self dealloc];
1167         return nil;
1168     }
1169     [self init];
1170     image = i;
1171     return self;
1172 }
1173
1174 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
1175 {
1176     WebInternalImage *i = [[WebInternalImage alloc] initWithData:data MIMEType:MIME];
1177     if (i == nil) {
1178         [self dealloc];
1179         return nil;
1180     }
1181     [self init];
1182     image = i;
1183     return self;
1184 }
1185
1186 - (id)initWithContentsOfFile:(NSString *)filename
1187 {
1188     WebInternalImage *i = [[WebInternalImage alloc] initWithContentsOfFile:filename];
1189     if (i == nil) {
1190         [self dealloc];
1191         return nil;
1192     }
1193     [self init];
1194     image = i;
1195     return self;
1196 }
1197
1198 - (void)dealloc
1199 {
1200     [image releasePatternColor];
1201     [image release];
1202     [super dealloc];
1203 }
1204
1205 - (NSImage *)image
1206 {
1207     return image;
1208 }
1209
1210 - (NSString *)MIMEType
1211 {
1212     return [image MIMEType];
1213 }
1214
1215 - (NSData *)TIFFRepresentation
1216 {
1217     return [image TIFFRepresentation];
1218 }
1219
1220 - (int)frameCount
1221 {
1222     return [image frameCount];
1223 }
1224
1225 - (void)setOriginalData:(NSData *)data
1226 {
1227     NSData *oldData = image->originalData;
1228     image->originalData = [data retain];
1229     [oldData release];
1230 }
1231
1232 + (void)stopAnimationsInView:(NSView *)aView
1233 {
1234     [WebInternalImage stopAnimationsInView:aView];
1235 }
1236
1237 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete
1238 {
1239     return [image incrementalLoadWithBytes:bytes length:length complete:isComplete];
1240 }
1241
1242 - (NSSize)size
1243 {
1244     return [image size];
1245 }
1246
1247 - (void)resize:(NSSize)s
1248 {
1249     [image resize:s];
1250 }
1251
1252 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
1253 {
1254     [image drawImageInRect:ir fromRect:fr];
1255 }
1256
1257 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)compsiteOperator context:(CGContextRef)context
1258 {
1259     [image drawImageInRect:ir fromRect:fr compositeOperator:compsiteOperator context:context];
1260 }
1261
1262 - (void)stopAnimation
1263 {
1264     [image stopAnimation];
1265 }
1266
1267 - (void)tileInRect:(NSRect)r fromPoint:(NSPoint)p context:(CGContextRef)context
1268 {
1269     [image tileInRect:r fromPoint:p context:context];
1270 }
1271
1272 - (BOOL)isNull
1273 {
1274     return image == nil || [image isNull];
1275 }
1276
1277 - (id <WebCoreImageRenderer>)retainOrCopyIfNeeded
1278 {
1279     WebImageRenderer *newRenderer = [image createRendererIfNeeded];
1280     if (newRenderer) {
1281         return newRenderer;
1282     }
1283     [self retain];
1284     return self;
1285 }
1286
1287 - (void)increaseUseCount
1288 {
1289     [image increaseUseCount];
1290 }
1291
1292 - (void)decreaseUseCount
1293 {
1294     [image decreaseUseCount];
1295 }
1296
1297 - (void)flushRasterCache
1298 {
1299     [image flushRasterCache];
1300 }
1301
1302 - (CGImageRef)imageRef
1303 {
1304     return [image imageRef];
1305 }
1306
1307 - (id)copyWithZone:(NSZone *)zone
1308 {
1309     WebImageRenderer *copy = [[WebImageRenderer alloc] init];
1310     copy->image = [image copy];
1311     return copy;
1312 }
1313
1314 @end
1315
1316 //------------------------------------------------------------------------------------
1317
1318 @implementation WebPDFDocument
1319
1320 static void ReleasePDFDocumentData(void *info, const void *data, size_t size) {
1321     [(NSData*)info autorelease];
1322 }
1323
1324 - (id) initWithData:(NSData*)data
1325 {
1326     self = [super init];
1327     if (self != nil)
1328     {
1329         if (data != nil) {
1330             CGDataProviderRef dataProvider = CGDataProviderCreateWithData([data retain], [data bytes], [data length], ReleasePDFDocumentData);
1331             _document = CGPDFDocumentCreateWithProvider(dataProvider);
1332             CGDataProviderRelease(dataProvider);
1333         }
1334         _currentPage = -1;
1335         [self setCurrentPage:0];
1336     }
1337     return self;
1338 }
1339
1340 - (void)dealloc
1341 {
1342     if (_document != NULL) CGPDFDocumentRelease(_document);
1343     [super dealloc];
1344 }
1345
1346 - (void)finalize
1347 {
1348     if (_document != NULL) CGPDFDocumentRelease(_document);
1349     [super finalize];
1350 }
1351
1352 - (CGPDFDocumentRef) documentRef
1353 {
1354     return _document;
1355 }
1356
1357 - (CGRect) mediaBox
1358 {
1359     return _mediaBox;
1360 }
1361
1362 - (NSRect) bounds
1363 {
1364     NSRect rotatedRect;
1365
1366     // rotate the media box and calculate bounding box
1367     float sina   = sin (_rotation);
1368     float cosa   = cos (_rotation);
1369     float width  = NSWidth (_cropBox);
1370     float height = NSHeight(_cropBox);
1371
1372     // calculate rotated x and y axis
1373     NSPoint rx = NSMakePoint( width  * cosa, width  * sina);
1374     NSPoint ry = NSMakePoint(-height * sina, height * cosa);
1375
1376     // find delta width and height of rotated points
1377     rotatedRect.origin      = _cropBox.origin;
1378     rotatedRect.size.width  = ceil(fabs(rx.x - ry.x));
1379     rotatedRect.size.height = ceil(fabs(ry.y - rx.y));
1380
1381     return rotatedRect;
1382 }
1383
1384 - (void) adjustCTM:(CGContextRef)context
1385 {
1386     // rotate the crop box and calculate bounding box
1387     float sina   = sin (-_rotation);
1388     float cosa   = cos (-_rotation);
1389     float width  = NSWidth (_cropBox);
1390     float height = NSHeight(_cropBox);
1391
1392     // calculate rotated x and y edges of the corp box. if they're negative, it means part of the image has
1393     // been rotated outside of the bounds and we need to shift over the image so it lies inside the bounds again
1394     NSPoint rx = NSMakePoint( width  * cosa, width  * sina);
1395     NSPoint ry = NSMakePoint(-height * sina, height * cosa);
1396
1397     // adjust so we are at the crop box origin
1398     CGContextTranslateCTM(context, floor(-MIN(0,MIN(rx.x, ry.x))), floor(-MIN(0,MIN(rx.y, ry.y))));
1399
1400     // rotate -ve to remove rotation
1401     CGContextRotateCTM(context, -_rotation);
1402
1403     // shift so we are completely within media box
1404     CGContextTranslateCTM(context, _mediaBox.origin.x - _cropBox.origin.x, _mediaBox.origin.y - _cropBox.origin.y);
1405 }
1406
1407 - (void) setCurrentPage:(int)page
1408 {
1409     if (page != _currentPage && page >= 0 && page < [self pageCount]) {
1410
1411         CGRect r;
1412
1413         _currentPage = page;
1414
1415         // get media box (guaranteed)
1416         _mediaBox = CGPDFDocumentGetMediaBox(_document, page + 1);
1417
1418         // get crop box (not always there). if not, use _mediaBox
1419         r = CGPDFDocumentGetCropBox(_document, page + 1);
1420         if (!CGRectIsEmpty(r)) {
1421             _cropBox = NSMakeRect(r.origin.x, r.origin.y, r.size.width, r.size.height);
1422         } else {
1423             _cropBox = NSMakeRect(_mediaBox.origin.x, _mediaBox.origin.y, _mediaBox.size.width, _mediaBox.size.height);
1424         }
1425
1426         // get page rotation angle
1427         _rotation = CGPDFDocumentGetRotationAngle(_document, page + 1) * M_PI / 180.0;  // to radians
1428     }
1429 }
1430
1431 - (int) currentPage
1432 {
1433     return _currentPage;
1434 }
1435
1436 - (int) pageCount
1437 {
1438     return CGPDFDocumentGetNumberOfPages(_document);
1439 }
1440
1441 @end
1442
1443 static CGImageRef _createImageRef(NSBitmapImageRep *rep) {
1444     BOOL isPlanar = [rep isPlanar];
1445     if (isPlanar)
1446         return 0;
1447         
1448     const unsigned char *bitmapData = [rep bitmapData];
1449     int pixelsWide = [rep pixelsWide];
1450     int pixelsHigh = [rep pixelsHigh];
1451     int bitsPerSample = [rep bitsPerSample];
1452     int bitsPerPixel = [rep bitsPerPixel];
1453     int bytesPerRow = [rep bytesPerRow];
1454     BOOL hasAlpha = [rep hasAlpha];
1455     CGImageRef image;
1456     CGDataProviderRef dataProvider;
1457
1458     CGColorSpaceRef colorSpace = WebCGColorSpaceCreateRGB();
1459     dataProvider = CGDataProviderCreateWithData(NULL, bitmapData, pixelsHigh * bytesPerRow, NULL);
1460
1461     image = CGImageCreate(pixelsWide, pixelsHigh, bitsPerSample, bitsPerPixel, bytesPerRow, colorSpace,
1462                           hasAlpha ? kCGImageAlphaPremultipliedLast : kCGImageAlphaNone,
1463                           dataProvider, NULL, false /*shouldInterpolate*/, kCGRenderingIntentDefault);
1464
1465     CGDataProviderRelease(dataProvider);
1466     CGColorSpaceRelease(colorSpace);    
1467
1468     return image;
1469 }
1470 #endif
1471
1472 CGColorSpaceRef WebCGColorSpaceCreateRGB(void)
1473 {
1474 #ifdef COLORMATCH_EVERYTHING
1475 #if BUILDING_ON_PANTHER
1476     return CGColorSpaceCreateWithName(kCGColorSpaceUserRGB);
1477 #else // !BUILDING_ON_PANTHER
1478     return CGColorSpaceCreateDeviceRGB();
1479 #endif // BUILDING_ON_PANTHER
1480 #else // !COLORMATCH_EVERYTHING
1481 #if BUILDING_ONPANTHER
1482     return CGColorSpaceCreateDeviceRGB();
1483 #else // !BUILDING_ON_PANTHER
1484     return CGColorSpaceCreateDisplayRGB();
1485 #endif // BUILDING_ON_PANTHER
1486 #endif    
1487 }
1488
1489 CGColorSpaceRef WebCGColorSpaceCreateGray(void)
1490 {
1491 #ifdef COLORMATCH_EVERYTHING
1492 #if BUILDING_ON_PANTHER
1493     return CGColorSpaceCreateWithName(kCGColorSpaceUserGray);
1494 #else // !BUILDING_ON_PANTHER
1495     return CGColorSpaceCreateDeviceGray();
1496 #endif // BUILDING_ON_PANTHER
1497 #else // !COLORMATCH_EVERYTHING
1498 #if BUILDING_ONPANTHER
1499     return CGColorSpaceCreateDeviceGray();
1500 #else // !BUILDING_ON_PANTHER
1501     return CGColorSpaceCreateDisplayGray();
1502 #endif // BUILDING_ON_PANTHER
1503 #endif    
1504 }
1505
1506 CGColorSpaceRef WebCGColorSpaceCreateCMYK(void)
1507 {
1508 #ifdef COLORMATCH_EVERYTHING
1509 #if BUILDING_ON_PANTHER
1510     return CGColorSpaceCreateWithName(kCGColorSpaceUserCMYK);
1511 #else // !BUILDING_ON_PANTHER
1512     return CGColorSpaceCreateDeviceCMYK();
1513 #endif // BUILDING_ON_PANTHER
1514 #else // !COLORMATCH_EVERYTHING
1515 #if BUILDING_ONPANTHER
1516     return CGColorSpaceCreateDeviceCMYK();
1517 #else // !BUILDING_ON_PANTHER
1518     // FIXME: no device CMYK
1519     return CGColorSpaceCreateDeviceCMYK();
1520 #endif // BUILDING_ON_PANTHER
1521 #endif    
1522 }