3 Copyright (c) 2002, 2003, Apple, Inc. All rights reserved.
5 #import <WebKit/WebImageRenderer.h>
7 #import <WebKit/WebAssertions.h>
8 #import <WebKit/WebImageRendererFactory.h>
9 #import <WebKit/WebGraphicsBridge.h>
10 #import <WebKit/WebHTMLView.h>
11 #import <WebKit/WebImageView.h>
12 #import <WebKit/WebNSObjectExtras.h>
14 #import <WebCore/WebCoreImageRenderer.h>
16 #import <CoreGraphics/CGContextPrivate.h>
17 #import <CoreGraphics/CGContextGState.h>
18 #import <CoreGraphics/CGColorSpacePrivate.h>
23 #import <WebKit/WebImageData.h>
25 // Forward declarations of internal methods.
26 @interface WebImageRenderer (WebInternal)
27 - (void)_startOrContinueAnimationIfNecessary;
30 @implementation WebImageRenderer
32 - (id)initWithMIMEType:(NSString *)MIME
36 MIMEType = [MIME copy];
41 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
45 MIMEType = [MIME copy];
46 imageData = [[WebImageData alloc] init];
47 [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES callback:0];
52 - (id)initWithContentsOfFile:(NSString *)filename
56 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
57 NSString *imagePath = [bundle pathForResource:filename ofType:@"tiff"];
59 imageData = [[WebImageData alloc] init];
60 NSData *data = [NSData dataWithContentsOfFile:imagePath];
61 [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES callback:0];
76 - copyWithZone:(NSZone *)zone
78 WebImageRenderer *copy;
80 copy = [[WebImageRenderer alloc] init];
81 copy->MIMEType = [MIMEType copy];
82 copy->adjustedSize = adjustedSize;
83 copy->isSizeAdjusted = isSizeAdjusted;
84 copy->imageData = [imageData retain];
89 - (id <WebCoreImageRenderer>)retainOrCopyIfNeeded
91 return [self copyWithZone:0];
94 - (void)resize:(NSSize)s
108 CGSize sz = [imageData size];
109 return NSMakeSize(sz.width, sz.height);
112 - (NSString *)MIMEType
119 return [imageData numberOfImages];
125 return [imageData isNull];
128 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
131 imageData = [[WebImageData alloc] init];
132 if ([MIMEType isEqual:@"application/pdf"]) {
133 [imageData setIsPDF:YES];
136 return [imageData incrementalLoadWithBytes:bytes length:length complete:isComplete callback:c];
139 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
141 CGContextRef aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
142 CGCompositeOperation op = kCGCompositeSover;
144 [self drawImageInRect:ir fromRect:fr compositeOperator:op context:aContext];
147 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)operator context:(CGContextRef)aContext
150 aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
152 CGCompositeOperation op = (CGCompositeOperation)operator;
153 if (op == kCGCompositeUnknown)
154 op = kCGCompositeSover;
156 if (isSizeAdjusted) {
157 [imageData drawImageAtIndex:[imageData currentFrame] inRect:CGRectMake(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height)
158 fromRect:CGRectMake(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height)
159 adjustedSize:CGSizeMake(adjustedSize.width, adjustedSize.height)
160 compositeOperation:op context:aContext];
163 [imageData drawImageAtIndex:[imageData currentFrame] inRect:CGRectMake(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height)
164 fromRect:CGRectMake(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height)
165 compositeOperation:op context:aContext];
168 targetAnimationRect = ir;
169 [self _startOrContinueAnimationIfNecessary];
172 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext
175 aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
177 [imageData tileInRect:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)
178 fromPoint:CGPointMake(point.x, point.y) context:aContext];
180 targetAnimationRect = rect;
181 [self _startOrContinueAnimationIfNecessary];
184 - (void)_startOrContinueAnimationIfNecessary
186 NSView *targetView = [NSView focusView];
188 // Only animate if we're drawing into a WebHTMLView or WebImageView. This fixes problems
189 // like <rdar://problem/3966973>, which describes a third party application that renders thumbnails of
190 // the page into a alternate view.
191 if (([targetView isKindOfClass:[WebHTMLView class]] || [targetView isKindOfClass:[WebImageView class]])
192 && [imageData shouldAnimate] && [MIMEType isEqual:@"image/gif"]) {
193 [imageData addAnimatingRenderer:self inView:targetView];
198 + (void)stopAnimationsInView:(NSView *)aView
200 [WebImageData stopAnimationsInView:aView];
203 - (void)resetAnimation
205 [imageData resetAnimation];
209 - (void)stopAnimation
211 [imageData removeAnimatingRenderer:self];
214 - (NSRect)targetAnimationRect
216 return targetAnimationRect;
219 - (void)increaseUseCount
223 - (void)decreaseUseCount
227 - (void)flushRasterCache
231 - (CGImageRef)imageRef
233 return [imageData imageAtIndex:0];
236 - (NSData *)TIFFRepresentation
239 CGImageRef image = [imageData imageAtIndex:0];
243 CFMutableDataRef data = 0;
244 CGImageDestinationRef destination = 0;
246 data = CFDataCreateMutable(NULL, 0);
247 // FIXME: Use type kCGImageTypeIdentifierTIFF constant once is becomes available in the API
248 destination = CGImageDestinationCreateWithData (data, CFSTR("public.tiff"), 1, NULL);
250 CGImageDestinationAddImage (destination, image, NULL);
251 if (!CGImageDestinationFinalize (destination)) {
252 ERROR ("Unable to create image\n");
254 CFRelease (destination);
257 TIFFData = (NSData *)data;
266 nsimage = [[NSImage alloc] initWithData:[self TIFFRepresentation]];
274 @interface WebInternalImage : NSImage <NSCopying>
283 NSColor *patternColor;
284 int patternColorLoadStatus;
286 int repetitionsComplete;
287 BOOL animationFinished;
292 int compositeOperator;
294 CGContextRef context;
295 BOOL needFlushRasterCache;
297 NSImageCacheMode rasterFlushingOldMode;
303 CGImageRef cachedImageRef;
308 NSData *originalData;
311 - (id)initWithMIMEType:(NSString *)MIME;
312 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME;
314 - (void)releasePatternColor;
316 - (NSString *)MIMEType;
319 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c;
320 - (void)resize:(NSSize)s;
321 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr;
322 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)compsiteOperator context:(CGContextRef)context;
323 - (void)stopAnimation;
324 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext;
326 - (void)increaseUseCount;
327 - (void)decreaseUseCount;
328 - (WebImageRenderer *)createRendererIfNeeded;
329 - (void)flushRasterCache;
330 - (CGImageRef)imageRef;
332 + (void)stopAnimationsInView:(NSView *)aView;
333 - (void)resetAnimation;
335 - (void)startAnimationIfNecessary;
336 - (NSGraphicsContext *)_beginRedirectContext:(CGContextRef)aContext;
337 - (void)_endRedirectContext:(NSGraphicsContext *)aContext;
338 - (void)_needsRasterFlush;
339 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(NSCompositingOperation)op alpha:(float)alpha flipped:(BOOL)flipped;
343 extern NSString *NSImageLoopCount;
346 We need to get the AppKit to redirect drawing to our
347 CGContext. There is currently (on Panther) no public
348 way to make this happen. So we create a NSCGSContext
349 subclass and twiddle it's _cgsContext ivar directly.
350 Very fragile, but the only way...
352 @interface NSCGSContext : NSGraphicsContext {
353 CGContextRef _cgsContext;
357 static CGImageRef _createImageRef(NSBitmapImageRep *rep);
359 @interface NSBitmapImageRep (AppKitInternals)
360 - (CGImageRef)_CGImageRef;
363 @interface NSFocusStack : NSObject
366 @interface WebImageContext : NSCGSContext {
368 NSFocusStack* _focusStack;
372 - (id)initWithBounds:(NSRect)b context:(CGContextRef)context;
377 @implementation WebImageContext
379 - (id)initWithBounds:(NSRect)b context:(CGContextRef)context {
386 _cgsContext = CGContextRetain(context);
395 [_focusStack release];
397 CGContextRelease(_cgsContext);
398 _cgsContext = 0; // super dealloc may also release
406 CGContextRelease(_cgsContext);
407 _cgsContext = 0; // super finalize may also release
412 - (void)saveGraphicsState
415 CGContextSaveGState(_cgsContext);
419 - (void)restoreGraphicsState
422 CGContextRestoreGState(_cgsContext);
426 - (BOOL)isDrawingToScreen
433 if (!_focusStack) _focusStack = [[NSFocusStack allocWithZone:NULL] init];
437 - (void)setFocusStack:(void *)stack
439 id oldstack = _focusStack;
440 _focusStack = [(id)stack retain];
456 #define MINIMUM_DURATION (1.0/30.0)
458 @implementation WebInternalImage
460 static NSMutableSet *activeImageRenderers;
462 + (void)stopAnimationsInView:(NSView *)aView
464 NSEnumerator *objectEnumerator = [activeImageRenderers objectEnumerator];
465 WebInternalImage *renderer;
466 NSMutableSet *renderersToStop = [[NSMutableSet alloc] init];
468 while ((renderer = [objectEnumerator nextObject])) {
469 if (renderer->frameView == aView) {
470 [renderersToStop addObject: renderer];
474 objectEnumerator = [renderersToStop objectEnumerator];
475 while ((renderer = [objectEnumerator nextObject])) {
476 if (renderer->frameView == aView) {
477 [renderer stopAnimation];
480 [renderersToStop release];
483 - (id)initWithMIMEType:(NSString *)MIME
487 // Work around issue with flipped images and TIFF by never using the image cache.
488 // See bug 3344259 and related bugs.
489 [self setCacheMode:NSImageCacheNever];
491 loadStatus = NSImageRepLoadStatusUnknownType;
492 MIMEType = [MIME copy];
494 compositeOperator = (int)NSCompositeSourceOver;
500 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
502 WebInternalImage *result = nil;
506 result = [super initWithData:data];
508 // Work around issue with flipped images and TIFF by never using the image cache.
509 // See bug 3344259 and related bugs.
510 [result setCacheMode:NSImageCacheNever];
512 result->loadStatus = NSImageRepLoadStatusUnknownType;
513 result->MIMEType = [MIME copy];
514 result->isNull = [data length] == 0;
515 result->compositeOperator = (int)NSCompositeSourceOver;
527 - (id)initWithContentsOfFile:(NSString *)filename
529 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
530 NSString *imagePath = [bundle pathForResource:filename ofType:@"tiff"];
531 WebInternalImage *result = nil;
535 result = [super initWithContentsOfFile:imagePath];
537 // Work around issue with flipped images and TIFF by never using the image cache.
538 // See bug 3344259 and related bugs.
539 [result setCacheMode:NSImageCacheNever];
541 result->loadStatus = NSImageRepLoadStatusUnknownType;
542 result->compositeOperator = (int)NSCompositeSourceOver;
554 - (void)increaseUseCount
559 - (void)decreaseUseCount
564 - (WebImageRenderer *)createRendererIfNeeded
566 // If an animated image appears multiple times in a given page, we
567 // must create multiple WebCoreImageRenderers so that each copy
568 // animates. However, we don't want to incur the expense of
569 // re-decoding for the very first use on a page, since QPixmap
570 // assignment always calls this method, even when just fetching
571 // the image from the cache for the first time for a page.
572 if (originalData && useCount) {
573 return [[[WebImageRendererFactory sharedFactory] imageRendererWithData:originalData MIMEType:MIMEType] retain];
578 - copyWithZone:(NSZone *)zone
580 // FIXME: If we copy while doing an incremental load, it won't work.
581 WebInternalImage *copy;
583 copy = [super copyWithZone:zone];
584 copy->MIMEType = [MIMEType copy];
585 copy->originalData = [originalData retain];
586 copy->frameTimer = nil;
587 copy->frameView = nil;
588 copy->patternColor = nil;
589 copy->compositeOperator = compositeOperator;
600 - (void)_adjustSizeToPixelDimensions
602 // Force the image to use the pixel size and ignore the dpi.
603 // Ignore any absolute size in the image and always use pixel dimensions.
604 NSBitmapImageRep *imageRep = [[self representations] objectAtIndex:0];
605 NSSize size = NSMakeSize([imageRep pixelsWide], [imageRep pixelsHigh]);
606 [imageRep setSize:size];
608 [self setScalesWhenResized:YES];
612 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
614 NSArray *reps = [self representations];
615 NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
617 if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]]) {
618 NSData *data = [[NSData alloc] initWithBytes:bytes length:length];
621 // Get rep again to avoid bogus compiler warning.
622 NSBitmapImageRep *aRep = [[self representations] objectAtIndex:0];
624 loadStatus = [aRep incrementalLoadFromData:data complete:isComplete];
626 loadStatus = NSImageRepLoadStatusInvalidData; // Arbitrary choice; any error will do.
629 // Hold onto the original data in case we need to copy this image. (Workaround for appkit NSImage
631 if (isComplete && [self frameCount] > 1)
636 switch (loadStatus) {
637 case NSImageRepLoadStatusUnknownType: // not enough data to determine image format. please feed me more data
638 //printf ("NSImageRepLoadStatusUnknownType size %d, isComplete %d\n", length, isComplete);
640 case NSImageRepLoadStatusReadingHeader: // image format known, reading header. not yet valid. more data needed
641 //printf ("NSImageRepLoadStatusReadingHeader size %d, isComplete %d\n", length, isComplete);
643 case NSImageRepLoadStatusWillNeedAllData: // can't read incrementally. will wait for complete data to become avail.
644 //printf ("NSImageRepLoadStatusWillNeedAllData size %d, isComplete %d\n", length, isComplete);
646 case NSImageRepLoadStatusInvalidData: // image decompression encountered error.
647 //printf ("NSImageRepLoadStatusInvalidData size %d, isComplete %d\n", length, isComplete);
649 case NSImageRepLoadStatusUnexpectedEOF: // ran out of data before full image was decompressed.
650 //printf ("NSImageRepLoadStatusUnexpectedEOF size %d, isComplete %d\n", length, isComplete);
652 case NSImageRepLoadStatusCompleted: // all is well, the full pixelsHigh image is valid.
653 //printf ("NSImageRepLoadStatusCompleted size %d, isComplete %d\n", length, isComplete);
654 [self _adjustSizeToPixelDimensions];
658 [self _adjustSizeToPixelDimensions];
659 //printf ("incrementalLoadWithBytes: size %d, isComplete %d\n", length, isComplete);
660 // We have some valid data. Return YES so we can attempt to draw what we've got.
667 originalData = [[NSData alloc] initWithBytes:bytes length:length];
668 if ([MIMEType isEqual:@"application/pdf"]) {
669 Class repClass = [NSImageRep imageRepClassForData:originalData];
671 NSImageRep *rep = [[repClass alloc] initWithData:originalData];
672 [self addRepresentation:rep];
684 ASSERT(frameTimer == nil);
685 ASSERT(frameView == nil);
686 [patternColor release];
688 [originalData release];
691 CGContextRelease(context);
695 if (cachedImageRef) {
696 CGImageRelease (cachedImageRef);
707 ASSERT(frameTimer == nil);
708 ASSERT(frameView == nil);
711 CGContextRelease(context);
714 if (cachedImageRef) {
715 CGImageRelease (cachedImageRef);
722 - (id)firstRepProperty:(NSString *)propertyName
724 id firstRep = [[self representations] objectAtIndex:0];
726 if ([firstRep respondsToSelector:@selector(valueForProperty:)])
727 property = [firstRep valueForProperty:propertyName];
733 id property = [self firstRepProperty:NSImageFrameCount];
734 return property ? [property intValue] : 1;
739 id property = [self firstRepProperty:NSImageCurrentFrame];
740 return property ? [property intValue] : 1;
743 - (void)setCurrentFrame:(int)frame
745 NSBitmapImageRep *imageRep = [[self representations] objectAtIndex:0];
746 [imageRep setProperty:NSImageCurrentFrame withValue:[NSNumber numberWithInt:frame]];
749 - (float)unadjustedFrameDuration
751 id property = [self firstRepProperty:NSImageCurrentFrameDuration];
752 return property ? [property floatValue] : 0.0;
755 - (float)frameDuration
757 float duration = [self unadjustedFrameDuration];
758 if (duration < MINIMUM_DURATION) {
760 Many annoying ads specify a 0 duration to make an image flash
761 as quickly as possible. However a zero duration is faster than
762 the refresh rate. We need to pick a minimum duration.
764 Browsers handle the minimum time case differently. IE seems to use something
765 close to 1/30th of a second. Konqueror uses 0. The ImageMagick library
766 uses 1/100th. The units in the GIF specification are 1/100th of second.
767 We will use 1/30th of second as the minimum time.
769 duration = MINIMUM_DURATION;
774 - (int)repetitionCount
776 id property = [self firstRepProperty:NSImageLoopCount];
777 int count = property ? [property intValue] : 0;
781 - (void)scheduleFrame
783 if (frameTimer && [frameTimer isValid])
785 frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self frameDuration]
787 selector:@selector(nextFrame:)
792 - (NSGraphicsContext *)_beginRedirectContext:(CGContextRef)aContext
794 NSGraphicsContext *oldContext = 0;
796 oldContext = [NSGraphicsContext currentContext];
797 // Assumes that we are redirecting to a CGBitmapContext.
798 size_t w = CGBitmapContextGetWidth (aContext);
799 size_t h = CGBitmapContextGetHeight (aContext);
800 redirectContext = [[WebImageContext alloc] initWithBounds:NSMakeRect(0, 0, (float)w, (float)h) context:aContext];
801 [NSGraphicsContext setCurrentContext:redirectContext];
806 - (void)_endRedirectContext:(NSGraphicsContext *)aContext
809 [NSGraphicsContext setCurrentContext:aContext];
810 [redirectContext autorelease];
815 - (void)_needsRasterFlush
818 if (needFlushRasterCache && [MIMEType isEqual: @"application/pdf"]) {
819 // FIXME: At this point we need to flush the cached rasterized PDF.
824 - (void)_adjustColorSpace
826 #if COLORMATCH_EVERYTHING
827 NSArray *reps = [self representations];
828 NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
829 if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]] &&
830 [imageRep valueForProperty:NSImageColorSyncProfileData] == nil &&
831 [[imageRep colorSpaceName] isEqualToString:NSDeviceRGBColorSpace]) {
832 [imageRep setColorSpaceName:NSCalibratedRGBColorSpace];
835 NSArray *reps = [self representations];
836 NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
837 if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]] &&
838 [imageRep valueForProperty:NSImageColorSyncProfileData] == nil &&
839 [[imageRep colorSpaceName] isEqualToString:NSCalibratedRGBColorSpace]) {
840 [imageRep setColorSpaceName:NSDeviceRGBColorSpace];
846 - (void)drawClippedToValidInRect:(NSRect)ir fromRect:(NSRect)fr
848 [self _adjustColorSpace];
850 if (loadStatus >= 0) {
851 // The last line might be a partial line, so the number of complete lines is the number
852 // we get from NSImage minus one.
853 int numCompleteLines = loadStatus - 1;
854 if (numCompleteLines <= 0) {
857 int pixelsHigh = [[[self representations] objectAtIndex:0] pixelsHigh];
858 if (pixelsHigh > numCompleteLines) {
859 // Figure out how much of the image is OK to draw. We can't simply
860 // use numCompleteLines because the image may be scaled.
861 float clippedImageHeight = floor([self size].height * numCompleteLines / pixelsHigh);
863 // Figure out how much of the source is OK to draw from.
864 float clippedSourceHeight = clippedImageHeight - fr.origin.y;
865 if (clippedSourceHeight < 1) {
869 // Figure out how much of the destination we are going to draw to.
870 float clippedDestinationHeight = ir.size.height * clippedSourceHeight / fr.size.height;
872 // Reduce heights of both rectangles without changing their positions.
873 // In the flipped case, just adjusting the height is sufficient.
874 ASSERT([self isFlipped]);
875 ASSERT([[NSView focusView] isFlipped]);
876 ir.size.height = clippedDestinationHeight;
877 fr.size.height = clippedSourceHeight;
882 NSGraphicsContext *oldContext = [self _beginRedirectContext:context];
883 [self _needsRasterFlush];
885 // If we have PDF then draw the PDF ourselves, bypassing the NSImage caching mechanisms,
886 // but only do this when we're rendering to an offscreen context. NSImage will always
887 // cache the PDF image at it's native resolution, thus, causing artifacts when the image
888 // is drawn into a scaled or rotated context.
889 if ([MIMEType isEqual:@"application/pdf"])
890 [self _PDFDrawFromRect:fr toRect:ir operation:compositeOperator alpha:1.0 flipped:YES];
892 [self drawInRect:ir fromRect:fr operation:compositeOperator fraction: 1.0];
894 [self _endRedirectContext:oldContext];
897 [self drawInRect:ir fromRect:fr operation:compositeOperator fraction: 1.0];
901 - (CGPDFDocumentRef)_PDFDocumentRef
904 _PDFDoc = [[WebPDFDocument alloc] initWithData:originalData];
907 return [_PDFDoc documentRef];
912 CGPDFDocumentRef document = [self _PDFDocumentRef];
913 if (document != NULL) {
914 CGContextRef _context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
915 CGRect mediaBox = [_PDFDoc mediaBox];
917 CGContextSaveGState(_context);
918 // Rotate translate image into position according to doc properties.
919 [_PDFDoc adjustCTM:_context];
921 // Media box may have non-zero origin which we ignore. CGPDFDocumentShowPage pages start
923 CGContextDrawPDFDocument(_context, CGRectMake(0, 0, mediaBox.size.width, mediaBox.size.height), document, 1);
925 CGContextRestoreGState(_context);
929 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(NSCompositingOperation)op alpha:(float)alpha flipped:(BOOL)flipped
931 // FIXME: The rasterized PDF should be drawn into a cache, and the raster then composited.
933 CGContextRef _context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
934 float hScale, vScale;
936 CGContextSaveGState(_context);
938 CGContextSetCompositeOperation (_context, op);
940 // Scale and translate so the document is rendered in the correct location.
941 hScale = dstRect.size.width / srcRect.size.width;
942 vScale = dstRect.size.height / srcRect.size.height;
944 CGContextTranslateCTM (_context, dstRect.origin.x - srcRect.origin.x * hScale, dstRect.origin.y - srcRect.origin.y * vScale);
945 CGContextScaleCTM (_context, hScale, vScale);
947 // Reverse if flipped image.
949 CGContextScaleCTM(_context, 1, -1);
950 CGContextTranslateCTM (_context, 0, -(dstRect.origin.y + dstRect.size.height));
953 // Clip to destination in case we are imaging part of the source only
954 CGContextClipToRect(_context, CGRectIntegral(*(CGRect*)&srcRect));
959 // done with our fancy transforms
960 CGContextRestoreGState(_context);
965 - (void)resetAnimation
967 [self stopAnimation];
968 [self setCurrentFrame:0];
969 repetitionsComplete = 0;
970 animationFinished = NO;
973 - (void)nextFrame:(id)context
977 // Release the timer that just fired.
978 [frameTimer release];
981 currentFrame = [self currentFrame] + 1;
982 if (currentFrame >= [self frameCount]) {
983 repetitionsComplete += 1;
984 if ([self repetitionCount] && repetitionsComplete >= [self repetitionCount]) {
985 animationFinished = YES;
990 [self setCurrentFrame:currentFrame];
992 // Release the tiling pattern so next frame will update the pattern if we're tiling.
993 [patternColor release];
996 [frameView setNeedsDisplayInRect:targetRect];
999 // Will be called when the containing view is displayed by WebCore RenderImage (via QPainter).
1000 // If the image is an animated image it will begin animating. If the image is already animating,
1001 // it's frame will have been advanced by nextFrame:.
1003 // Also used to draw the image by WebImageView.
1004 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
1007 [self tileInRect:ir fromPoint:tilePoint context:context];
1010 [self drawClippedToValidInRect:ir fromRect:fr];
1013 [self startAnimationIfNecessary];
1017 - (void)flushRasterCache
1019 needFlushRasterCache = YES;
1022 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)operator context:(CGContextRef)aContext
1024 compositeOperator = operator;
1026 if (aContext != context) {
1028 CGContextRetain(aContext);
1031 CGContextRelease(context);
1036 [self drawImageInRect:ir fromRect:fr];
1039 - (void)startAnimationIfNecessary
1041 if ([self frameCount] > 1 && !animationFinished) {
1042 NSView *newView = [NSView focusView];
1043 if (newView != frameView){
1044 [frameView release];
1045 frameView = [newView retain];
1047 [self scheduleFrame];
1048 if (!activeImageRenderers) {
1049 activeImageRenderers = [[NSMutableSet alloc] init];
1051 [activeImageRenderers addObject:self];
1055 - (void)stopAnimation
1057 [frameTimer invalidate];
1058 [frameTimer release];
1061 [frameView release];
1064 [activeImageRenderers removeObject:self];
1067 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext
1069 // These calculations are only correct for the flipped case.
1070 ASSERT([self isFlipped]);
1071 ASSERT([[NSView focusView] isFlipped]);
1073 [self _adjustColorSpace];
1075 NSSize size = [self size];
1077 // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
1079 oneTileRect.origin.x = rect.origin.x + fmodf(fmodf(-point.x, size.width) - size.width, size.width);
1080 oneTileRect.origin.y = rect.origin.y + fmodf(fmodf(-point.y, size.height) - size.height, size.height);
1081 // I think this is a simpler way to say the same thing. Also, if either point.x or point.y is negative, both
1082 // methods would end up with the wrong answer. For example, fmod(-22,5) is -2, which is the correct delta to
1083 // the start of the pattern, but fmod(-(-23), 5) is 3. This is the delta to the *end* of the pattern
1084 // instead of the start, so onTileRect will be too far right.
1085 // oneTileRect.origin.x = rect.origin.x - fmodf(point.x, size.width);
1086 // oneTileRect.origin.y = rect.origin.y - fmodf(point.y, size.height);
1087 oneTileRect.size = size;
1089 // Compute the appropriate phase relative to the top level view in the window.
1090 // Conveniently, the oneTileRect we computed above has the appropriate origin.
1091 NSPoint originInWindow = [[NSView focusView] convertPoint:oneTileRect.origin toView:nil];
1092 // WebCore may translate the focus, and thus need an extra phase correction
1093 NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
1094 originInWindow.x += extraPhase.x;
1095 originInWindow.y += extraPhase.y;
1096 CGSize phase = CGSizeMake(fmodf(originInWindow.x, size.width), fmodf(originInWindow.y, size.height));
1098 // If the single image draw covers the whole area, then just draw once.
1099 if (NSContainsRect(oneTileRect, rect)) {
1102 fromRect.origin.x = rect.origin.x - oneTileRect.origin.x;
1103 fromRect.origin.y = rect.origin.y - oneTileRect.origin.y;
1104 fromRect.size = rect.size;
1106 [self drawClippedToValidInRect:rect fromRect:fromRect];
1110 // If we only have a partial image, just do nothing, because CoreGraphics will not help us tile
1111 // with a partial image. But maybe later we can fix this by constructing a pattern image that's
1112 // transparent where needed.
1113 if (loadStatus != NSImageRepLoadStatusCompleted) {
1117 // Since we need to tile, construct an NSColor so we can get CoreGraphics to do it for us.
1118 if (patternColorLoadStatus != loadStatus) {
1119 [patternColor release];
1122 if (patternColor == nil) {
1123 patternColor = [[NSColor colorWithPatternImage:self] retain];
1124 patternColorLoadStatus = loadStatus;
1127 NSGraphicsContext *oldContext = [self _beginRedirectContext:context];
1128 [self _needsRasterFlush];
1130 [NSGraphicsContext saveGraphicsState];
1132 CGContextSetPatternPhase((CGContextRef)[[NSGraphicsContext currentContext] graphicsPort], phase);
1134 [NSBezierPath fillRect:rect];
1136 [NSGraphicsContext restoreGraphicsState];
1138 [self _endRedirectContext:oldContext];
1143 [self startAnimationIfNecessary];
1146 - (void)resize:(NSSize)s
1148 [self setScalesWhenResized:YES];
1152 - (NSString *)MIMEType
1157 // Try hard to get a CGImageRef. First try to snag one from the
1158 // NSBitmapImageRep, then try to create one. In some cases we may
1159 // not be able to create one.
1160 - (CGImageRef)imageRef
1165 return cachedImageRef;
1167 if ([[self representations] count] > 0) {
1168 NSBitmapImageRep *rep = [[self representations] objectAtIndex:0];
1170 if ([rep respondsToSelector:@selector(_CGImageRef)])
1171 ref = [rep _CGImageRef];
1174 cachedImageRef = _createImageRef(rep);
1175 ref = cachedImageRef;
1181 - (void)releasePatternColor
1183 [patternColor release];
1189 @implementation WebImageRenderer
1191 - (id)initWithMIMEType:(NSString *)MIME
1193 WebInternalImage *i = [[WebInternalImage alloc] initWithMIMEType:MIME];
1203 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
1205 WebInternalImage *i = [[WebInternalImage alloc] initWithData:data MIMEType:MIME];
1215 - (id)initWithContentsOfFile:(NSString *)filename
1217 WebInternalImage *i = [[WebInternalImage alloc] initWithContentsOfFile:filename];
1229 [image releasePatternColor];
1239 - (NSString *)MIMEType
1241 return [image MIMEType];
1244 - (NSData *)TIFFRepresentation
1246 return [image TIFFRepresentation];
1251 return [image frameCount];
1254 - (void)setOriginalData:(NSData *)data
1256 NSData *oldData = image->originalData;
1257 image->originalData = [data retain];
1261 + (void)stopAnimationsInView:(NSView *)aView
1263 [WebInternalImage stopAnimationsInView:aView];
1266 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
1268 return [image incrementalLoadWithBytes:bytes length:length complete:isComplete callback:c];
1276 return [image size];
1279 - (void)resize:(NSSize)s
1284 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
1286 [image drawImageInRect:ir fromRect:fr];
1289 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)compsiteOperator context:(CGContextRef)context
1291 [image drawImageInRect:ir fromRect:fr compositeOperator:compsiteOperator context:context];
1294 - (void)resetAnimation
1296 [image resetAnimation];
1299 - (void)stopAnimation
1301 [image stopAnimation];
1304 - (void)tileInRect:(NSRect)r fromPoint:(NSPoint)p context:(CGContextRef)context
1306 [image tileInRect:r fromPoint:p context:context];
1311 return image == nil || [image isNull];
1314 - (id <WebCoreImageRenderer>)retainOrCopyIfNeeded
1316 WebImageRenderer *newRenderer = [image createRendererIfNeeded];
1324 - (void)increaseUseCount
1326 [image increaseUseCount];
1329 - (void)decreaseUseCount
1331 [image decreaseUseCount];
1334 - (void)flushRasterCache
1336 [image flushRasterCache];
1339 - (CGImageRef)imageRef
1341 return [image imageRef];
1344 - (id)copyWithZone:(NSZone *)zone
1346 WebImageRenderer *copy = [[WebImageRenderer alloc] init];
1347 copy->image = [image copy];
1353 static CGImageRef _createImageRef(NSBitmapImageRep *rep) {
1354 BOOL isPlanar = [rep isPlanar];
1358 const unsigned char *bitmapData = [rep bitmapData];
1359 int pixelsWide = [rep pixelsWide];
1360 int pixelsHigh = [rep pixelsHigh];
1361 int bitsPerSample = [rep bitsPerSample];
1362 int bitsPerPixel = [rep bitsPerPixel];
1363 int bytesPerRow = [rep bytesPerRow];
1364 BOOL hasAlpha = [rep hasAlpha];
1366 CGDataProviderRef dataProvider;
1368 CGColorSpaceRef colorSpace = WebCGColorSpaceCreateRGB();
1369 dataProvider = CGDataProviderCreateWithData(NULL, bitmapData, pixelsHigh * bytesPerRow, NULL);
1371 image = CGImageCreate(pixelsWide, pixelsHigh, bitsPerSample, bitsPerPixel, bytesPerRow, colorSpace,
1372 hasAlpha ? kCGImageAlphaPremultipliedLast : kCGImageAlphaNone,
1373 dataProvider, NULL, false /*shouldInterpolate*/, kCGRenderingIntentDefault);
1375 CGDataProviderRelease(dataProvider);
1376 CGColorSpaceRelease(colorSpace);
1383 //------------------------------------------------------------------------------------
1385 @implementation WebPDFDocument
1387 static void ReleasePDFDocumentData(void *info, const void *data, size_t size) {
1388 [(NSData*)info autorelease];
1391 - (id) initWithData:(NSData*)data
1393 self = [super init];
1397 CGDataProviderRef dataProvider = CGDataProviderCreateWithData([data retain], [data bytes], [data length], ReleasePDFDocumentData);
1398 _document = CGPDFDocumentCreateWithProvider(dataProvider);
1399 CGDataProviderRelease(dataProvider);
1402 [self setCurrentPage:0];
1409 if (_document != NULL) CGPDFDocumentRelease(_document);
1415 if (_document != NULL) CGPDFDocumentRelease(_document);
1419 - (CGPDFDocumentRef) documentRef
1433 // rotate the media box and calculate bounding box
1434 float sina = sin (_rotation);
1435 float cosa = cos (_rotation);
1436 float width = NSWidth (_cropBox);
1437 float height = NSHeight(_cropBox);
1439 // calculate rotated x and y axis
1440 NSPoint rx = NSMakePoint( width * cosa, width * sina);
1441 NSPoint ry = NSMakePoint(-height * sina, height * cosa);
1443 // find delta width and height of rotated points
1444 rotatedRect.origin = _cropBox.origin;
1445 rotatedRect.size.width = ceil(fabs(rx.x - ry.x));
1446 rotatedRect.size.height = ceil(fabs(ry.y - rx.y));
1451 - (void) adjustCTM:(CGContextRef)context
1453 // rotate the crop box and calculate bounding box
1454 float sina = sin (-_rotation);
1455 float cosa = cos (-_rotation);
1456 float width = NSWidth (_cropBox);
1457 float height = NSHeight(_cropBox);
1459 // calculate rotated x and y edges of the corp box. if they're negative, it means part of the image has
1460 // been rotated outside of the bounds and we need to shift over the image so it lies inside the bounds again
1461 NSPoint rx = NSMakePoint( width * cosa, width * sina);
1462 NSPoint ry = NSMakePoint(-height * sina, height * cosa);
1464 // adjust so we are at the crop box origin
1465 CGContextTranslateCTM(context, floor(-MIN(0,MIN(rx.x, ry.x))), floor(-MIN(0,MIN(rx.y, ry.y))));
1467 // rotate -ve to remove rotation
1468 CGContextRotateCTM(context, -_rotation);
1470 // shift so we are completely within media box
1471 CGContextTranslateCTM(context, _mediaBox.origin.x - _cropBox.origin.x, _mediaBox.origin.y - _cropBox.origin.y);
1474 - (void) setCurrentPage:(int)page
1476 if (page != _currentPage && page >= 0 && page < [self pageCount]) {
1480 _currentPage = page;
1482 // get media box (guaranteed)
1483 _mediaBox = CGPDFDocumentGetMediaBox(_document, page + 1);
1485 // get crop box (not always there). if not, use _mediaBox
1486 r = CGPDFDocumentGetCropBox(_document, page + 1);
1487 if (!CGRectIsEmpty(r)) {
1488 _cropBox = NSMakeRect(r.origin.x, r.origin.y, r.size.width, r.size.height);
1490 _cropBox = NSMakeRect(_mediaBox.origin.x, _mediaBox.origin.y, _mediaBox.size.width, _mediaBox.size.height);
1493 // get page rotation angle
1494 _rotation = CGPDFDocumentGetRotationAngle(_document, page + 1) * M_PI / 180.0; // to radians
1500 return _currentPage;
1505 return CGPDFDocumentGetNumberOfPages(_document);
1510 CGColorSpaceRef WebCGColorSpaceCreateRGB(void)
1512 #ifdef COLORMATCH_EVERYTHING
1513 #if BUILDING_ON_PANTHER
1514 return CGColorSpaceCreateWithName(kCGColorSpaceUserRGB);
1515 #else // !BUILDING_ON_PANTHER
1516 return CGColorSpaceCreateDeviceRGB();
1517 #endif // BUILDING_ON_PANTHER
1518 #else // !COLORMATCH_EVERYTHING
1519 #if BUILDING_ONPANTHER
1520 return CGColorSpaceCreateDeviceRGB();
1521 #else // !BUILDING_ON_PANTHER
1522 return CGColorSpaceCreateDisplayRGB();
1523 #endif // BUILDING_ON_PANTHER
1527 CGColorSpaceRef WebCGColorSpaceCreateGray(void)
1529 #ifdef COLORMATCH_EVERYTHING
1530 #if BUILDING_ON_PANTHER
1531 return CGColorSpaceCreateWithName(kCGColorSpaceUserGray);
1532 #else // !BUILDING_ON_PANTHER
1533 return CGColorSpaceCreateDeviceGray();
1534 #endif // BUILDING_ON_PANTHER
1535 #else // !COLORMATCH_EVERYTHING
1536 #if BUILDING_ONPANTHER
1537 return CGColorSpaceCreateDeviceGray();
1538 #else // !BUILDING_ON_PANTHER
1539 return CGColorSpaceCreateDisplayGray();
1540 #endif // BUILDING_ON_PANTHER
1544 CGColorSpaceRef WebCGColorSpaceCreateCMYK(void)
1546 #ifdef COLORMATCH_EVERYTHING
1547 #if BUILDING_ON_PANTHER
1548 return CGColorSpaceCreateWithName(kCGColorSpaceUserCMYK);
1549 #else // !BUILDING_ON_PANTHER
1550 return CGColorSpaceCreateDeviceCMYK();
1551 #endif // BUILDING_ON_PANTHER
1552 #else // !COLORMATCH_EVERYTHING
1553 #if BUILDING_ONPANTHER
1554 return CGColorSpaceCreateDeviceCMYK();
1555 #else // !BUILDING_ON_PANTHER
1556 // FIXME: no device CMYK
1557 return CGColorSpaceCreateDeviceCMYK();
1558 #endif // BUILDING_ON_PANTHER