llgcode / draw2d

2D rendering for different output (raster, pdf, svg)
BSD 2-Clause "Simplified" License
1.09k stars 105 forks source link

Alpha fills seem broken #150

Closed gordallott closed 5 years ago

gordallott commented 5 years ago

Hi all,

trying to use this library for some basic drawing but alpha fills seem broken, with entirely unexpected results. using the base2dimg backend (haven't tested with others)

the following code produces the following results

package main

import (
    "image"
    "image/color"

    "github.com/llgcode/draw2d/draw2dimg"
    "github.com/llgcode/draw2d/draw2dkit"
)

func main() {
    darkBlue := color.RGBA{0x48, 0x4d, 0x61, 0xff}
    alphaOrange := color.RGBA{0xce, 0x82, 0x3f, 0x1f}

    canvas := image.NewRGBA(image.Rect(0, 0, 256, 256))
    gc := draw2dimg.NewGraphicContext(canvas)

    // draw background 
    gc.SetFillColor(darkBlue)
    draw2dkit.Rectangle(gc, 0, 0, 256, 256)
    gc.Fill()

    gc.SetFillColor(alphaOrange)
    gc.SetStrokeColor(alphaOrange)

    // layer a transparent orange rectangle (filled)
    draw2dkit.Rectangle(gc, 64, 64, 128, 128)
    gc.Fill()

    // layer a transparent orange rectangle (stroked)
    draw2dkit.Rectangle(gc, 128, 128, 192, 192)
    gc.Stroke()

    draw2dimg.SaveToPngFile("alpha-test.png", canvas)
}

alpha-test

the green colour is entirely unexpected, stroke draws the correct colour but fill does its own thing

llgcode commented 5 years ago

Hi @gordallott , I think the problem comes from you forgot to call gc.beginPath() before defining a new rectangle. Without beginPath you add a rectangle each time to the current path so it fill or stroke over the previous rectangle.

regards

gordallott commented 5 years ago

adding the gc.BeginPaths() produces no changes. it wouldn't really make sense for that to have any effect, the Stroke/Fill's already get around that problem and the green colour is nothing like any other colour

llgcode commented 5 years ago
package main

import (
    "image"
    "image/color"

    "github.com/llgcode/draw2d"
    "github.com/llgcode/draw2d/draw2dimg"
    "github.com/llgcode/draw2d/draw2dkit"
    "github.com/llgcode/draw2d/draw2dpdf"
    "github.com/llgcode/draw2d/draw2dsvg"
)

func main() {

    canvas := image.NewRGBA(image.Rect(0, 0, 256, 256))
    gc2d := draw2dimg.NewGraphicContext(canvas)
    draw(gc2d)
    draw2dimg.SaveToPngFile("alpha-test.png", canvas)

    svg := draw2dsvg.NewSvg()
    gcsvg := draw2dsvg.NewGraphicContext(svg)
    draw(gcsvg)
    draw2dsvg.SaveToSvgFile("alpha-test.svg", svg)

    pdf := draw2dpdf.NewPdf("L", "mm", "A4")
    gcpdf := draw2dpdf.NewGraphicContext(pdf)
    draw(gcpdf)
    draw2dpdf.SaveToPdfFile("alpha-test.pdf", pdf)
}

func draw(gc draw2d.GraphicContext) {

    darkBlue := color.RGBA{0x48, 0x4d, 0x61, 0xff}
    alphaOrange := color.RGBA{0xce, 0x82, 0x3f, 0x1f}
    // draw background
    gc.SetFillColor(darkBlue)
    draw2dkit.Rectangle(gc, 0, 0, 256, 256)
    gc.Fill()

    gc.SetFillColor(alphaOrange)
    gc.SetStrokeColor(alphaOrange)

    // layer a transparent orange rectangle (filled)
    draw2dkit.Rectangle(gc, 64, 64, 128, 128)
    gc.Fill()

    // layer a transparent orange rectangle (stroked)
    draw2dkit.Rectangle(gc, 128, 128, 192, 192)
    gc.Stroke()
}

I tried with other implementations that seems to better implement transparency alpha-test.pdf svg has the same result

paulhankin commented 5 years ago

color.RGBA is an "alpha pre-multiplied color". From the godoc for image/color/RGBA: "An alpha-premultiplied color component C has been scaled by alpha (A), so has valid values 0 <= C <= A."

So the orange color is wrong: "alphaOrange := color.RGBA{0xce, 0x82, 0x3f, 0x1f}" You need something like: color.RGBA{uint8(0xce 0x1f / 255), uint8(0x82 0x1f / 255), uint8(0xef * 0x1f / 255), 0x1f}

This has caught me out too in the past.

llgcode commented 5 years ago

Hi @paulhankin, Thanks for your help. draw2d uses RGBAPainter (go freetype) to fill color on the image. https://github.com/golang/freetype/blob/master/raster/paint.go#L156

paulhankin commented 5 years ago

Sorry, I don't think I was clear. I don't think this is a bug in the draw2d code -- the color provided -- color.RGBA{0xce, 0x82, 0x3f, 0x1f} -- in the example isn't valid (because a valid RGBA color has R<=A, G<=A, B<=A). The invalid color causes overflow in the drawing code, and so the weird output color.

llgcode commented 5 years ago

Thank you @paulhankin. Actually I did not understand sorry. It's a great news. Thank you for your analysis.