2
votes

Problem description

[[NSColor clearColor] set];
NSRectFillUsingOperation(win, NSCompositeCopy);

"copy" result

I also tried NSCompositeClear, NSCompositeDestinationOut, but none of them worked as I wish. I want to erase the background in the blue clip window, so its content will be brighter. I've learned Cocoa and obj-c for merely a week, so maybe I misunderstood something about it. I really want to know how to realize this in Cocoa Framework?

#import "ImageCutterView.h"
#define CONTROL_POINT_SIZE 6
#define CLIP_WINDOW_MIN_SIZE 100
#define CLIP_WINDOW_MAX_SIZE 200

@implementation ImageCutterView {
    NSRect win;
    NSRect controlPoints[4];
    CGFloat gridLinePattern[2];
    NSBezierPath* gridLines[4];
    NSPoint p1,p2;

    BOOL startDragging;
    BOOL topLeftDragging;
    BOOL topRightDragging;
    BOOL bottomLeftDragging;
    BOOL bottomRightDragging;

    NSColor *borderColor;
    NSColor *gridColor;
    NSColor *controlPointBorderColor;
    NSColor *controlPointFillColor;
    NSColor *backgroundColor;

    CGFloat distance, preDistance;

    NSCursor *diagonal1, *diagonal2;
}

-(void)calculateControlPolints{
    CGFloat w, h;
    w = win.size.width;
    h = win.size.height;
    CGFloat ox,oy;
    ox = win.origin.x;
    oy = win.origin.y;
    controlPoints[0].origin.x = ox - CONTROL_POINT_SIZE/2;
    controlPoints[0].origin.y = oy - CONTROL_POINT_SIZE/2;
    controlPoints[0].size.width = CONTROL_POINT_SIZE;
    controlPoints[0].size.height = CONTROL_POINT_SIZE;
    controlPoints[1].origin.x = ox - CONTROL_POINT_SIZE/2;
    controlPoints[1].origin.y = oy + h - CONTROL_POINT_SIZE/2;
    controlPoints[1].size.width = CONTROL_POINT_SIZE;
    controlPoints[1].size.height = CONTROL_POINT_SIZE;
    controlPoints[2].origin.x = ox + w - CONTROL_POINT_SIZE/2;
    controlPoints[2].origin.y = oy + h - CONTROL_POINT_SIZE/2;
    controlPoints[2].size.width = CONTROL_POINT_SIZE;
    controlPoints[2].size.height = CONTROL_POINT_SIZE;
    controlPoints[3].origin.x = ox + w - CONTROL_POINT_SIZE/2;
    controlPoints[3].origin.y = oy - CONTROL_POINT_SIZE/2;
    controlPoints[3].size.width = CONTROL_POINT_SIZE;
    controlPoints[3].size.height = CONTROL_POINT_SIZE;
}
-(void)calculateGridLines{
    CGFloat w, h, w3, h3;
    w = win.size.width;
    h = win.size.height;
    w3 = w/3;
    h3 = h/3;
    CGFloat ox,oy;
    ox = win.origin.x;
    oy = win.origin.y;
    gridLines[0] = [NSBezierPath bezierPath];
    [gridLines[0] setLineDash:gridLinePattern count:2 phase:0.0];
    [gridLines[0] moveToPoint:NSMakePoint(ox + w3, oy + h)];
    [gridLines[0] lineToPoint:NSMakePoint(ox + w3, oy)];
    gridLines[1] = [NSBezierPath bezierPath];
    [gridLines[1] setLineDash:gridLinePattern count:2 phase:0.0];
    [gridLines[1] moveToPoint:NSMakePoint(ox + w3*2, oy + h)];
    [gridLines[1] lineToPoint:NSMakePoint(ox + w3*2, oy)];
    gridLines[2] = [NSBezierPath bezierPath];
    [gridLines[2] setLineDash:gridLinePattern count:2 phase:0.0];
    [gridLines[2] moveToPoint:NSMakePoint(ox, oy + h3*2)];
    [gridLines[2] lineToPoint:NSMakePoint(ox + w, oy + h3*2)];
    gridLines[3] = [NSBezierPath bezierPath];
    [gridLines[3] setLineDash:gridLinePattern count:2 phase:0.0];
    [gridLines[3] moveToPoint:NSMakePoint(ox, oy + h3)];
    [gridLines[3] lineToPoint:NSMakePoint(ox +w, oy + h3)];
}

