Fixed many drawing issues (many ones being related to the flipping).

See bug report #27782

In particular, fixed -[NSImage drawXXX] and -[NSImage composite/dissolveXXX] 
methods to work exactly as Cocoa when the Cairo backend is used.
Added a new draw operator (in addition to composite) to the backend. Cairo is 
the only backend that implements it for now.
Eliminated as many flipping checks as possible.

Warning: Untested with the winlib backend. 
You must update, recompile and install both Back and Gui.


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@30523 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
qmathe 2010-06-01 11:04:36 +00:00
parent 67adeb4133
commit 0eee79f7b5
9 changed files with 274 additions and 28 deletions

View file

@ -1,3 +1,44 @@
2010-06-01 Quentin Mathe <quentin.mathe@gmail.com>
Fixed many drawing issues (many ones being related to the flipping).
In particular, fixed -[NSImage drawXXX] methods to work exactly as
Cocoa and improve the drawing performance in some cases (the last two
points only holds with Cairo backend).
Eliminated as many flipping checks as possible.
Warning: Untested with the winlib backend.
* Headers/AppKit/NSGraphicsContext.h:
* Source/NSGraphicsContext.m:
(-supportsDrawGState, -GSdraw:toPoint:fromRect:operation:fraction:):
Added (see also the Backend Changelog).
* Source/NSImage.m
(-drawInRect:fromRect:operation:fraction:): Moved the previous
implementation to -guiDrawInRect:fromRect:operation:fraction: and
rewritten as a switch that checks -supportsDrawState.
(-guiDrawInRect:fromRect:operation:fraction:): New method identical to
the old -drawInRect:fromRect:operation:fraction:.
(-nativeDrawInRect:fromRect:operation:fraction:): Added.
New method that leverages the backend as much possible and implement
semantic that exactly matches Cocoa.
(-drawRepresentation:inRect:): Removed flipping
check.
* Source/NSImageRep.m (-drawInPoint:, -drawInRect:): Removed the
flipping checks.
* Source/NSImageCell.m (-drawInteriorWithFrame:inView:): Fixed
-drawInRect:fromRect:operation:fraction use as Apple doc suggests it
rather than using a negative height trick which doesn't work anymore
(at least with Cairo) and has never worked on Mac OS X.
* Source/NSView.m (-scrollRect:by:): Ajusted to do the copy bits on
the window gstate rather than the view gstate.
Required now that Cairo NSCopyBits/compositeGState won't compensate the
flipping when copyOnSelf is YES. Moreover -scrollRect:by: was broken
previously when the view wass not flipped (at least for Cairo).
Finally NSCopyBits() behaves in another way on Mac OS X when the view
gstate is used instead of the window gstate. By using the window gstate,
we can more easily change how NSCopyBits() handles the view gstate to
match Cocoa.
* Source/NSClipView.m (-setBoundsOrigin:): Turned off dubious code that
should probably be removed.
2010-06-01 Wolfgang Lux <wolfgang.lux@gmail.com>
* Source/NSDocumentController.m (-_setupOpenPanel): Disable

View file

@ -410,6 +410,12 @@ APPKIT_EXPORT NSGraphicsContext *GSCurrentContext(void);
fromRect: (NSRect)srcRect
operation: (NSCompositingOperation)op
fraction: (float)delta;
- (BOOL) supportsDrawGState;
- (void) GSdraw: (int)gstateNum
toPoint: (NSPoint)aPoint
fromRect: (NSRect)srcRect
operation: (NSCompositingOperation)op
fraction: (float)delta;
- (void) GSDrawImage: (NSRect)rect : (void *)imageref;
/* ----------------------------------------------------------------------- */

View file

@ -118,6 +118,7 @@ PACKAGE_SCOPE
int _gstate;
void *_nextKeyView;
void *_previousKeyView;
NSRect *_dirtyRects;
@public
/*

View file

@ -372,11 +372,11 @@ static inline NSRect integralRect (NSRect rect, NSView *view)
}
/* ?? TODO: Understand the following code - and add explanatory comment */
if ([NSView focusView] == _documentView)
/*if ([NSView focusView] == _documentView)
{
PStranslate (NSMinX (originalBounds) - aPoint.x,
NSMinY (originalBounds) - aPoint.y);
}
}*/
[_super_view reflectScrolledClipView: self];
}

View file

