* 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
This commit is contained in:
Eric Wasylishen 2011-08-18 23:57:50 +00:00
parent e4979c9102
commit 99abec2ebf
4 changed files with 397 additions and 439 deletions

View file

@ -1,3 +1,14 @@
2011-08-18 Eric Wasylishen <ewasylishen@gmail.com>
* 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 <ewasylishen@gmail.com>
* Source/Functions.m (NSDrawNinePartImage): Pixel-align drawing rect using

View file

@ -34,6 +34,7 @@
#import <Foundation/NSGeometry.h>
#import <Foundation/NSObject.h>
#import <AppKit/AppKitDefines.h>
#import <AppKit/NSGraphicsContext.h>
@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

View file

@ -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

View file

@ -28,6 +28,7 @@
#include "config.h"
#include <string.h>
#import <Foundation/NSAffineTransform.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSData.h>
#import <Foundation/NSValue.h>
@ -36,14 +37,19 @@
#import <Foundation/NSNotification.h>
#import <Foundation/NSUserDefaults.h>
#import <Foundation/NSDebug.h>
#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
{