-(NSCursor *)getSystemCursorByName:(NSString *)cursorName {
    NSString *cursorPath =     [@"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors" stringByAppendingPathComponent:cursorName];
    NSImage *image = [[NSImage alloc] initByReferencingFile:[cursorPath stringByAppendingPathComponent:@"cursor.pdf"]];
    NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"info.plist"]];
    NSCursor *cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint([[info valueForKey:@"hotx"] doubleValue], [[info valueForKey:@"hoty"] doubleValue])];
    return cursor;
}

-(void)awakeFromNib {
    _showGrid = YES;
    CGFloat cx,cy;
    cx = self.frame.size.width / 2;
    cy = self.frame.size.height / 2;
    win.size.width = (CLIP_WINDOW_MIN_SIZE + CLIP_WINDOW_MAX_SIZE) / 2;
    win.size.height = (CLIP_WINDOW_MIN_SIZE + CLIP_WINDOW_MAX_SIZE) / 2;
    win.origin = NSMakePoint(cx - win.size.width/2, cy - win.size.height/2);

    gridLinePattern[0] = 6.0;
    gridLinePattern[1] = 4.0;

    startDragging = NO;
    topLeftDragging = NO;
    topRightDragging = NO;
    bottomLeftDragging = NO;
    bottomRightDragging = NO;

    borderColor = [NSColor colorWithSRGBRed:(0x44 + 1.0)/256 green:(0xce + 1.0)/256 blue:(0xf6 + 1.0)/256 alpha:0.8f];
    gridColor = [NSColor colorWithSRGBRed:(0x44 + 1.0)/256 green:(0xce + 1.0)/256 blue:(0xf6 + 1.0)/256 alpha:0.8f];
    controlPointBorderColor = [NSColor colorWithSRGBRed:(0x44 + 1.0)/256 green:(0xce + 1.0)/256 blue:(0xf6 + 1.0)/256 alpha:0.8f];
    controlPointFillColor = [NSColor whiteColor];
    backgroundColor = [NSColor colorWithSRGBRed:0.7 green:0.7 blue:0.7 alpha:0.4f];
    //backgroundColor = [NSColor blackColor];

    [self calculateControlPolints];
    [self calculateGridLines];

    diagonal1 = [self getSystemCursorByName:@"resizenorthwestsoutheast"];
    diagonal2 = [self getSystemCursorByName:@"resizenortheastsouthwest"];

};

//-(BOOL)isOpaque{
//    return YES;
//}

- (void)resetCursorRects
{
    [super resetCursorRects];
    if (diagonal1) {
        [self addCursorRect:controlPoints[1] cursor: diagonal1];
        [self addCursorRect:controlPoints[3] cursor: diagonal1];
    }
    if(diagonal2){
        [self addCursorRect:controlPoints[0] cursor: diagonal2];
        [self addCursorRect:controlPoints[2] cursor: diagonal2];
    }
}

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];
    NSGraphicsContext* ctx = [NSGraphicsContext currentContext];
    [ctx setShouldAntialias:NO];
    [NSBezierPath setDefaultLineWidth:0.0];
    [backgroundColor set];
    [NSBezierPath fillRect:dirtyRect];

    /*
    [ctx saveGraphicsState];
    //[ctx setCompositingOperation:NSCompositeDestinationOut];
    //[ctx setCompositingOperation:NSCompositeDestinationOver];
    [ctx setCompositingOperation:NSCompositeCopy];
    [[NSColor clearColor] set];
    [NSBezierPath fillRect:win];
    [ctx restoreGraphicsState];*/

    [[NSColor clearColor] set];
    //[[NSColor colorWithSRGBRed:1.0f green:1.0f blue:1.0f alpha:0.0f] set];
    NSRectFillUsingOperation(win, NSCompositeCopy);

    [[NSColor colorWithSRGBRed:(0x44 + 1.0)/256 green:(0xce + 1.0)/256 blue:(0xf6 + 1.0)/256 alpha:0.8f] set];
    [NSBezierPath strokeRect:win];
    if (self.showGrid) {
        for (int i=0; i<4; i++) {
            [gridLines[i] stroke];
        }
    }
    [[NSColor colorWithSRGBRed:(0x44 + 1.0)/256 green:(0xce + 1.0)/256 blue:(0xf6 + 1.0)/256 alpha:1.0f] set];
    for (int i=0; i<4; i++) {
        [NSBezierPath strokeRect:controlPoints[i]];
    }
}

