diff --git a/ChangeLog b/ChangeLog index c76ab94d5..4f8040d7c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,44 @@ +2010-06-01 Quentin Mathe + + 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 * Source/NSDocumentController.m (-_setupOpenPanel): Disable diff --git a/Headers/AppKit/NSGraphicsContext.h b/Headers/AppKit/NSGraphicsContext.h index 65ccbcb4d..b8d9503d3 100644 --- a/Headers/AppKit/NSGraphicsContext.h +++ b/Headers/AppKit/NSGraphicsContext.h @@ -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; /* ----------------------------------------------------------------------- */ diff --git a/Headers/AppKit/NSView.h b/Headers/AppKit/NSView.h index a7d8e7659..6b7c9140a 100644 --- a/Headers/AppKit/NSView.h +++ b/Headers/AppKit/NSView.h @@ -118,6 +118,7 @@ PACKAGE_SCOPE int _gstate; void *_nextKeyView; void *_previousKeyView; + NSRect *_dirtyRects; @public /* diff --git a/Source/NSClipView.m b/Source/NSClipView.m index 6126e82a6..cfe5f5e27 100644 --- a/Source/NSClipView.m +++ b/Source/NSClipView.m @@ -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]; } diff --git a/Source/NSGraphicsContext.m b/Source/NSGraphicsContext.m index 501a5b904..ac899cf0c 100644 --- a/Source/NSGraphicsContext.m +++ b/Source/NSGraphicsContext.m @@ -1540,6 +1540,44 @@ NSGraphicsContext *GSCurrentContext(void) [self subclassResponsibility: _cmd]; } +/** +Returns whether the backend supports a GSDraw operator. + +By default, returns NO.
+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; +} + +/** +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. */ diff --git a/Source/NSImage.m b/Source/NSImage.m index 4ce828c27..a07675c21 100644 --- a/Source/NSImage.m +++ b/Source/NSImage.m @@ -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; diff --git a/Source/NSImageCell.m b/Source/NSImageCell.m index 6d3cc81e5..7b13d5508 100644 --- a/Source/NSImageCell.m +++ b/Source/NSImageCell.m @@ -28,6 +28,7 @@ #include "config.h" #include +#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 diff --git a/Source/NSImageRep.m b/Source/NSImageRep.m index 47a5dc427..da1875b19 100644 --- a/Source/NSImageRep.m +++ b/Source/NSImageRep.m @@ -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); diff --git a/Source/NSView.m b/Source/NSView.m index 1a553b1f9..51aab1c97 100644 --- a/Source/NSView.m +++ b/Source/NSView.m @@ -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]; }