From f3a9150d7cc734c5b6100a394bdacfc04dd530bb Mon Sep 17 00:00:00 2001 From: ericwa Date: Thu, 18 Aug 2011 23:57:50 +0000 Subject: [PATCH] * Headers/AppKit/NSImageRep.h: Add -drawInRect:fromRect:operation:fraction:respectFlipped:hints: method * Source/NSImage.m: * Source/NSImageRep.m: Refactor drawing code from NSImage to NSImageRep. This should cause no functionlity change, but it lets NSImageRep subclasses implement more efficient versions of -drawInRect:fromRect:... that don't involve the (expensive) drawing of the image in a temporary offscreen window and then drawing from there on to the destination surface. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@33764 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 11 + Headers/AppKit/NSImageRep.h | 9 + Source/NSImage.m | 500 +++++------------------------------- Source/NSImageRep.m | 316 +++++++++++++++++++++++ 4 files changed, 397 insertions(+), 439 deletions(-) diff --git a/ChangeLog b/ChangeLog index dfa54de77..60350c674 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2011-08-18 Eric Wasylishen + + * Headers/AppKit/NSImageRep.h: Add + -drawInRect:fromRect:operation:fraction:respectFlipped:hints: method + * Source/NSImage.m: + * Source/NSImageRep.m: Refactor drawing code from NSImage to NSImageRep. + This should cause no functionlity change, but it lets NSImageRep + subclasses implement more efficient versions of -drawInRect:fromRect:... + method that don't involve the wasteful drawing the image in a temporary + offscreen window and then drawing from there on to the destination surface. + 2011-08-17 Eric Wasylishen * Source/Functions.m (NSDrawNinePartImage): Pixel-align drawing rect using diff --git a/Headers/AppKit/NSImageRep.h b/Headers/AppKit/NSImageRep.h index 578f583e1..ae3af677b 100644 --- a/Headers/AppKit/NSImageRep.h +++ b/Headers/AppKit/NSImageRep.h @@ -34,6 +34,7 @@ #import #import #import +#import @class NSString; @class NSArray; @@ -147,6 +148,14 @@ enum { - (BOOL)draw; - (BOOL)drawAtPoint:(NSPoint)aPoint; - (BOOL)drawInRect:(NSRect)aRect; +#if OS_API_VERSION(MAC_OS_X_VERSION_10_6, GS_API_LATEST) +- (BOOL) drawInRect: (NSRect)dstRect + fromRect: (NSRect)srcRect + operation: (NSCompositingOperation)op + fraction: (float)delta + respectFlipped: (BOOL)respectFlipped + hints: (NSDictionary*)hints; +#endif // // Managing NSImageRep Subclasses diff --git a/Source/NSImage.m b/Source/NSImage.m index 7774a1c86..4caf4096b 100644 --- a/Source/NSImage.m +++ b/Source/NSImage.m @@ -55,24 +55,6 @@ #import "GNUstepGUI/GSDisplayServer.h" #import "GSThemePrivate.h" - -/* Helpers. Would be nicer to use the C99 fmin/fmax functions, but that - isn't currently possible. */ -static double gs_min(double x, double y) -{ - if (x > y) - return y; - else - return x; -} -static double gs_max(double x, double y) -{ - if (x < y) - return y; - else - return x; -} - BOOL NSImageForceCaching = NO; /* use on missmatch */ @implementation NSBundle (NSImageAdditions) @@ -922,390 +904,9 @@ repd_for_rep(NSArray *_reps, NSImageRep *rep) [self drawInRect: NSMakeRect(point.x, point.y, srcRect.size.width, srcRect.size.height) fromRect: srcRect operation: op - fraction: delta]; -} - -/* New code path that delegates as much as possible to the backend and whose -behavior precisely matches Cocoa. */ -- (void) nativeDrawInRect: (NSRect)dstRect - fromRect: (NSRect)srcRect - operation: (NSCompositingOperation)op - fraction: (float)delta -{ - NSGraphicsContext *ctxt = GSCurrentContext(); - NSSize imgSize = [self size]; - float widthScaleFactor; - float heightScaleFactor; - NSImageRep *rep; - - if (NSEqualRects(srcRect, NSZeroRect)) - { - srcRect.size = imgSize; - /* For -drawAtPoint:fromRect:operation:fraction: used with a zero rect */ - if (NSEqualSizes(dstRect.size, NSZeroSize)) - { - dstRect.size = imgSize; - } - } - - // Choose a rep to use - - rep = [self bestRepresentationForRect: dstRect - context: nil - hints: nil]; - if (rep == nil) - return; - - if (!dstRect.size.width || !dstRect.size.height - || !srcRect.size.width || !srcRect.size.height) - return; - - // Clip to image bounds - if (srcRect.origin.x < 0) - srcRect.origin.x = 0; - if (srcRect.origin.y < 0) - srcRect.origin.y = 0; - if (NSMaxX(srcRect) > imgSize.width) - srcRect.size.width = imgSize.width - srcRect.origin.x; - if (NSMaxY(srcRect) > imgSize.height) - srcRect.size.height = imgSize.height - srcRect.origin.y; - - widthScaleFactor = dstRect.size.width / srcRect.size.width; - heightScaleFactor = dstRect.size.height / srcRect.size.height; - - if (![ctxt isDrawingToScreen]) - { - /* We can't composite or dissolve if we aren't drawing to a screen, - so we'll just draw the right part of the image in the right - place. */ - NSPoint p; - - p.x = dstRect.origin.x / widthScaleFactor - srcRect.origin.x; - p.y = dstRect.origin.y / heightScaleFactor - srcRect.origin.y; - - DPSgsave(ctxt); - DPSrectclip(ctxt, dstRect.origin.x, dstRect.origin.y, - dstRect.size.width, dstRect.size.height); - DPSscale(ctxt, widthScaleFactor, heightScaleFactor); - [self drawRepresentation: rep - inRect: NSMakeRect(p.x, p.y, imgSize.width, imgSize.height)]; - DPSgrestore(ctxt); - - return; - } - - /* We cannot ask the backend to draw the image directly when the source rect - doesn't cover the whole image. - Cairo doesn't support to specify a source rect for a surface used as a - source, see cairo_set_source_surface()). - CoreGraphics is similarly limited, see CGContextDrawImage(). - For now, we always use a two step process: - - draw the image data in a cache to apply the srcRect to inRect scaling - - draw the cache into the destination context - It might be worth to move the first step to the backend, so we don't have - to create a cache window but just an intermediate surface. - We create a cache every time but otherwise we are more efficient than the - old code path since the cache size is limited to what we actually draw - and doesn't involve drawing the whole image. */ - { - /* An intermediate image used to scale the image to be drawn as needed */ - NSCachedImageRep *cache; - /* The scaled image graphics state we used as the source from which we - draw into the destination (the current graphics context)*/ - int gState; - /* The context of the cache window */ - NSGraphicsContext *cacheCtxt; - NSSize repSize = [rep size]; - /* The size of the cache window that will hold the scaled image */ - NSSize cacheSize; - - CGFloat imgToCacheWidthScaleFactor; - CGFloat imgToCacheHeightScaleFactor;; - - NSRect srcRectInCache; - NSAffineTransform *transform, *backup; - - if (NSEqualSizes(repSize, NSZeroSize)) - { - repSize = dstRect.size; - } - - if (([rep pixelsWide] == NSImageRepMatchesDevice && - [rep pixelsHigh] == NSImageRepMatchesDevice) && - (dstRect.size.width > repSize.width || - dstRect.size.height > repSize.height)) - { - cacheSize = [[ctxt GSCurrentCTM] transformSize: dstRect.size]; - } - else - { - cacheSize = [[ctxt GSCurrentCTM] transformSize: repSize]; - } - - if (cacheSize.width < 0) - cacheSize.width *= -1; - if (cacheSize.height < 0) - cacheSize.height *= -1; - - imgToCacheWidthScaleFactor = cacheSize.width / imgSize.width; - imgToCacheHeightScaleFactor = cacheSize.height / imgSize.height; - - srcRectInCache = NSMakeRect(srcRect.origin.x * imgToCacheWidthScaleFactor, - srcRect.origin.y * imgToCacheHeightScaleFactor, - srcRect.size.width * imgToCacheWidthScaleFactor, - srcRect.size.height * imgToCacheHeightScaleFactor); - - cache = [[NSCachedImageRep alloc] - initWithSize: NSMakeSize(ceil(cacheSize.width), ceil(cacheSize.height)) - depth: [[NSScreen mainScreen] depth] - separate: YES - alpha: YES]; - - [[[cache window] contentView] lockFocus]; - cacheCtxt = GSCurrentContext(); - - /* Clear the cache window surface */ - DPScompositerect(cacheCtxt, 0, 0, ceil(cacheSize.width), ceil(cacheSize.height), NSCompositeClear); - gState = [cacheCtxt GSDefineGState]; - - //NSLog(@"Draw in cache size %@", NSStringFromSize(cacheSize)); - - /* We must not use -drawRepresentation:inRect: because the image must drawn - scaled even when -scalesWhenResized is NO */ - [rep - drawInRect: NSMakeRect(0, 0, cacheSize.width, cacheSize.height)]; - /* If we're doing a dissolve, use a DestinationIn composite to lower - the alpha of the pixels. */ - if (delta != 1.0) - { - DPSsetalpha(cacheCtxt, delta); - DPScompositerect(cacheCtxt, 0, 0, ceil(cacheSize.width), ceil(cacheSize.height), - NSCompositeDestinationIn); - } - - [[[cache window] contentView] unlockFocus]; - - //NSLog(@"Draw in %@ from %@ from cache rect %@", NSStringFromRect(dstRect), - // NSStringFromRect(srcRect), NSStringFromRect(srcRectInCache)); - - backup = [ctxt GSCurrentCTM]; - - transform = [NSAffineTransform transform]; - [transform translateXBy: dstRect.origin.x yBy: dstRect.origin.y]; - [transform scaleXBy: dstRect.size.width / srcRectInCache.size.width - yBy: dstRect.size.height / srcRectInCache.size.height]; - [transform concat]; - - [ctxt GSdraw: gState - toPoint: NSMakePoint(0,0) - fromRect: srcRectInCache - operation: op - fraction: delta]; - - [ctxt GSSetCTM: backup]; - - [ctxt GSUndefineGState: gState]; - DESTROY(cache); - } -} - -/* Old code path that can probably partially be merged with the new native implementation. -Fallback for backends other than Cairo. */ -- (void) guiDrawInRect: (NSRect)dstRect - fromRect: (NSRect)srcRect - operation: (NSCompositingOperation)op - fraction: (float)delta -{ - NSGraphicsContext *ctxt = GSCurrentContext(); - NSAffineTransform *transform; - NSSize s; - NSImageRep *rep; - - s = [self size]; - - if (NSEqualRects(srcRect, NSZeroRect)) - { - srcRect.size = s; - /* For -drawAtPoint:fromRect:operation:fraction: used with a zero rect */ - if (NSEqualSizes(dstRect.size, NSZeroSize)) - { - dstRect.size = s; - } - } - - // Choose a rep to use - - rep = [self bestRepresentationForRect: dstRect - context: nil - hints: nil]; - - if (rep == nil) - return; - - if (!dstRect.size.width || !dstRect.size.height - || !srcRect.size.width || !srcRect.size.height) - return; - - // CLip to image bounds - if (srcRect.origin.x < 0) - srcRect.origin.x = 0; - if (srcRect.origin.y < 0) - srcRect.origin.y = 0; - if (NSMaxX(srcRect) > s.width) - srcRect.size.width = s.width - srcRect.origin.x; - if (NSMaxY(srcRect) > s.height) - srcRect.size.height = s.height - srcRect.origin.y; - - if (![ctxt isDrawingToScreen]) - { - /* We can't composite or dissolve if we aren't drawing to a screen, - so we'll just draw the right part of the image in the right - place. */ - NSPoint p; - double fx, fy; - - fx = dstRect.size.width / srcRect.size.width; - fy = dstRect.size.height / srcRect.size.height; - - p.x = dstRect.origin.x / fx - srcRect.origin.x; - p.y = dstRect.origin.y / fy - srcRect.origin.y; - - DPSgsave(ctxt); - DPSrectclip(ctxt, dstRect.origin.x, dstRect.origin.y, - dstRect.size.width, dstRect.size.height); - DPSscale(ctxt, fx, fy); - [self drawRepresentation: rep - inRect: NSMakeRect(p.x, p.y, s.width, s.height)]; - DPSgrestore(ctxt); - - return; - } - - /* Figure out what the effective transform from image space to - 'window space' is. */ - transform = [ctxt GSCurrentCTM]; - - [transform scaleXBy: dstRect.size.width / srcRect.size.width - yBy: dstRect.size.height / srcRect.size.height]; - - /* We can't composite or dissolve directly from the image reps, so we - create a temporary off-screen window large enough to hold the - transformed image, draw the image rep there, and composite from there - to the destination. - - Optimization: Since we do the entire image at once, we might need a - huge buffer. If this starts hurting too much, there are a couple of - things we could do to: - - 1. Take srcRect into account and only process the parts of the image - we really need. - 2. Take the clipping path into account. Desirable, especially if we're - being drawn as lots of small strips in a scrollview. We don't have - the clipping path here, though. - 3. Allocate a permanent but small buffer and process the image - piecewise. - - */ - { - NSCachedImageRep *cache; - NSAffineTransformStruct ts; - NSPoint p; - double x0, y0, x1, y1, w, h; - int gState; - NSGraphicsContext *ctxt1; - - /* Figure out how big we need to make the window that'll hold the - transformed image. */ - p = [transform transformPoint: NSMakePoint(0, s.height)]; - x0 = x1 = p.x; - y0 = y1 = p.y; - - p = [transform transformPoint: NSMakePoint(s.width, 0)]; - x0 = gs_min(x0, p.x); - y0 = gs_min(y0, p.y); - x1 = gs_max(x1, p.x); - y1 = gs_max(y1, p.y); - - p = [transform transformPoint: NSMakePoint(s.width, s.height)]; - x0 = gs_min(x0, p.x); - y0 = gs_min(y0, p.y); - x1 = gs_max(x1, p.x); - y1 = gs_max(y1, p.y); - - p = [transform transformPoint: NSMakePoint(0, 0)]; - x0 = gs_min(x0, p.x); - y0 = gs_min(y0, p.y); - x1 = gs_max(x1, p.x); - y1 = gs_max(y1, p.y); - - x0 = floor(x0); - y0 = floor(y0); - x1 = ceil(x1); - y1 = ceil(y1); - - w = x1 - x0; - h = y1 - y0; - - /* This is where we want the origin of image space to be in our - window. */ - p.x -= x0; - p.y -= y0; - - cache = [[NSCachedImageRep alloc] - initWithSize: NSMakeSize(w, h) - depth: [[NSScreen mainScreen] depth] - separate: YES - alpha: YES]; - - [[[cache window] contentView] lockFocus]; - // The context of the cache window - ctxt1 = GSCurrentContext(); - DPScompositerect(ctxt1, 0, 0, w, h, NSCompositeClear); - - /* Set up the effective transform. We also save a gState with this - transform to make it easier to do the final composite. */ - ts = [transform transformStruct]; - ts.tX = p.x; - ts.tY = p.y; - [transform setTransformStruct: ts]; - [ctxt1 GSSetCTM: transform]; - gState = [ctxt1 GSDefineGState]; - - - /* We must not use -drawRepresentation:inRect: because the image must drawn - scaled even when -scalesWhenResized is NO */ - - // FIXME: should the background color be filled here? - // If I don't I get black backgrounds on images with xlib; maybe an xlib backend bug - PSgsave(); - if (_color != nil) - { - [_color set]; - NSRectFill(NSMakeRect(0, 0, s.width, s.height)); - } - [rep drawInRect: NSMakeRect(0, 0, s.width, s.height)]; - PSgrestore(); - - /* If we're doing a dissolve, use a DestinationIn composite to lower - the alpha of the pixels. */ - if (delta != 1.0) - { - DPSsetalpha(ctxt1, delta); - DPScompositerect(ctxt1, 0, 0, s.width, s.height, - NSCompositeDestinationIn); - } - - [[[cache window] contentView] unlockFocus]; - - - DPScomposite(ctxt, srcRect.origin.x, srcRect.origin.y, - srcRect.size.width, srcRect.size.height, gState, - dstRect.origin.x, dstRect.origin.y, op); - - [ctxt GSUndefineGState: gState]; - - DESTROY(cache); - } + fraction: delta + respectFlipped: NO + hints: nil]; } - (void) drawInRect: (NSRect)dstRect @@ -1313,57 +914,78 @@ Fallback for backends other than Cairo. */ operation: (NSCompositingOperation)op fraction: (float)delta { - if ([GSCurrentContext() supportsDrawGState]) - { - [self nativeDrawInRect: dstRect fromRect: srcRect operation: op fraction: delta]; - } - else - { - [self guiDrawInRect: dstRect fromRect: srcRect operation: op fraction: delta]; - } + [self drawInRect: dstRect + fromRect: srcRect + operation: op + fraction: delta + respectFlipped: NO + hints: nil]; } -- (void) drawInRect: (NSRect)dstRect +/** + * Base drawing method in NSImage; all other draw methods call this one + */ +- (void) drawInRect: (NSRect)dstRect // Negative width/height => Nothing draws. fromRect: (NSRect)srcRect operation: (NSCompositingOperation)op fraction: (float)delta respectFlipped: (BOOL)respectFlipped hints: (NSDictionary*)hints { - NSAffineTransform *backup = nil; - NSGraphicsContext *ctx = GSCurrentContext(); - BOOL compensateForFlip = (respectFlipped && [ctx isFlipped]); + NSImageRep *rep; + NSGraphicsContext *ctxt; + NSSize imgSize, repSize; + NSRect repSrcRect; - // FIXME: Hints are currently ignored + ctxt = GSCurrentContext(); + imgSize = [self size]; - if (compensateForFlip) + // Handle abbreviated parameters + + if (NSEqualRects(srcRect, NSZeroRect)) { - CGFloat height; - NSAffineTransform *newXform; - - height = dstRect.size.height != 0 ? - dstRect.size.height : [self size].height; - - backup = [ctx GSCurrentCTM]; - - newXform = [backup copy]; - [newXform translateXBy: dstRect.origin.x yBy: dstRect.origin.y + height]; - [newXform scaleXBy: 1 yBy: -1]; - [ctx GSSetCTM: newXform]; - [newXform release]; - - dstRect.origin = NSMakePoint(0, 0); + srcRect.size = imgSize; + } + if (NSEqualSizes(dstRect.size, NSZeroSize)) // For -drawAtPoint:fromRect:operation:fraction: + { + dstRect.size = imgSize; } - [self drawInRect: dstRect - fromRect: srcRect - operation: op - fraction: delta]; + if (imgSize.width <= 0 || imgSize.height <= 0) + return; - if (compensateForFlip) - { - [ctx GSSetCTM: backup]; - } + // Select a rep + + rep = [self bestRepresentationForRect: dstRect + context: ctxt + hints: hints]; + if (rep == nil) + return; + + repSize = [rep size]; + + // Convert srcRect from image coordinate space to rep coordinate space + { + const CGFloat imgToRepWidthScaleFactor = repSize.width / imgSize.width; + const CGFloat imgToRepHeightScaleFactor = repSize.height / imgSize.height; + + repSrcRect = NSMakeRect(srcRect.origin.x * imgToRepWidthScaleFactor, + srcRect.origin.y * imgToRepHeightScaleFactor, + srcRect.size.width * imgToRepWidthScaleFactor, + srcRect.size.height * imgToRepHeightScaleFactor); + } + + // FIXME: Insert caching code here which gets the cached + // copy of rep and draws from that, if caching is enabled. + + // FIXME: Draw background? + + [rep drawInRect: dstRect + fromRect: repSrcRect + operation: op + fraction: delta + respectFlipped: respectFlipped + hints: hints]; } - (void) addRepresentation: (NSImageRep *)imageRep diff --git a/Source/NSImageRep.m b/Source/NSImageRep.m index 1b05d2f8f..4cb0e7149 100644 --- a/Source/NSImageRep.m +++ b/Source/NSImageRep.m @@ -28,6 +28,7 @@ #include "config.h" #include +#import #import #import #import @@ -36,14 +37,19 @@ #import #import #import +#import "AppKit/NSAffineTransform.h" #import "AppKit/NSImageRep.h" #import "AppKit/NSBitmapImageRep.h" +#import "AppKit/NSCachedImageRep.h" #import "AppKit/NSEPSImageRep.h" #import "AppKit/NSPasteboard.h" #import "AppKit/NSGraphicsContext.h" #import "AppKit/NSView.h" #import "AppKit/NSColor.h" +#import "AppKit/NSWindow.h" +#import "AppKit/NSScreen.h" #import "AppKit/DPSOperators.h" +#import "AppKit/PSOperators.h" #import "GNUstepGUI/GSGhostscriptImageRep.h" #import "GNUstepGUI/GSImageMagickImageRep.h" @@ -491,6 +497,316 @@ implement, so we can't do that. */ return ok; } +/* New code path that delegates as much as possible to the backend and whose +behavior precisely matches Cocoa. */ +- (void) nativeDrawInRect: (NSRect)dstRect + fromRect: (NSRect)srcRect + operation: (NSCompositingOperation)op + fraction: (float)delta +{ + NSGraphicsContext *ctxt = GSCurrentContext(); + /* An intermediate image used to scale the image to be drawn as needed */ + NSCachedImageRep *cache; + /* The scaled image graphics state we used as the source from which we + draw into the destination (the current graphics context)*/ + int gState; + /* The context of the cache window */ + NSGraphicsContext *cacheCtxt; + const NSSize repSize = [self size]; + /* The size of the cache window */ + NSSize cacheSize; + + CGFloat repToCacheWidthScaleFactor; + CGFloat repToCacheHeightScaleFactor; + + NSRect srcRectInCache; + NSAffineTransform *transform, *backup; + + // FIXME: Revisit this calculation of cache size + + if (([self pixelsWide] == NSImageRepMatchesDevice && + [self pixelsHigh] == NSImageRepMatchesDevice) && + (dstRect.size.width > repSize.width || + dstRect.size.height > repSize.height)) + { + cacheSize = [[ctxt GSCurrentCTM] transformSize: dstRect.size]; + } + else + { + cacheSize = [[ctxt GSCurrentCTM] transformSize: repSize]; + } + + if (cacheSize.width < 0) + cacheSize.width *= -1; + if (cacheSize.height < 0) + cacheSize.height *= -1; + + repToCacheWidthScaleFactor = cacheSize.width / repSize.width; + repToCacheHeightScaleFactor = cacheSize.height / repSize.height; + + srcRectInCache = NSMakeRect(srcRect.origin.x * repToCacheWidthScaleFactor, + srcRect.origin.y * repToCacheHeightScaleFactor, + srcRect.size.width * repToCacheWidthScaleFactor, + srcRect.size.height * repToCacheHeightScaleFactor); + + cache = [[NSCachedImageRep alloc] + initWithSize: NSMakeSize(ceil(cacheSize.width), ceil(cacheSize.height)) + depth: [[NSScreen mainScreen] depth] + separate: YES + alpha: YES]; + + [[[cache window] contentView] lockFocus]; + cacheCtxt = GSCurrentContext(); + + /* Clear the cache window surface */ + DPScompositerect(cacheCtxt, 0, 0, ceil(cacheSize.width), ceil(cacheSize.height), NSCompositeClear); + gState = [cacheCtxt GSDefineGState]; + + //NSLog(@"Draw in cache size %@", NSStringFromSize(cacheSize)); + + [self drawInRect: NSMakeRect(0, 0, cacheSize.width, cacheSize.height)]; + + [[[cache window] contentView] unlockFocus]; + + //NSLog(@"Draw in %@ from %@ from cache rect %@", NSStringFromRect(dstRect), + // NSStringFromRect(srcRect), NSStringFromRect(srcRectInCache)); + + backup = [ctxt GSCurrentCTM]; + + transform = [NSAffineTransform transform]; + [transform translateXBy: dstRect.origin.x yBy: dstRect.origin.y]; + [transform scaleXBy: dstRect.size.width / srcRectInCache.size.width + yBy: dstRect.size.height / srcRectInCache.size.height]; + [transform concat]; + + [ctxt GSdraw: gState + toPoint: NSMakePoint(0,0) + fromRect: srcRectInCache + operation: op + fraction: delta]; + + [ctxt GSSetCTM: backup]; + + [ctxt GSUndefineGState: gState]; + DESTROY(cache); +} + +/* Old code path that can probably partially be merged with the new native implementation. +Fallback for backends other than Cairo. */ +- (void) guiDrawInRect: (NSRect)dstRect + fromRect: (NSRect)srcRect + operation: (NSCompositingOperation)op + fraction: (float)delta +{ + NSGraphicsContext *ctxt = GSCurrentContext(); + NSAffineTransform *transform; + NSSize repSize; + + repSize = [self size]; + + /* Figure out what the effective transform from rep space to + 'window space' is. */ + transform = [ctxt GSCurrentCTM]; + + [transform scaleXBy: dstRect.size.width / srcRect.size.width + yBy: dstRect.size.height / srcRect.size.height]; + + /* We can't composite or dissolve directly from the image reps, so we + create a temporary off-screen window large enough to hold the + transformed image, draw the image rep there, and composite from there + to the destination. + + Optimization: Since we do the entire image at once, we might need a + huge buffer. If this starts hurting too much, there are a couple of + things we could do to: + + + 1. Take srcRect into account and only process the parts of the image + we really need. + 2. Take the clipping path into account. Desirable, especially if we're + being drawn as lots of small strips in a scrollview. We don't have + the clipping path here, though. + 3. Allocate a permanent but small buffer and process the image + piecewise. + + */ + { + NSCachedImageRep *cache; + NSAffineTransformStruct ts; + NSPoint p; + double x0, y0, x1, y1, w, h; + int gState; + NSGraphicsContext *ctxt1; + + /* Figure out how big we need to make the window that'll hold the + transformed image. */ + p = [transform transformPoint: NSMakePoint(0, repSize.height)]; + x0 = x1 = p.x; + y0 = y1 = p.y; + + p = [transform transformPoint: NSMakePoint(repSize.width, 0)]; + x0 = MIN(x0, p.x); + y0 = MIN(y0, p.y); + x1 = MAX(x1, p.x); + y1 = MAX(y1, p.y); + + p = [transform transformPoint: NSMakePoint(repSize.width, repSize.height)]; + x0 = MIN(x0, p.x); + y0 = MIN(y0, p.y); + x1 = MAX(x1, p.x); + y1 = MAX(y1, p.y); + + p = [transform transformPoint: NSMakePoint(0, 0)]; + x0 = MIN(x0, p.x); + y0 = MIN(y0, p.y); + x1 = MAX(x1, p.x); + y1 = MAX(y1, p.y); + + x0 = floor(x0); + y0 = floor(y0); + x1 = ceil(x1); + y1 = ceil(y1); + + w = x1 - x0; + h = y1 - y0; + + /* This is where we want the origin of image space to be in our + window. */ + p.x -= x0; + p.y -= y0; + + cache = [[NSCachedImageRep alloc] + initWithSize: NSMakeSize(w, h) + depth: [[NSScreen mainScreen] depth] + separate: YES + alpha: YES]; + + [[[cache window] contentView] lockFocus]; + // The context of the cache window + ctxt1 = GSCurrentContext(); + DPScompositerect(ctxt1, 0, 0, w, h, NSCompositeClear); + + /* Set up the effective transform. We also save a gState with this + transform to make it easier to do the final composite. */ + ts = [transform transformStruct]; + ts.tX = p.x; + ts.tY = p.y; + [transform setTransformStruct: ts]; + [ctxt1 GSSetCTM: transform]; + gState = [ctxt1 GSDefineGState]; + + { + // Hack for xlib. Without it, transparent parts of images are black + [[NSColor clearColor] set]; + NSRectFill(NSMakeRect(0, 0, repSize.width, repSize.height)); + } + + [self drawInRect: NSMakeRect(0, 0, repSize.width, repSize.height)]; + + /* If we're doing a dissolve, use a DestinationIn composite to lower + the alpha of the pixels. */ + if (delta != 1.0) + { + DPSsetalpha(ctxt1, delta); + DPScompositerect(ctxt1, 0, 0, repSize.width, repSize.height, + NSCompositeDestinationIn); + } + + [[[cache window] contentView] unlockFocus]; + + DPScomposite(ctxt, srcRect.origin.x, srcRect.origin.y, + srcRect.size.width, srcRect.size.height, gState, + dstRect.origin.x, dstRect.origin.y, op); + + [ctxt GSUndefineGState: gState]; + + DESTROY(cache); + } +} + +/** + * Fallback implementation for subclasses which don't implement their + * own direct drawing + * TODO: explain how -draw, -drawInRect:, -drawAtPoint: clear their background + */ +- (BOOL) drawInRect: (NSRect)dstRect + fromRect: (NSRect)srcRect + operation: (NSCompositingOperation)op + fraction: (float)delta + respectFlipped: (BOOL)respectFlipped + hints: (NSDictionary*)hints +{ + NSAffineTransform *backup = nil; + NSGraphicsContext *ctx = GSCurrentContext(); + const BOOL compensateForFlip = (respectFlipped && [ctx isFlipped]); + const NSSize repSize = [self size]; + + // Handle abbreviated parameters + + if (NSEqualRects(srcRect, NSZeroRect)) + { + srcRect.size = repSize; + } + if (NSEqualSizes(dstRect.size, NSZeroSize)) + { + dstRect.size = repSize; + } + + if (dstRect.size.width <= 0 || dstRect.size.height <= 0 + || srcRect.size.width <= 0 || srcRect.size.height <= 0) + return NO; + + // Clip to image bounds + + if (srcRect.origin.x < 0) + srcRect.origin.x = 0; + if (srcRect.origin.y < 0) + srcRect.origin.y = 0; + if (NSMaxX(srcRect) > repSize.width) + srcRect.size.width = repSize.width - srcRect.origin.x; + if (NSMaxY(srcRect) > repSize.height) + srcRect.size.height = repSize.height - srcRect.origin.y; + + // FIXME: Hints are currently ignored + + // Compensate for flip + + if (compensateForFlip) + { + NSAffineTransform *newXform; + + backup = [ctx GSCurrentCTM]; + + newXform = [backup copy]; + [newXform translateXBy: dstRect.origin.x yBy: dstRect.origin.y + dstRect.size.height]; + [newXform scaleXBy: 1 yBy: -1]; + [ctx GSSetCTM: newXform]; + [newXform release]; + + dstRect.origin = NSMakePoint(0, 0); + } + + // Draw + + if ([ctx supportsDrawGState]) + { + [self nativeDrawInRect: dstRect fromRect: srcRect operation: op fraction: delta]; + } + else + { + [self guiDrawInRect: dstRect fromRect: srcRect operation: op fraction: delta]; + } + + // Undo flip compensation + + if (compensateForFlip) + { + [ctx GSSetCTM: backup]; + } + + return YES; +} + // NSCopying protocol - (id) copyWithZone: (NSZone *)zone {