@ -1540,6 +1540,44 @@ NSGraphicsContext *GSCurrentContext(void)
[self subclassResponsibility: _cmd];
}
/** <override-dummy />
Returns whether the backend supports a GSDraw operator.
By default, returns NO.<br />
When a GSContext backend subclass overrides this method to return YES, the
backend must also implement -drawGState:fromRect:toPoint:op:fraction: in its
GSState subclass.
When YES is returned, -[NSImage drawXXX] methods that involves rotation,
scaling etc. will delegate as much as possible the image drawing to the backend,
rather than trying to emulate the resulting image in Gui by using intermediate
images to rotate and scale the content, and then composite the result with
-GScomposite:toPoint:fromRect:operation:fraction:.
Backends which doesn't implement -compositeGState:fromRect:toPoint:op:fraction:
can draw rotated or scaled images, but the semantic won't exactly match the
NSImage documentation in non-trivial cases. */
- (BOOL) supportsDrawGState
{
return NO;
}
/** <override-dummy />
Draws a gstate in a way that fully respects the destination transform,
unlike the GSComposite operator which ignores the rotation and the scaling
effect on the content.
Note: For the GScomposite operator, the scaling and rotation affects the
destination point but not the content. */
- (void) GSdraw: (int)gstateNum
toPoint: (NSPoint)aPoint
fromRect: (NSRect)srcRect
operation: (NSCompositingOperation)op
fraction: (float)delta
{
[self subclassResponsibility: _cmd];
}
/** Generic method to draw an image into a rect. The image is defined
by imageref, an opaque structure. Support for this method hasn't
been implemented yet, so it should not be used anywhere. */

View file

