libs-gui icon indicating copy to clipboard operation
libs-gui copied to clipboard

NSBezierPath does not clip gradients

Open optimisme opened this issue 2 years ago • 6 comments

NSBeizerPath does not clip gradients properly, see

                NSColor *startColor = GVThemeColorRGB(35, 135, 255, 1.0);
                NSColor *endColor = GVThemeColorRGB(0, 110, 255, 1.0);
                NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:startColor endingColor:endColor];

                NSBezierPath    *bezelPath2 = [NSBezierPath bezierPathWithRoundedRect:paddedFrame xRadius:15.0 yRadius:15.0];
                [NSGraphicsContext saveGraphicsState];
                [bezelPath2 setClip];
                [gradient drawInRect:frame angle:90.0]; 
                [NSGraphicsContext restoreGraphicsState];

                [[NSColor redColor] setStroke];
                [bezelPath2 setLineWidth:1.0];
                [bezelPath2 stroke];

The exepected behaviour is seeing nothing blue out of red circle

Screenshot 2023-12-23 at 01 15 23

optimisme avatar Dec 23 '23 00:12 optimisme

This bug report is rather disturbing. Up to now I had the impression that clipping was more or less working and there is no special magic build in for gradients. It should just respect the normal clipping mechanisms. Which backend are you using? At the moment cairo is the best supported backend, you really should be working with that.

fredkiefer avatar Dec 23 '23 22:12 fredkiefer

I installed GNUStep on Ubuntu 22.04 using this repo https://github.com/plaurent/gnustep-build For me it is the easiest way to install and updated version of GNUStep Looking at the install script: https://github.com/plaurent/gnustep-build/blob/master/ubuntu-22.04-clang-14.0-runtime-2.1/GNUstep-buildon-ubuntu2204.sh I think my installation is using Cairo backend.

optimisme avatar Dec 24 '23 06:12 optimisme

I attach two images from the next test source.

Top image is rendered by GNUStep:

  • First poligon is not clipping the gradient
  • The star looks right
  • The Oval did not apply the gradient right
- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];

    // Clipped gradient example
    NSColor *startColor = [NSColor greenColor];
    NSColor *endColor = [NSColor blueColor];
    NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:startColor endingColor:endColor];

    NSRect frame = NSMakeRect(50, 50, 100, 50);
    NSBezierPath *bezelPath = [NSBezierPath bezierPathWithRoundedRect:frame xRadius:15.0 yRadius:15.0];
    [NSGraphicsContext saveGraphicsState];
    [bezelPath setClip];
    [gradient drawInRect:frame angle:90.0];
    [NSGraphicsContext restoreGraphicsState];

    // Star
    NSColor *starStartColor = [NSColor redColor];
    NSColor *starEndColor = [NSColor yellowColor];
    NSGradient *starGradient = [[NSGradient alloc] initWithStartingColor:starStartColor endingColor:starEndColor];

    NSBezierPath *starPath = [NSBezierPath bezierPath];
    CGFloat starRadius = 50.0;  // Radi exterior de la estrella
    CGFloat innerRadius = starRadius * sin(M_PI / 10) / sin(7 * M_PI / 10); // Radi interior
    NSPoint center = NSMakePoint(250, 100); // Centre de l'estrella
    for (int i = 0; i < 10; i++) {
        // Calculem l'angle per a cada punt
        CGFloat angle = (CGFloat)(2 * M_PI / 10 * i);

        // Alternem entre radi exterior i interior
        CGFloat radius = i % 2 == 0 ? starRadius : innerRadius;
        CGFloat x = center.x + sin(angle) * radius;
        CGFloat y = center.y + cos(angle) * radius;

        if (i == 0) {
            [starPath moveToPoint:NSMakePoint(x, y)];
        } else {
            [starPath lineToPoint:NSMakePoint(x, y)];
        }
    }
    [starPath closePath];

    [NSGraphicsContext saveGraphicsState];
    [starPath setClip];
    [starGradient drawInBezierPath:starPath angle:90.0];
    [NSGraphicsContext restoreGraphicsState];
    
    // 45 degrees Oval
    NSRect ovalRect = NSMakeRect(350, 50, 100, 50);
    NSBezierPath *ovalPath = [NSBezierPath bezierPathWithOvalInRect:ovalRect];
    [NSGraphicsContext saveGraphicsState];
    NSAffineTransform *transform = [NSAffineTransform transform];
    NSPoint centerOval = NSMakePoint(NSMidX(ovalRect), NSMidY(ovalRect));
    [transform translateXBy:centerOval.x yBy:centerOval.y];
    [transform rotateByDegrees:45];
    [transform translateXBy:-centerOval.x yBy:-centerOval.y];
    [ovalPath transformUsingAffineTransform:transform];
    [ovalPath setClip];
    [NSGraphicsContext restoreGraphicsState];
    [gradient drawInBezierPath:ovalPath angle:-25.0];   
}
Captura de pantalla 2023-12-25 a les 17 59 18

optimisme avatar Dec 25 '23 17:12 optimisme

This is the project with the example test-bug-gradients.zip

optimisme avatar Dec 31 '23 09:12 optimisme

Thank you very much for these examples. The first looks like a bug in GNUstep and in the new year I hope to find time to investigate that. As for the third example I don't understand how this is working on macOS. What is the setClip doing here? I really need to spend more time on that one.

fredkiefer avatar Dec 31 '23 10:12 fredkiefer

I looked into the first issue and it is even worse. This is a limitation of the way we interact with the cairo library. When saving the graphics state we need to copy the state within cairo and there is a limitation that only rectangular clipping can be copied correctly. For all other cases a replacement gets used. In this case a rectangular one, which looks like no clipping at all. Maybe it would be possible to implement this interaction completely different, but although I wrote most of the original one, I don't see how to do it differently. This may be the reason for quite a few of the drawing artefacts you may see. As this happens every time a non rectangular shape gets used.

I still need to look into the third case.

fredkiefer avatar Jan 01 '24 21:01 fredkiefer