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>
21 #import <WebKit/WebImageData.h>
23 // Forward declarations of internal methods.
24 @interface WebImageRenderer (WebInternal)
25 - (void)_startOrContinueAnimationIfNecessary;
28 @implementation WebImageRenderer
30 - (id)initWithMIMEType:(NSString *)MIME
34 MIMEType = [MIME copy];
39 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
43 MIMEType = [MIME copy];
44 imageData = [[WebImageData alloc] init];
45 [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES callback:0];
50 - (id)initWithContentsOfFile:(NSString *)filename
54 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
55 NSString *imagePath = [bundle pathForResource:filename ofType:@"tiff"];
57 imageData = [[WebImageData alloc] init];
58 NSData *data = [NSData dataWithContentsOfFile:imagePath];
59 [imageData incrementalLoadWithBytes:[data bytes] length:[data length] complete:YES callback:0];
74 - copyWithZone:(NSZone *)zone
76 WebImageRenderer *copy;
78 copy = [[WebImageRenderer alloc] init];
79 copy->MIMEType = [MIMEType copy];
80 copy->adjustedSize = adjustedSize;
81 copy->isSizeAdjusted = isSizeAdjusted;
82 copy->imageData = [imageData retain];
87 - (id <WebCoreImageRenderer>)retainOrCopyIfNeeded
89 return [self copyWithZone:0];
92 - (void)resize:(NSSize)s
106 CGSize sz = [imageData size];
107 return NSMakeSize(sz.width, sz.height);
110 - (NSString *)MIMEType
117 return [imageData numberOfImages];
123 return [imageData isNull];
126 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
129 imageData = [[WebImageData alloc] init];
130 if ([MIMEType isEqual:@"application/pdf"]) {
131 [imageData setIsPDF:YES];
134 return [imageData incrementalLoadWithBytes:bytes length:length complete:isComplete callback:c];
137 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
139 CGContextRef aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
140 NSCompositingOperation op = NSCompositeSourceOver;
142 [self drawImageInRect:ir fromRect:fr compositeOperator:op context:aContext];
145 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)operator context:(CGContextRef)aContext
148 aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
150 NSCompositingOperation op = (NSCompositingOperation)operator;
152 if (isSizeAdjusted) {
153 [imageData drawImageAtIndex:[imageData currentFrame] inRect:CGRectMake(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height)
154 fromRect:CGRectMake(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height)
155 adjustedSize:CGSizeMake(adjustedSize.width, adjustedSize.height)
156 compositeOperation:op context:aContext];
159 [imageData drawImageAtIndex:[imageData currentFrame] inRect:CGRectMake(ir.origin.x, ir.origin.y, ir.size.width, ir.size.height)
160 fromRect:CGRectMake(fr.origin.x, fr.origin.y, fr.size.width, fr.size.height)
161 compositeOperation:op context:aContext];
164 targetAnimationRect = ir;
165 [self _startOrContinueAnimationIfNecessary];
168 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext
171 aContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
173 [imageData tileInRect:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)
174 fromPoint:CGPointMake(point.x, point.y) context:aContext];
176 targetAnimationRect = rect;
177 [self _startOrContinueAnimationIfNecessary];
180 - (void)_startOrContinueAnimationIfNecessary
182 NSView *targetView = [NSView focusView];
184 // Only animate if we're drawing into a WebHTMLView or WebImageView. This fixes problems
185 // like <rdar://problem/3966973>, which describes a third party application that renders thumbnails of
186 // the page into a alternate view.
187 if (([targetView isKindOfClass:[WebHTMLView class]] || [targetView isKindOfClass:[WebImageView class]])
188 && [imageData shouldAnimate] && [MIMEType isEqual:@"image/gif"]) {
189 [imageData addAnimatingRenderer:self inView:targetView];
194 + (void)stopAnimationsInView:(NSView *)aView
196 [WebImageData stopAnimationsInView:aView];
199 - (void)resetAnimation
201 [imageData resetAnimation];
205 - (void)stopAnimation
207 [imageData removeAnimatingRenderer:self];
210 - (NSRect)targetAnimationRect
212 return targetAnimationRect;
215 - (void)increaseUseCount
219 - (void)decreaseUseCount
223 - (void)flushRasterCache
227 - (CGImageRef)imageRef
229 return [imageData imageAtIndex:0];
232 - (NSData *)TIFFRepresentation
235 CGImageRef image = [imageData imageAtIndex:0];
239 CFMutableDataRef data = 0;
240 CGImageDestinationRef destination = 0;
242 data = CFDataCreateMutable(NULL, 0);
243 // FIXME: Use type kCGImageTypeIdentifierTIFF constant once is becomes available in the API
244 destination = CGImageDestinationCreateWithData (data, CFSTR("public.tiff"), 1, NULL);
246 CGImageDestinationAddImage (destination, image, NULL);
247 if (!CGImageDestinationFinalize (destination)) {
248 ERROR ("Unable to create image\n");
250 CFRelease (destination);
253 TIFFData = (NSData *)data;
262 nsimage = [[NSImage alloc] initWithData:[self TIFFRepresentation]];
270 @interface WebInternalImage : NSImage <NSCopying>
279 NSColor *patternColor;
280 int patternColorLoadStatus;
282 int repetitionsComplete;
283 BOOL animationFinished;
288 int compositeOperator;
290 CGContextRef context;
291 BOOL needFlushRasterCache;
293 NSImageCacheMode rasterFlushingOldMode;
299 CGImageRef cachedImageRef;
304 NSData *originalData;
307 - (id)initWithMIMEType:(NSString *)MIME;
308 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME;
310 - (void)releasePatternColor;
312 - (NSString *)MIMEType;
315 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c;
316 - (void)resize:(NSSize)s;
317 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr;
318 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)compsiteOperator context:(CGContextRef)context;
319 - (void)stopAnimation;
320 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext;
322 - (void)increaseUseCount;
323 - (void)decreaseUseCount;
324 - (WebImageRenderer *)createRendererIfNeeded;
325 - (void)flushRasterCache;
326 - (CGImageRef)imageRef;
328 + (void)stopAnimationsInView:(NSView *)aView;
329 - (void)resetAnimation;
331 - (void)startAnimationIfNecessary;
332 - (NSGraphicsContext *)_beginRedirectContext:(CGContextRef)aContext;
333 - (void)_endRedirectContext:(NSGraphicsContext *)aContext;
334 - (void)_needsRasterFlush;
335 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(NSCompositingOperation)op alpha:(float)alpha flipped:(BOOL)flipped;
339 extern NSString *NSImageLoopCount;
342 We need to get the AppKit to redirect drawing to our
343 CGContext. There is currently (on Panther) no public
344 way to make this happen. So we create a NSCGSContext
345 subclass and twiddle it's _cgsContext ivar directly.
346 Very fragile, but the only way...
348 @interface NSCGSContext : NSGraphicsContext {
349 CGContextRef _cgsContext;
353 static CGImageRef _createImageRef(NSBitmapImageRep *rep);
355 @interface NSBitmapImageRep (AppKitInternals)
356 - (CGImageRef)_CGImageRef;
359 @interface NSFocusStack : NSObject
362 @interface WebImageContext : NSCGSContext {
364 NSFocusStack* _focusStack;
368 - (id)initWithBounds:(NSRect)b context:(CGContextRef)context;
373 @implementation WebImageContext
375 - (id)initWithBounds:(NSRect)b context:(CGContextRef)context {
382 _cgsContext = CGContextRetain(context);
391 [_focusStack release];
393 CGContextRelease(_cgsContext);
394 _cgsContext = 0; // super dealloc may also release
402 CGContextRelease(_cgsContext);
403 _cgsContext = 0; // super finalize may also release
408 - (void)saveGraphicsState
411 CGContextSaveGState(_cgsContext);
415 - (void)restoreGraphicsState
418 CGContextRestoreGState(_cgsContext);
422 - (BOOL)isDrawingToScreen
429 if (!_focusStack) _focusStack = [[NSFocusStack allocWithZone:NULL] init];
433 - (void)setFocusStack:(void *)stack
435 id oldstack = _focusStack;
436 _focusStack = [(id)stack retain];
452 #define MINIMUM_DURATION (1.0/30.0)
454 @implementation WebInternalImage
456 static NSMutableSet *activeImageRenderers;
458 + (void)stopAnimationsInView:(NSView *)aView
460 NSEnumerator *objectEnumerator = [activeImageRenderers objectEnumerator];
461 WebInternalImage *renderer;
462 NSMutableSet *renderersToStop = [[NSMutableSet alloc] init];
464 while ((renderer = [objectEnumerator nextObject])) {
465 if (renderer->frameView == aView) {
466 [renderersToStop addObject: renderer];
470 objectEnumerator = [renderersToStop objectEnumerator];
471 while ((renderer = [objectEnumerator nextObject])) {
472 if (renderer->frameView == aView) {
473 [renderer stopAnimation];
476 [renderersToStop release];
479 - (id)initWithMIMEType:(NSString *)MIME
483 // Work around issue with flipped images and TIFF by never using the image cache.
484 // See bug 3344259 and related bugs.
485 [self setCacheMode:NSImageCacheNever];
487 loadStatus = NSImageRepLoadStatusUnknownType;
488 MIMEType = [MIME copy];
490 compositeOperator = (int)NSCompositeSourceOver;
496 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
498 WebInternalImage *result = nil;
502 result = [super initWithData:data];
504 // Work around issue with flipped images and TIFF by never using the image cache.
505 // See bug 3344259 and related bugs.
506 [result setCacheMode:NSImageCacheNever];
508 result->loadStatus = NSImageRepLoadStatusUnknownType;
509 result->MIMEType = [MIME copy];
510 result->isNull = [data length] == 0;
511 result->compositeOperator = (int)NSCompositeSourceOver;
523 - (id)initWithContentsOfFile:(NSString *)filename
525 NSBundle *bundle = [NSBundle bundleForClass:[self class]];
526 NSString *imagePath = [bundle pathForResource:filename ofType:@"tiff"];
527 WebInternalImage *result = nil;
531 result = [super initWithContentsOfFile:imagePath];
533 // Work around issue with flipped images and TIFF by never using the image cache.
534 // See bug 3344259 and related bugs.
535 [result setCacheMode:NSImageCacheNever];
537 result->loadStatus = NSImageRepLoadStatusUnknownType;
538 result->compositeOperator = (int)NSCompositeSourceOver;
550 - (void)increaseUseCount
555 - (void)decreaseUseCount
560 - (WebImageRenderer *)createRendererIfNeeded
562 // If an animated image appears multiple times in a given page, we
563 // must create multiple WebCoreImageRenderers so that each copy
564 // animates. However, we don't want to incur the expense of
565 // re-decoding for the very first use on a page, since QPixmap
566 // assignment always calls this method, even when just fetching
567 // the image from the cache for the first time for a page.
568 if (originalData && useCount) {
569 return [[[WebImageRendererFactory sharedFactory] imageRendererWithData:originalData MIMEType:MIMEType] retain];
574 - copyWithZone:(NSZone *)zone
576 // FIXME: If we copy while doing an incremental load, it won't work.
577 WebInternalImage *copy;
579 copy = [super copyWithZone:zone];
580 copy->MIMEType = [MIMEType copy];
581 copy->originalData = [originalData retain];
582 copy->frameTimer = nil;
583 copy->frameView = nil;
584 copy->patternColor = nil;
585 copy->compositeOperator = compositeOperator;
596 - (void)_adjustSizeToPixelDimensions
598 // Force the image to use the pixel size and ignore the dpi.
599 // Ignore any absolute size in the image and always use pixel dimensions.
600 NSBitmapImageRep *imageRep = [[self representations] objectAtIndex:0];
601 NSSize size = NSMakeSize([imageRep pixelsWide], [imageRep pixelsHigh]);
602 [imageRep setSize:size];
604 [self setScalesWhenResized:YES];
608 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
610 NSArray *reps = [self representations];
611 NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
613 if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]]) {
614 NSData *data = [[NSData alloc] initWithBytes:bytes length:length];
617 // Get rep again to avoid bogus compiler warning.
618 NSBitmapImageRep *aRep = [[self representations] objectAtIndex:0];
620 loadStatus = [aRep incrementalLoadFromData:data complete:isComplete];
622 loadStatus = NSImageRepLoadStatusInvalidData; // Arbitrary choice; any error will do.
625 // Hold onto the original data in case we need to copy this image. (Workaround for appkit NSImage
627 if (isComplete && [self frameCount] > 1)
632 switch (loadStatus) {
633 case NSImageRepLoadStatusUnknownType: // not enough data to determine image format. please feed me more data
634 //printf ("NSImageRepLoadStatusUnknownType size %d, isComplete %d\n", length, isComplete);
636 case NSImageRepLoadStatusReadingHeader: // image format known, reading header. not yet valid. more data needed
637 //printf ("NSImageRepLoadStatusReadingHeader size %d, isComplete %d\n", length, isComplete);
639 case NSImageRepLoadStatusWillNeedAllData: // can't read incrementally. will wait for complete data to become avail.
640 //printf ("NSImageRepLoadStatusWillNeedAllData size %d, isComplete %d\n", length, isComplete);
642 case NSImageRepLoadStatusInvalidData: // image decompression encountered error.
643 //printf ("NSImageRepLoadStatusInvalidData size %d, isComplete %d\n", length, isComplete);
645 case NSImageRepLoadStatusUnexpectedEOF: // ran out of data before full image was decompressed.
646 //printf ("NSImageRepLoadStatusUnexpectedEOF size %d, isComplete %d\n", length, isComplete);
648 case NSImageRepLoadStatusCompleted: // all is well, the full pixelsHigh image is valid.
649 //printf ("NSImageRepLoadStatusCompleted size %d, isComplete %d\n", length, isComplete);
650 [self _adjustSizeToPixelDimensions];
654 [self _adjustSizeToPixelDimensions];
655 //printf ("incrementalLoadWithBytes: size %d, isComplete %d\n", length, isComplete);
656 // We have some valid data. Return YES so we can attempt to draw what we've got.
663 originalData = [[NSData alloc] initWithBytes:bytes length:length];
664 if ([MIMEType isEqual:@"application/pdf"]) {
665 Class repClass = [NSImageRep imageRepClassForData:originalData];
667 NSImageRep *rep = [[repClass alloc] initWithData:originalData];
668 [self addRepresentation:rep];
680 ASSERT(frameTimer == nil);
681 ASSERT(frameView == nil);
682 [patternColor release];
684 [originalData release];
687 CGContextRelease(context);
691 if (cachedImageRef) {
692 CGImageRelease (cachedImageRef);
703 ASSERT(frameTimer == nil);
704 ASSERT(frameView == nil);
707 CGContextRelease(context);
710 if (cachedImageRef) {
711 CGImageRelease (cachedImageRef);
718 - (id)firstRepProperty:(NSString *)propertyName
720 id firstRep = [[self representations] objectAtIndex:0];
722 if ([firstRep respondsToSelector:@selector(valueForProperty:)])
723 property = [firstRep valueForProperty:propertyName];
729 id property = [self firstRepProperty:NSImageFrameCount];
730 return property ? [property intValue] : 1;
735 id property = [self firstRepProperty:NSImageCurrentFrame];
736 return property ? [property intValue] : 1;
739 - (void)setCurrentFrame:(int)frame
741 NSBitmapImageRep *imageRep = [[self representations] objectAtIndex:0];
742 [imageRep setProperty:NSImageCurrentFrame withValue:[NSNumber numberWithInt:frame]];
745 - (float)unadjustedFrameDuration
747 id property = [self firstRepProperty:NSImageCurrentFrameDuration];
748 return property ? [property floatValue] : 0.0;
751 - (float)frameDuration
753 float duration = [self unadjustedFrameDuration];
754 if (duration < MINIMUM_DURATION) {
756 Many annoying ads specify a 0 duration to make an image flash
757 as quickly as possible. However a zero duration is faster than
758 the refresh rate. We need to pick a minimum duration.
760 Browsers handle the minimum time case differently. IE seems to use something
761 close to 1/30th of a second. Konqueror uses 0. The ImageMagick library
762 uses 1/100th. The units in the GIF specification are 1/100th of second.
763 We will use 1/30th of second as the minimum time.
765 duration = MINIMUM_DURATION;
770 - (int)repetitionCount
772 id property = [self firstRepProperty:NSImageLoopCount];
773 int count = property ? [property intValue] : 0;
777 - (void)scheduleFrame
779 if (frameTimer && [frameTimer isValid])
781 frameTimer = [[NSTimer scheduledTimerWithTimeInterval:[self frameDuration]
783 selector:@selector(nextFrame:)
788 - (NSGraphicsContext *)_beginRedirectContext:(CGContextRef)aContext
790 NSGraphicsContext *oldContext = 0;
792 oldContext = [NSGraphicsContext currentContext];
793 // Assumes that we are redirecting to a CGBitmapContext.
794 size_t w = CGBitmapContextGetWidth (aContext);
795 size_t h = CGBitmapContextGetHeight (aContext);
796 redirectContext = [[WebImageContext alloc] initWithBounds:NSMakeRect(0, 0, (float)w, (float)h) context:aContext];
797 [NSGraphicsContext setCurrentContext:redirectContext];
802 - (void)_endRedirectContext:(NSGraphicsContext *)aContext
805 [NSGraphicsContext setCurrentContext:aContext];
806 [redirectContext autorelease];
811 - (void)_needsRasterFlush
814 if (needFlushRasterCache && [MIMEType isEqual: @"application/pdf"]) {
815 // FIXME: At this point we need to flush the cached rasterized PDF.
820 - (void)_adjustColorSpace
822 #if COLORMATCH_EVERYTHING
823 NSArray *reps = [self representations];
824 NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
825 if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]] &&
826 [imageRep valueForProperty:NSImageColorSyncProfileData] == nil &&
827 [[imageRep colorSpaceName] isEqualToString:NSDeviceRGBColorSpace]) {
828 [imageRep setColorSpaceName:NSCalibratedRGBColorSpace];
831 NSArray *reps = [self representations];
832 NSBitmapImageRep *imageRep = [reps count] > 0 ? [[self representations] objectAtIndex:0] : nil;
833 if (imageRep && [imageRep isKindOfClass: [NSBitmapImageRep class]] &&
834 [imageRep valueForProperty:NSImageColorSyncProfileData] == nil &&
835 [[imageRep colorSpaceName] isEqualToString:NSCalibratedRGBColorSpace]) {
836 [imageRep setColorSpaceName:NSDeviceRGBColorSpace];
842 - (void)drawClippedToValidInRect:(NSRect)ir fromRect:(NSRect)fr
844 [self _adjustColorSpace];
846 if (loadStatus >= 0) {
847 // The last line might be a partial line, so the number of complete lines is the number
848 // we get from NSImage minus one.
849 int numCompleteLines = loadStatus - 1;
850 if (numCompleteLines <= 0) {
853 int pixelsHigh = [[[self representations] objectAtIndex:0] pixelsHigh];
854 if (pixelsHigh > numCompleteLines) {
855 // Figure out how much of the image is OK to draw. We can't simply
856 // use numCompleteLines because the image may be scaled.
857 float clippedImageHeight = floor([self size].height * numCompleteLines / pixelsHigh);
859 // Figure out how much of the source is OK to draw from.
860 float clippedSourceHeight = clippedImageHeight - fr.origin.y;
861 if (clippedSourceHeight < 1) {
865 // Figure out how much of the destination we are going to draw to.
866 float clippedDestinationHeight = ir.size.height * clippedSourceHeight / fr.size.height;
868 // Reduce heights of both rectangles without changing their positions.
869 // In the flipped case, just adjusting the height is sufficient.
870 ASSERT([self isFlipped]);
871 ASSERT([[NSView focusView] isFlipped]);
872 ir.size.height = clippedDestinationHeight;
873 fr.size.height = clippedSourceHeight;
878 NSGraphicsContext *oldContext = [self _beginRedirectContext:context];
879 [self _needsRasterFlush];
881 // If we have PDF then draw the PDF ourselves, bypassing the NSImage caching mechanisms,
882 // but only do this when we're rendering to an offscreen context. NSImage will always
883 // cache the PDF image at it's native resolution, thus, causing artifacts when the image
884 // is drawn into a scaled or rotated context.
885 if ([MIMEType isEqual:@"application/pdf"])
886 [self _PDFDrawFromRect:fr toRect:ir operation:compositeOperator alpha:1.0 flipped:YES];
888 [self drawInRect:ir fromRect:fr operation:compositeOperator fraction: 1.0];
890 [self _endRedirectContext:oldContext];
893 [self drawInRect:ir fromRect:fr operation:compositeOperator fraction: 1.0];
897 - (CGPDFDocumentRef)_PDFDocumentRef
900 _PDFDoc = [[WebPDFDocument alloc] initWithData:originalData];
903 return [_PDFDoc documentRef];
908 CGPDFDocumentRef document = [self _PDFDocumentRef];
909 if (document != NULL) {
910 CGContextRef _context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
911 CGRect mediaBox = [_PDFDoc mediaBox];
913 CGContextSaveGState(_context);
914 // Rotate translate image into position according to doc properties.
915 [_PDFDoc adjustCTM:_context];
917 // Media box may have non-zero origin which we ignore. CGPDFDocumentShowPage pages start
919 CGContextDrawPDFDocument(_context, CGRectMake(0, 0, mediaBox.size.width, mediaBox.size.height), document, 1);
921 CGContextRestoreGState(_context);
925 - (BOOL)_PDFDrawFromRect:(NSRect)srcRect toRect:(NSRect)dstRect operation:(NSCompositingOperation)op alpha:(float)alpha flipped:(BOOL)flipped
927 // FIXME: The rasterized PDF should be drawn into a cache, and the raster then composited.
929 CGContextRef _context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
930 float hScale, vScale;
932 CGContextSaveGState(_context);
934 CGContextSetCompositeOperation (_context, op);
936 // Scale and translate so the document is rendered in the correct location.
937 hScale = dstRect.size.width / srcRect.size.width;
938 vScale = dstRect.size.height / srcRect.size.height;
940 CGContextTranslateCTM (_context, dstRect.origin.x - srcRect.origin.x * hScale, dstRect.origin.y - srcRect.origin.y * vScale);
941 CGContextScaleCTM (_context, hScale, vScale);
943 // Reverse if flipped image.
945 CGContextScaleCTM(_context, 1, -1);
946 CGContextTranslateCTM (_context, 0, -(dstRect.origin.y + dstRect.size.height));
949 // Clip to destination in case we are imaging part of the source only
950 CGContextClipToRect(_context, CGRectIntegral(*(CGRect*)&srcRect));
955 // done with our fancy transforms
956 CGContextRestoreGState(_context);
961 - (void)resetAnimation
963 [self stopAnimation];
964 [self setCurrentFrame:0];
965 repetitionsComplete = 0;
966 animationFinished = NO;
969 - (void)nextFrame:(id)context
973 // Release the timer that just fired.
974 [frameTimer release];
977 currentFrame = [self currentFrame] + 1;
978 if (currentFrame >= [self frameCount]) {
979 repetitionsComplete += 1;
980 if ([self repetitionCount] && repetitionsComplete >= [self repetitionCount]) {
981 animationFinished = YES;
986 [self setCurrentFrame:currentFrame];
988 // Release the tiling pattern so next frame will update the pattern if we're tiling.
989 [patternColor release];
992 [frameView setNeedsDisplayInRect:targetRect];
995 // Will be called when the containing view is displayed by WebCore RenderImage (via QPainter).
996 // If the image is an animated image it will begin animating. If the image is already animating,
997 // it's frame will have been advanced by nextFrame:.
999 // Also used to draw the image by WebImageView.
1000 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
1003 [self tileInRect:ir fromPoint:tilePoint context:context];
1006 [self drawClippedToValidInRect:ir fromRect:fr];
1009 [self startAnimationIfNecessary];
1013 - (void)flushRasterCache
1015 needFlushRasterCache = YES;
1018 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)operator context:(CGContextRef)aContext
1020 compositeOperator = operator;
1022 if (aContext != context) {
1024 CGContextRetain(aContext);
1027 CGContextRelease(context);
1032 [self drawImageInRect:ir fromRect:fr];
1035 - (void)startAnimationIfNecessary
1037 if ([self frameCount] > 1 && !animationFinished) {
1038 NSView *newView = [NSView focusView];
1039 if (newView != frameView){
1040 [frameView release];
1041 frameView = [newView retain];
1043 [self scheduleFrame];
1044 if (!activeImageRenderers) {
1045 activeImageRenderers = [[NSMutableSet alloc] init];
1047 [activeImageRenderers addObject:self];
1051 - (void)stopAnimation
1053 [frameTimer invalidate];
1054 [frameTimer release];
1057 [frameView release];
1060 [activeImageRenderers removeObject:self];
1063 - (void)tileInRect:(NSRect)rect fromPoint:(NSPoint)point context:(CGContextRef)aContext
1065 // These calculations are only correct for the flipped case.
1066 ASSERT([self isFlipped]);
1067 ASSERT([[NSView focusView] isFlipped]);
1069 [self _adjustColorSpace];
1071 NSSize size = [self size];
1073 // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
1075 oneTileRect.origin.x = rect.origin.x + fmodf(fmodf(-point.x, size.width) - size.width, size.width);
1076 oneTileRect.origin.y = rect.origin.y + fmodf(fmodf(-point.y, size.height) - size.height, size.height);
1077 // I think this is a simpler way to say the same thing. Also, if either point.x or point.y is negative, both
1078 // methods would end up with the wrong answer. For example, fmod(-22,5) is -2, which is the correct delta to
1079 // the start of the pattern, but fmod(-(-23), 5) is 3. This is the delta to the *end* of the pattern
1080 // instead of the start, so onTileRect will be too far right.
1081 // oneTileRect.origin.x = rect.origin.x - fmodf(point.x, size.width);
1082 // oneTileRect.origin.y = rect.origin.y - fmodf(point.y, size.height);
1083 oneTileRect.size = size;
1085 // Compute the appropriate phase relative to the top level view in the window.
1086 // Conveniently, the oneTileRect we computed above has the appropriate origin.
1087 NSPoint originInWindow = [[NSView focusView] convertPoint:oneTileRect.origin toView:nil];
1088 // WebCore may translate the focus, and thus need an extra phase correction
1089 NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
1090 originInWindow.x += extraPhase.x;
1091 originInWindow.y += extraPhase.y;
1092 CGSize phase = CGSizeMake(fmodf(originInWindow.x, size.width), fmodf(originInWindow.y, size.height));
1094 // If the single image draw covers the whole area, then just draw once.
1095 if (NSContainsRect(oneTileRect, rect)) {
1098 fromRect.origin.x = rect.origin.x - oneTileRect.origin.x;
1099 fromRect.origin.y = rect.origin.y - oneTileRect.origin.y;
1100 fromRect.size = rect.size;
1102 [self drawClippedToValidInRect:rect fromRect:fromRect];
1106 // If we only have a partial image, just do nothing, because CoreGraphics will not help us tile
1107 // with a partial image. But maybe later we can fix this by constructing a pattern image that's
1108 // transparent where needed.
1109 if (loadStatus != NSImageRepLoadStatusCompleted) {
1113 // Since we need to tile, construct an NSColor so we can get CoreGraphics to do it for us.
1114 if (patternColorLoadStatus != loadStatus) {
1115 [patternColor release];
1118 if (patternColor == nil) {
1119 patternColor = [[NSColor colorWithPatternImage:self] retain];
1120 patternColorLoadStatus = loadStatus;
1123 NSGraphicsContext *oldContext = [self _beginRedirectContext:context];
1124 [self _needsRasterFlush];
1126 [NSGraphicsContext saveGraphicsState];
1128 CGContextSetPatternPhase((CGContextRef)[[NSGraphicsContext currentContext] graphicsPort], phase);
1130 [NSBezierPath fillRect:rect];
1132 [NSGraphicsContext restoreGraphicsState];
1134 [self _endRedirectContext:oldContext];
1139 [self startAnimationIfNecessary];
1142 - (void)resize:(NSSize)s
1144 [self setScalesWhenResized:YES];
1148 - (NSString *)MIMEType
1153 // Try hard to get a CGImageRef. First try to snag one from the
1154 // NSBitmapImageRep, then try to create one. In some cases we may
1155 // not be able to create one.
1156 - (CGImageRef)imageRef
1161 return cachedImageRef;
1163 if ([[self representations] count] > 0) {
1164 NSBitmapImageRep *rep = [[self representations] objectAtIndex:0];
1166 if ([rep respondsToSelector:@selector(_CGImageRef)])
1167 ref = [rep _CGImageRef];
1170 cachedImageRef = _createImageRef(rep);
1171 ref = cachedImageRef;
1177 - (void)releasePatternColor
1179 [patternColor release];
1185 @implementation WebImageRenderer
1187 - (id)initWithMIMEType:(NSString *)MIME
1189 WebInternalImage *i = [[WebInternalImage alloc] initWithMIMEType:MIME];
1199 - (id)initWithData:(NSData *)data MIMEType:(NSString *)MIME
1201 WebInternalImage *i = [[WebInternalImage alloc] initWithData:data MIMEType:MIME];
1211 - (id)initWithContentsOfFile:(NSString *)filename
1213 WebInternalImage *i = [[WebInternalImage alloc] initWithContentsOfFile:filename];
1225 [image releasePatternColor];
1235 - (NSString *)MIMEType
1237 return [image MIMEType];
1240 - (NSData *)TIFFRepresentation
1242 return [image TIFFRepresentation];
1247 return [image frameCount];
1250 - (void)setOriginalData:(NSData *)data
1252 NSData *oldData = image->originalData;
1253 image->originalData = [data retain];
1257 + (void)stopAnimationsInView:(NSView *)aView
1259 [WebInternalImage stopAnimationsInView:aView];
1262 - (BOOL)incrementalLoadWithBytes:(const void *)bytes length:(unsigned)length complete:(BOOL)isComplete callback:(id)c
1264 return [image incrementalLoadWithBytes:bytes length:length complete:isComplete callback:c];
1272 return [image size];
1275 - (void)resize:(NSSize)s
1280 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr
1282 [image drawImageInRect:ir fromRect:fr];
1285 - (void)drawImageInRect:(NSRect)ir fromRect:(NSRect)fr compositeOperator:(NSCompositingOperation)compsiteOperator context:(CGContextRef)context
1287 [image drawImageInRect:ir fromRect:fr compositeOperator:compsiteOperator context:context];
1290 - (void)resetAnimation
1292 [image resetAnimation];
1295 - (void)stopAnimation
1297 [image stopAnimation];
1300 - (void)tileInRect:(NSRect)r fromPoint:(NSPoint)p context:(CGContextRef)context
1302 [image tileInRect:r fromPoint:p context:context];
1307 return image == nil || [image isNull];
1310 - (id <WebCoreImageRenderer>)retainOrCopyIfNeeded
1312 WebImageRenderer *newRenderer = [image createRendererIfNeeded];
1320 - (void)increaseUseCount
1322 [image increaseUseCount];
1325 - (void)decreaseUseCount
1327 [image decreaseUseCount];
1330 - (void)flushRasterCache
1332 [image flushRasterCache];
1335 - (CGImageRef)imageRef
1337 return [image imageRef];
1340 - (id)copyWithZone:(NSZone *)zone
1342 WebImageRenderer *copy = [[WebImageRenderer alloc] init];
1343 copy->image = [image copy];
1349 static CGImageRef _createImageRef(NSBitmapImageRep *rep) {
1350 BOOL isPlanar = [rep isPlanar];
1354 const unsigned char *bitmapData = [rep bitmapData];
1355 int pixelsWide = [rep pixelsWide];
1356 int pixelsHigh = [rep pixelsHigh];
1357 int bitsPerSample = [rep bitsPerSample];
1358 int bitsPerPixel = [rep bitsPerPixel];
1359 int bytesPerRow = [rep bytesPerRow];
1360 BOOL hasAlpha = [rep hasAlpha];
1362 CGDataProviderRef dataProvider;
1364 CGColorSpaceRef colorSpace = WebCGColorSpaceCreateRGB();
1365 dataProvider = CGDataProviderCreateWithData(NULL, bitmapData, pixelsHigh * bytesPerRow, NULL);
1367 image = CGImageCreate(pixelsWide, pixelsHigh, bitsPerSample, bitsPerPixel, bytesPerRow, colorSpace,
1368 hasAlpha ? kCGImageAlphaPremultipliedLast : kCGImageAlphaNone,
1369 dataProvider, NULL, false /*shouldInterpolate*/, kCGRenderingIntentDefault);
1371 CGDataProviderRelease(dataProvider);
1372 CGColorSpaceRelease(colorSpace);
1379 //------------------------------------------------------------------------------------
1381 @implementation WebPDFDocument
1383 static void ReleasePDFDocumentData(void *info, const void *data, size_t size) {
1384 [(NSData*)info autorelease];
1387 - (id) initWithData:(NSData*)data
1389 self = [super init];
1393 CGDataProviderRef dataProvider = CGDataProviderCreateWithData([data retain], [data bytes], [data length], ReleasePDFDocumentData);
1394 _document = CGPDFDocumentCreateWithProvider(dataProvider);
1395 CGDataProviderRelease(dataProvider);
1398 [self setCurrentPage:0];
1405 if (_document != NULL) CGPDFDocumentRelease(_document);
1411 if (_document != NULL) CGPDFDocumentRelease(_document);
1415 - (CGPDFDocumentRef) documentRef
1429 // rotate the media box and calculate bounding box
1430 float sina = sin (_rotation);
1431 float cosa = cos (_rotation);
1432 float width = NSWidth (_cropBox);
1433 float height = NSHeight(_cropBox);
1435 // calculate rotated x and y axis
1436 NSPoint rx = NSMakePoint( width * cosa, width * sina);
1437 NSPoint ry = NSMakePoint(-height * sina, height * cosa);
1439 // find delta width and height of rotated points
1440 rotatedRect.origin = _cropBox.origin;
1441 rotatedRect.size.width = ceil(fabs(rx.x - ry.x));
1442 rotatedRect.size.height = ceil(fabs(ry.y - rx.y));
1447 - (void) adjustCTM:(CGContextRef)context
1449 // rotate the crop box and calculate bounding box
1450 float sina = sin (-_rotation);
1451 float cosa = cos (-_rotation);
1452 float width = NSWidth (_cropBox);
1453 float height = NSHeight(_cropBox);
1455 // calculate rotated x and y edges of the corp box. if they're negative, it means part of the image has
1456 // been rotated outside of the bounds and we need to shift over the image so it lies inside the bounds again
1457 NSPoint rx = NSMakePoint( width * cosa, width * sina);
1458 NSPoint ry = NSMakePoint(-height * sina, height * cosa);
1460 // adjust so we are at the crop box origin
1461 CGContextTranslateCTM(context, floor(-MIN(0,MIN(rx.x, ry.x))), floor(-MIN(0,MIN(rx.y, ry.y))));
1463 // rotate -ve to remove rotation
1464 CGContextRotateCTM(context, -_rotation);
1466 // shift so we are completely within media box
1467 CGContextTranslateCTM(context, _mediaBox.origin.x - _cropBox.origin.x, _mediaBox.origin.y - _cropBox.origin.y);
1470 - (void) setCurrentPage:(int)page
1472 if (page != _currentPage && page >= 0 && page < [self pageCount]) {
1476 _currentPage = page;
1478 // get media box (guaranteed)
1479 _mediaBox = CGPDFDocumentGetMediaBox(_document, page + 1);
1481 // get crop box (not always there). if not, use _mediaBox
1482 r = CGPDFDocumentGetCropBox(_document, page + 1);
1483 if (!CGRectIsEmpty(r)) {
1484 _cropBox = NSMakeRect(r.origin.x, r.origin.y, r.size.width, r.size.height);
1486 _cropBox = NSMakeRect(_mediaBox.origin.x, _mediaBox.origin.y, _mediaBox.size.width, _mediaBox.size.height);
1489 // get page rotation angle
1490 _rotation = CGPDFDocumentGetRotationAngle(_document, page + 1) * M_PI / 180.0; // to radians
1496 return _currentPage;
1501 return CGPDFDocumentGetNumberOfPages(_document);
1506 CGColorSpaceRef WebCGColorSpaceCreateRGB(void)
1508 #ifdef COLORMATCH_EVERYTHING
1509 #if BUILDING_ON_PANTHER
1510 return CGColorSpaceCreateWithName(kCGColorSpaceUserRGB);
1511 #else // !BUILDING_ON_PANTHER
1512 return CGColorSpaceCreateDeviceRGB();
1513 #endif // BUILDING_ON_PANTHER
1514 #else // !COLORMATCH_EVERYTHING
1515 #if BUILDING_ONPANTHER
1516 return CGColorSpaceCreateDeviceRGB();
1517 #else // !BUILDING_ON_PANTHER
1518 return CGColorSpaceCreateDisplayRGB();
1519 #endif // BUILDING_ON_PANTHER
1523 CGColorSpaceRef WebCGColorSpaceCreateGray(void)
1525 #ifdef COLORMATCH_EVERYTHING
1526 #if BUILDING_ON_PANTHER
1527 return CGColorSpaceCreateWithName(kCGColorSpaceUserGray);
1528 #else // !BUILDING_ON_PANTHER
1529 return CGColorSpaceCreateDeviceGray();
1530 #endif // BUILDING_ON_PANTHER
1531 #else // !COLORMATCH_EVERYTHING
1532 #if BUILDING_ONPANTHER
1533 return CGColorSpaceCreateDeviceGray();
1534 #else // !BUILDING_ON_PANTHER
1535 return CGColorSpaceCreateDisplayGray();
1536 #endif // BUILDING_ON_PANTHER
1540 CGColorSpaceRef WebCGColorSpaceCreateCMYK(void)
1542 #ifdef COLORMATCH_EVERYTHING
1543 #if BUILDING_ON_PANTHER
1544 return CGColorSpaceCreateWithName(kCGColorSpaceUserCMYK);
1545 #else // !BUILDING_ON_PANTHER
1546 return CGColorSpaceCreateDeviceCMYK();
1547 #endif // BUILDING_ON_PANTHER
1548 #else // !COLORMATCH_EVERYTHING
1549 #if BUILDING_ONPANTHER
1550 return CGColorSpaceCreateDeviceCMYK();
1551 #else // !BUILDING_ON_PANTHER
1552 // FIXME: no device CMYK
1553 return CGColorSpaceCreateDeviceCMYK();
1554 #endif // BUILDING_ON_PANTHER