@ -797,7 +797,6 @@ repd_for_rep(NSArray *_reps, NSImageRep *rep)
return _color;
}
// Using the Image
- (void) compositeToPoint: (NSPoint)aPoint
operation: (NSCompositingOperation)op
@ -813,8 +812,8 @@ repd_for_rep(NSArray *_reps, NSImageRep *rep)
fromRect: (NSRect)aRect
operation: (NSCompositingOperation)op
{
[self compositeToPoint: aPoint
fromRect: aRect
[self compositeToPoint: aPoint
fromRect: aRect
operation: op
fraction: 1.0];
}
@ -850,27 +849,27 @@ repd_for_rep(NSArray *_reps, NSImageRep *rep)
if (cache != nil)
{
NSGraphicsContext *ctxt = GSCurrentContext();
NSRect rect = [cache rect];
NSDebugLLog(@"NSImage", @"composite rect %@ in %@",
NSStringFromRect(rect), NSStringFromRect(srcRect));
// Move the drawing rectangle to the origin of the image rep
// and intersect the two rects.
srcRect.origin.x += rect.origin.x;
srcRect.origin.y += rect.origin.y;
rect = NSIntersectionRect(srcRect, rect);
[GSCurrentContext() GScomposite: [[cache window] gState]
toPoint: aPoint
fromRect: rect
operation: op
fraction: delta];
[ctxt GScomposite: [[cache window] gState]
toPoint: aPoint
fromRect: rect
operation: op
fraction: delta];
}
else
{
NSRect rect;
rect = NSMakeRect(aPoint.x, aPoint.y, _size.width, _size.height);
NSRect rect = NSMakeRect(aPoint.x, aPoint.y, _size.width, _size.height);
[self drawRepresentation: rep inRect: rect];
}
}
@ -922,9 +921,6 @@ repd_for_rep(NSArray *_reps, NSImageRep *rep)
{
NSRect fillrect = aRect;
if ([[NSView focusView] isFlipped])
fillrect.origin.y -= _size.height;
[_color set];
NSRectFill(fillrect);
@ -957,10 +953,145 @@ repd_for_rep(NSArray *_reps, NSImageRep *rep)
fraction: delta];
}
- (void) drawInRect: (NSRect)dstRect
fromRect: (NSRect)srcRect
operation: (NSCompositingOperation)op
fraction: (float)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;
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;
}
}
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: [self bestRepresentationForDevice: nil]
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;
/* The size of the cache window that will hold the scaled image */
NSSize cacheSize = NSMakeSize(imgSize.width * widthScaleFactor,
imgSize.height * heightScaleFactor);
NSRect srcRectInCache = NSMakeRect(srcRect.origin.x * widthScaleFactor,
srcRect.origin.y * heightScaleFactor,
srcRect.size.width * widthScaleFactor,
srcRect.size.height * heightScaleFactor);
cache = [[NSCachedImageRep alloc]
initWithSize: cacheSize
depth: [[NSScreen mainScreen] depth]
separate: YES
alpha: YES];
[[[cache window] contentView] lockFocus];
cacheCtxt = GSCurrentContext();
/* Clear the cache window surface */
DPScompositerect(cacheCtxt, 0, 0, cacheSize.width, 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 */
[[self bestRepresentationForDevice: nil]
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, cacheSize.width, cacheSize.height,
NSCompositeDestinationIn);
}
[[[cache window] contentView] unlockFocus];
//NSLog(@"Draw in %@ from %@ from cache rect %@", NSStringFromRect(dstRect),
// NSStringFromRect(srcRect), NSStringFromRect(srcRectInCache));
[ctxt GSdraw: gState
toPoint: dstRect.origin
fromRect: srcRectInCache
operation: op
fraction: delta];
[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;
@ -1143,6 +1274,21 @@ repd_for_rep(NSArray *_reps, NSImageRep *rep)
}
}
- (void) drawInRect: (NSRect)dstRect
fromRect: (NSRect)srcRect
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];
}
}
- (void) addRepresentation: (NSImageRep *)imageRep
{
GSRepData *repd;

View file

@ -28,6 +28,7 @@
#include "config.h"
#include <Foundation/NSDebug.h>
#include "AppKit/NSAffineTransform.h"
#include "AppKit/NSCell.h"
#include "AppKit/NSGraphics.h"
#include "AppKit/NSImageCell.h"
@ -197,6 +198,7 @@ scaleProportionally(NSSize imageSize, NSRect canvasRect)
NSPoint position;
BOOL is_flipped = [controlView isFlipped];
NSSize imageSize, realImageSize;
NSAffineTransform *xform = nil;
NSDebugLLog(@"NSImageCell", @"NSImageCell drawInteriorWithFrame called");
@ -273,10 +275,13 @@ scaleProportionally(NSSize imageSize, NSRect canvasRect)
}
// account for flipped views
if (is_flipped)
if (is_flipped && controlView != nil)
{
position.y += imageSize.height;
imageSize.height = -imageSize.height;
xform = [NSAffineTransform transform];
[xform translateXBy: 0 yBy: [controlView bounds].size.height];
[xform scaleXBy: 1 yBy: -1];
[xform concat];
position.y = [controlView bounds].size.height - position.y - imageSize.height;
}
// draw!
@ -286,6 +291,12 @@ scaleProportionally(NSSize imageSize, NSRect canvasRect)
realImageSize.height)
operation: NSCompositeSourceOver
fraction: 1.0];
if (is_flipped && controlView != nil)
{
[xform invert];
[xform concat];
}
}
- (NSSize) cellSize

View file

@ -453,8 +453,6 @@ implement, so we can't do that. */
ctxt = GSCurrentContext();
if (aPoint.x != 0 || aPoint.y != 0)
{
if ([[ctxt focusView] isFlipped])
aPoint.y -= _size.height;
ctm = GSCurrentCTM(ctxt);
DPStranslate(ctxt, aPoint.x, aPoint.y);
reset = 1;
@ -480,8 +478,6 @@ implement, so we can't do that. */
ctxt = GSCurrentContext();
scale = NSMakeSize(NSWidth(aRect) / _size.width,
NSHeight(aRect) / _size.height);
if ([[ctxt focusView] isFlipped])
aRect.origin.y -= NSHeight(aRect);
ctm = GSCurrentCTM(ctxt);
DPStranslate(ctxt, NSMinX(aRect), NSMinY(aRect));
DPSscale(ctxt, scale.width, scale.height);

View file

@ -3002,9 +3002,16 @@ in the main thread.
destPoint = aRect.origin;
destPoint.x += delta.width;
destPoint.y += delta.height;
if ([self isFlipped])
{
destPoint.y += aRect.size.height;
}
//NSLog(@"destPoint %@ in %@", NSStringFromPoint(destPoint), NSStringFromRect(_bounds));
[self lockFocus];
NSCopyBits(0, aRect, destPoint);
//NSCopyBits(0, aRect, destPoint);
NSCopyBits([[self window] gState], [self convertRect: aRect toView: nil], destPoint);
[self unlockFocus];
}