-(void)mouseDown:(NSEvent *)theEvent {
    p1 = [theEvent locationInWindow];
    NSPoint tp = [self convertPoint:p1 fromView:nil];
    if (NSPointInRect(tp, controlPoints[0])){
        bottomLeftDragging = YES;
    } else if (NSPointInRect(tp, controlPoints[1])){
        topLeftDragging = YES;
        p1 = NSMakePoint(win.origin.x, win.origin.y + win.size.height);
        preDistance = ((tp.x-p1.x) - (tp.y - p1.y)) / 2;
    } else if (NSPointInRect(tp, controlPoints[2])){
        topRightDragging = YES;
    } else if (NSPointInRect(tp, controlPoints[3])){
        bottomRightDragging = YES;
    } else if (NSPointInRect(tp, win)) {
        startDragging = YES;
    }
}

-(void)mouseDragged:(NSEvent *)theEvent {
    p2 = [theEvent locationInWindow];
    if (startDragging) {
        NSAffineTransform *transfrom = [NSAffineTransform transform];
        [transfrom translateXBy:p2.x - p1.x yBy:p2.y - p1.y];
        win.origin = [transfrom transformPoint:win.origin];
        for(int i=0;i<4;i++){
            controlPoints[i].origin = [transfrom transformPoint:controlPoints[i].origin];
        }
        for (int i=0; i<4; i++) {
            [gridLines[i] transformUsingAffineTransform:transfrom];
        }
        [self setNeedsDisplay:YES];
        p1 = p2;
    } else if (topLeftDragging) {
        p2 = [self convertPoint:p2 fromView:nil];
        distance = ((p2.x-p1.x) - (p2.y - p1.y)) / 2;
        CGFloat dSize = distance - preDistance;
        CGFloat newSize = win.size.width - dSize;
        if (newSize >= CLIP_WINDOW_MIN_SIZE && newSize <= CLIP_WINDOW_MAX_SIZE){
            preDistance = distance;
            win.size.width = newSize;
            win.size.height = newSize;
            win.origin.x += dSize;
            [self calculateControlPolints];
            [self calculateGridLines];
            [self setNeedsDisplay:YES];
        }
    } else if (topRightDragging) {
    } else if (bottomLeftDragging) { 
    } else if (bottomRightDragging) { 
    }
}

- (void)mouseUp:(NSEvent *)theEvent {
    if (startDragging){
        startDragging = NO;
    } else if (topLeftDragging){
        topLeftDragging = NO;  
    } else if (topRightDragging){
        topRightDragging = NO;
    } else if (bottomLeftDragging) {
        bottomLeftDragging = NO;
    } else if (bottomRightDragging) {
        bottomRightDragging = NO;
    }
    [self resetCursorRects];
}
@end

I found a problem same as mine asked 13 years ago. http://www.cocoabuilder.com/archive/cocoa/47367-clearing-an-nsview.html

[Update 1] I always wonder why NSCompositeClear does NOT clear the area of my source properly (don't show me a black hole). So I came up with an idea that the weird things happened because I draw to the screen. What if I draw to a NSGraphicsContext of a bitmap image?

@implementation FooView {
    NSBitmapImageRep *offscreenRep;
    NSRect imgRect;
    NSSize imgSize;
}
-(void)awakeFromNib{
    imgRect = NSMakeRect(0.0, 0.0, 100.0, 100.0);
    imgSize = imgRect.size;

    offscreenRep = [[NSBitmapImageRep alloc]
                                   initWithBitmapDataPlanes:NULL
                                   pixelsWide:imgSize.width
                                   pixelsHigh:imgSize.height
                                   bitsPerSample:8
                                   samplesPerPixel:4
                                   hasAlpha:YES
                                   isPlanar:NO
                                   colorSpaceName:NSDeviceRGBColorSpace
                                   bitmapFormat:NSAlphaFirstBitmapFormat
                                   bytesPerRow:0
                                   bitsPerPixel:0];
}
- (void)drawRect:(NSRect)dirtyRect {
    // set offscreen context
    NSGraphicsContext *g = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
    [NSGraphicsContext saveGraphicsState];
    [NSGraphicsContext setCurrentContext:g];

    //draw something here
    NSBezierPath *circle = [NSBezierPath bezierPathWithOvalInRect:NSMakeRect(20, 20, 50, 50)];
    NSBezierPath *rectangle = [NSBezierPath bezierPathWithRect:NSMakeRect(5, 5, 40, 40)];
    [[NSColor greenColor] set];
    [circle fill];
    [g setCompositingOperation:NSCompositeClear];
    [[NSColor redColor] set];
    [rectangle fill];

    // done drawing, so set the current context back to what it was
    [NSGraphicsContext restoreGraphicsState];

    // create an NSImage and add the rep to it
     NSImage *img = [[NSImage alloc] initWithSize:imgSize];
    [img addRepresentation:offscreenRep];

    // then go on to save or view the NSImage
    [img drawAtPoint:NSMakePoint(0.0, 0.0)
             fromRect: NSMakeRect(0.0, 0.0, 100.0, 100.0)
            operation: NSCompositeSourceOver
             fraction: 1.0];
}
...

I copied the drawing code from (Mac OS X: Drawing into an offscreen NSGraphicsContext using CGContextRef C functions has no effect. Why?) and made some changes. It worked! So it proved my assumption above. However, there are 2 new questions now.

  1. Why it is different between drawing to a screen and an image?
  2. The code above of drawing to an image is not efficient. The content flashes when I move the window.

[Update 2] Question 1: Maybe there are no differences. "layer 0": black, "layer 1": window content, "layer 2": my image layer. When I draw to screen, I actually draw on layer 1. When I draw to an image, I actually on my custom "layer" 2.

2
Probably using layers is the better idea.Droppy
@Droppy CALayer? I have no idea with it. Can you give me some instructions. I wonder why "copy" with "clearColor" leads to black background and "destination out" leads to nothing just like the first illustration.bitdancer
Did you define the NSView as opaque?iCaramba
@VWGolf2 Yes I tried it. But the result is the same. I commented the code in the last code snippet.bitdancer
@Droppy I fix my mistake. You probably meant CGLayer. I don't hear about it until reading the "Quartz 2D Programming Guide".bitdancer

2 Answers

1
votes

First of all, thank you for writing such a well-thought out question and doing so much research on the topic!

I was struggling with the exact same problem. And, I was also mystified on why the blending modes you tried didn't work. I've come to believe that the issue was my (and your) mental model of what exactly is happening during NSView's drawRect operation. This is not definitive by any means, just my interpretation of what's going on.

In the case of an image, there are now two "pixel buffers", the image and the contents of the screen the NSView occupies. Blending modes work in these cases because there is both a source and dest. When you are just drawing/filling in drawRect, any draw operations necessarily overwrite the pixels on the screen. So, you cannot undo pixel changes by using different composite operations. The dest is always the current state of the screen, not some temporary pixel buffer created for your view's drawing. Again, not an expert.

For a solution that might work for you, take a look at https://stackoverflow.com/a/32200161/288405.

-2
votes

You are using NSRectFillUsingOperation which paints using the fill colour, not the regular drawing colour. So you should use 'setFill' instead of 'set' in NSColor. Eg:

[[NSColor clearColor] setFill];
NSRectFillUsingOperation(win, NSCompositeCopy);