gonum / plot

A repository for plotting and visualizing data
BSD 3-Clause "New" or "Revised" License
2.72k stars 202 forks source link

How to set transparency or opacity? #749

Closed ABetterCat closed 2 years ago

ABetterCat commented 2 years ago

How to set transparency or opacity of font?

sbinet commented 2 years ago

basically, it should just be a matter of playing with the alpha channel of the color you want to display your text.

ex:

func ExampleLabels_withOpacity() {
    p := plot.New()
    p.Title.Text = "Labels"
    p.X.Min = -5
    p.X.Max = +5
    p.Y.Min = 0
    p.Y.Max = +10

    const N = 50
    genXYs := func(n int) []plotter.XY {
        o := make([]plotter.XY, n)
        for i := range o {
            o[i] = plotter.XY{X: 0, Y: 5}
        }
        return o
    }
    genLabels := func(n int) []string {
        o := make([]string, n)
        for i := range o {
            o[i] = "OOOOOOOO"
        }
        return o
    }

    labels, err := plotter.NewLabels(plotter.XYLabels{
        XYs:    genXYs(N),
        Labels: genLabels(N),
    })
    if err != nil {
        log.Fatalf("could not creates labels plotter: %+v", err)
    }

    for i := range labels.TextStyle {
        v := &labels.TextStyle[i]
        v.Rotation = float64(i) * math.Pi / N
        v.Font.Size = 20
        var (
            ir uint8
            ig uint8
            ib uint8
        )
        switch i % 3 {
        case 0:
            ir = 255
        case 1:
            ig = 255
        case 2:
            ib = 255
        }
        v.Color = color.RGBA{R: ir, G: ig, B: ib, A: 127}
    }

    p.Add(labels)

    err = p.Save(10*vg.Centimeter, 10*vg.Centimeter, "testdata/labels_opacity.png")
    if err != nil {
        log.Fatalf("could not save plot: %+v", err)
    }
}

labels_opacity

does this help ?

ABetterCat commented 2 years ago

Thanks for your reply.It helps me. I want to generate a watermark with a transparent background. I referred to your code. But I can't generate watermark on a png with transparent background.Can you give me an example?

sbinet commented 2 years ago

like this ?

// An example of embedding an image in a plot with a watermark.
func ExampleImage_watermark() {
    p := plot.New()
    p.Title.Text = "A Logo w/ a watermark"

    // load an image
    f, err := os.Open("testdata/gopher.png")
    if err != nil {
        log.Fatalf("error opening image file: %v\n", err)
    }
    defer f.Close()

    img, err := png.Decode(f)
    if err != nil {
        log.Fatalf("error decoding image file: %v\n", err)
    }

    p.Add(plotter.NewImage(img, 0, 0, 20, 20))

    labels, err := plotter.NewLabels(plotter.XYLabels{
        XYs: []plotter.XY{
            {X: 5, Y: 5},
        },
        Labels: []string{"DRAFT"},
    })
    if err != nil {
        log.Fatalf("could not creates labels plotter: %+v", err)
    }
    labels.TextStyle[0].Color = color.RGBA{R:255, A: 120}
    labels.TextStyle[0].Rotation = math.Pi / 4
    labels.TextStyle[0].Font.Size = vg.Points(60)
    p.Add(labels)

    const (
        w = 10 * vg.Centimeter
        h = 10 * vg.Centimeter
    )

    err = p.Save(w, h, "testdata/image_plot_watermark.png")
    if err != nil {
        log.Fatalf("error saving image plot: %v\n", err)
    }

    // Output:
}

image_plot_watermark

sbinet commented 2 years ago

or do you want the image itself being somewhat transparent ? and by image, do you mean a uniform color ? or a plain image like the logo I've shown above ?

ABetterCat commented 2 years ago

I want the image itself being transparent totally.

And i want to write watermark on it.

---Original--- From: "Sebastien @.> Date: Fri, Aug 19, 2022 16:10 PM To: @.>; Cc: @.**@.>; Subject: Re: [gonum/plot] How to set transparency or opacity? (Issue #749)

or do you want the image itself being somewhat transparent ? and by image, do you mean a uniform color ? or a plain image like the logo I've shown above ?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

sbinet commented 2 years ago

ok.

well, it's not possible to draw a transparent data image (ie: using the plotter.NewImage scaffolding). it is possible to use an image as background, though:

// An example of embedding an image in a plot with a watermark.
func ExampleImage_watermark2() {
    p := plot.New()
    p.BackgroundColor = color.NRGBA{B: 255, A: 120}
    p.Title.Text = "A Logo w/ a watermark"

    // load an image
    f, err := os.Open("testdata/gopher.png")
    if err != nil {
        log.Fatalf("error opening image file: %v\n", err)
    }
    defer f.Close()

    img, err := png.Decode(f)
    if err != nil {
        log.Fatalf("error decoding image file: %v\n", err)
    }

    c := vgimg.PngCanvas{vgimg.NewWith(
        vgimg.UseBackgroundColor(color.Transparent),
        vgimg.UseImage(img.(draw.Image)),
    )}

    labels, err := plotter.NewLabels(plotter.XYLabels{
        XYs: []plotter.XY{
            {X: 5, Y: 5},
        },
        Labels: []string{"DRAFT"},
    })
    if err != nil {
        log.Fatalf("could not creates labels plotter: %+v", err)
    }
    labels.TextStyle[0].Color = color.RGBA{A: 100, R: 255}
    labels.TextStyle[0].Rotation = math.Pi / 4
    labels.TextStyle[0].Font.Size = vg.Points(60)

    p.Add(labels)

    p.Draw(vgdraw.New(c))

    o, err := os.Create("testdata/image_plot_watermark2.png")
    if err != nil {
        log.Fatalf("could not create output file: %+v", err)
    }

    _, err = c.WriteTo(o)
    if err != nil {
        log.Fatalf("error saving image plot: %v\n", err)
    }

    err = o.Close()
    if err != nil {
        log.Fatalf("error closing image plot: %v\n", err)
    }

    // Output:
}

image_plot_watermark2

sbinet commented 2 years ago

it's possible to draw transparent patches of colors with plotter.Image, but not "plain" images:


// An example of embedding an image in a plot with a watermark.
func ExampleImage_watermark2() {
    p := plot.New()
    //p.BackgroundColor = color.NRGBA{B: 255, A: 120}
    p.Title.Text = "A Logo w/ a watermark"

    // load an image
    f, err := os.Open("testdata/gopher.png")
    if err != nil {
        log.Fatalf("error opening image file: %v\n", err)
    }
    defer f.Close()

    img, err := png.Decode(f)
    if err != nil {
        log.Fatalf("error decoding image file: %v\n", err)
    }

    c := vgimg.PngCanvas{vgimg.NewWith(
        vgimg.UseBackgroundColor(color.Transparent),
    )}

    p.Add(plotter.NewImage(img, 0, 0, 20, 20))

    {
        img := image.NewRGBA(image.Rect(0, 0, 10, 10))
        draw.Draw(img, img.Bounds(), image.NewUniform(color.RGBA{G: 255, A: 120}), image.Point{}, draw.Over)
        p.Add(plotter.NewImage(img, 0, 0, 10, 10))
    }
    {
        img := image.NewRGBA(image.Rect(0, 0, 10, 10))
        draw.Draw(img, img.Bounds(), image.NewUniform(color.RGBA{B: 255, A: 120}), image.Point{}, draw.Over)
        p.Add(plotter.NewImage(img, 5, 5, 15, 15))
    }
    if true {
        f, err := os.Open("testdata/gopher_running.png")
        if err != nil {
            log.Fatalf("error opening image file: %v\n", err)
        }
        defer f.Close()

        img, err := png.Decode(f)
        if err != nil {
            log.Fatalf("error decoding image file: %v\n", err)
        }
        p.Add(plotter.NewImage(img, 10, 10, 20, 20))
    }

    labels, err := plotter.NewLabels(plotter.XYLabels{
        XYs: []plotter.XY{
            {X: 5, Y: 5},
        },
        Labels: []string{"DRAFT"},
    })
    if err != nil {
        log.Fatalf("could not creates labels plotter: %+v", err)
    }
    labels.TextStyle[0].Color = color.RGBA{A: 100, R: 255}
    labels.TextStyle[0].Rotation = math.Pi / 4
    labels.TextStyle[0].Font.Size = vg.Points(60)

    p.Add(labels)

    p.Draw(vgdraw.New(c))

    o, err := os.Create("testdata/image_plot_watermark3.png")
    if err != nil {
        log.Fatalf("could not create output file: %+v", err)
    }

    _, err = c.WriteTo(o)
    if err != nil {
        log.Fatalf("error saving image plot: %v\n", err)
    }

    err = o.Close()
    if err != nil {
        log.Fatalf("error closing image plot: %v\n", err)
    }

    // Output:
}

without the running gopher: image_plot_watermark2

with the running gopher: image_plot_watermark3

sbinet commented 2 years ago

to achieve this, we'd have to either introduce a new method to vg.Canvas, like:

DrawImageMask(rect vg.Rectangle, img image.Image, mask image.Image, mp vg.Point)

or add some state/context to specify the mask + mask offset.

(or just modify DrawImage signature to have the same than above, documenting that if mask==nil, no mask should be applied)

@kortschak WDYT ?

kortschak commented 2 years ago

If the alpha of the image obtained from the png file is appropriately adjusted, it will be transparent. I don't think there is anything that we need to do here.

Adjusted externally, but demonstrated with this

image_plot_watermark3

sbinet commented 2 years ago

here is an example:

// An example of embedding an image in a plot with a watermark.
func ExampleImage_watermark() {
    p := plot.New()
    p.Title.Text = "A Logo w/ a watermark"

    // load an image
    f, err := os.Open("testdata/gopher.png")
    if err != nil {
        log.Fatalf("error opening image file: %v\n", err)
    }
    defer f.Close()

    img, err := png.Decode(f)
    if err != nil {
        log.Fatalf("error decoding image file: %v\n", err)
    }

    p.Add(
        plotter.NewImage(img, 0, 0, 20, 20),
        plotter.NewImage(image.NewUniform(color.RGBA{G: 255, A: 120}), 0, 0, 10, 10),
        plotter.NewImage(image.NewUniform(color.RGBA{B: 255, A: 120}), 5, 5, 15, 15),
    )

    {
        f, err := os.Open("testdata/gopher_running.png")
        if err != nil {
            log.Fatalf("error opening image file: %v\n", err)
        }
        defer f.Close()

        src, err := png.Decode(f)
        if err != nil {
            log.Fatalf("error decoding image file: %v\n", err)
        }

        img := src.(*image.NRGBA)
        bnd := img.Bounds()
        for iy := 0; iy < bnd.Dy(); iy++ {
            for ix := 0; ix < bnd.Dx(); ix++ {
                r, g, b, _ := img.At(ix, iy).RGBA()
                img.SetNRGBA(ix, iy, color.NRGBA{uint8(r), uint8(g), uint8(b), 120})
            }
        }

        p.Add(plotter.NewImage(img, 10, 10, 20, 20))
    }

    labels, err := plotter.NewLabels(plotter.XYLabels{
        XYs: []plotter.XY{
            {X: 5, Y: 5},
        },
        Labels: []string{"DRAFT"},
    })
    if err != nil {
        log.Fatalf("could not creates labels plotter: %+v", err)
    }
    labels.TextStyle[0].Color = color.RGBA{A: 100, R: 255}
    labels.TextStyle[0].Rotation = math.Pi / 4
    labels.TextStyle[0].Font.Size = vg.Points(60)

    p.Add(labels)

    err = p.Save(10*vg.Centimeter, 10*vg.Centimeter, "testdata/image_plot_watermark.png")
    if err != nil {
        log.Fatalf("error saving image plot: %v\n", err)
    }

    // Output:
}
sbinet commented 2 years ago

closing as solved.

ABetterCat commented 2 years ago

When saving,it spends long time. And It never saved a png file.

ABetterCat commented 2 years ago

I mean when i use a total transparent png as the background, it output a white background image. transparent transparent_marked

kortschak commented 2 years ago

Code?

ABetterCat commented 2 years ago

I refered to the example

package main

import (
    "bytes"
    "fmt"
    "gonum.org/v1/plot/font"
    "gonum.org/v1/plot/font/liberation"
    "strings"

    "io/ioutil"
    "os"
    "path"

    "math"
    "math/rand"
    "time"

    "image"
    "image/color"
    "image/draw"
    "image/jpeg"
    "image/png"

    "gonum.org/v1/plot/vg"
    "gonum.org/v1/plot/vg/vgimg"
)

func init() {
    rand.Seed(time.Now().UnixNano())
}

// WaterMark for adding a watermark on the image
func WaterMark(img image.Image, markText string) (image.Image, error) {
    // image's length to canvas's length
    bounds := img.Bounds()
    w := vg.Length(bounds.Max.X) * vg.Inch / vgimg.DefaultDPI
    h := vg.Length(bounds.Max.Y) * vg.Inch / vgimg.DefaultDPI
    diagonal := vg.Length(math.Sqrt(float64(w*w + h*h)))

    // create a canvas, which width and height are diagonal
    c := vgimg.New(diagonal, diagonal)

    // draw image on the center of canvas
    rect := vg.Rectangle{}
    rect.Min.X = diagonal/2 - w/2
    rect.Min.Y = diagonal/2 - h/2
    rect.Max.X = diagonal/2 + w/2
    rect.Max.Y = diagonal/2 + h/2
    c.DrawImage(rect, img)

    cache := font.NewCache(liberation.Collection())
    face := cache.Lookup(font.Font{Typeface: "Courier",  Variant: "Serif"}, vg.Inch*0.15)

    // repeat the markText
    markTextWidth := face.Width(markText)
    // 空格
    space := ""
    for i := 0; i < len(markText); i++ {
        space += strings.Repeat(" ", 2)
    }
    unitText := markText
    for markTextWidth <= diagonal {
        markText += space + unitText
        markTextWidth = face.Width(markText)
    }

    // set the color of markText
    //c.SetColor(color.RGBA{225, 225, 225, 100})
    c.SetColor(color.RGBA{128, 42, 42, 100})

    // set a random angle between 0 and π/2
    //θ := math.Pi * rand.Float64() / 2
    c.Rotate(45)

    // set the lineHeight and add the markText

    //lineHeight := fontStyle.Extents().Height * 4
    lineHeight := face.Extents().Height *8
    n:=0
    for offset := -2 * diagonal; offset < 2*diagonal; offset += lineHeight {
        sss := addSpace(ifSpace(n), space)
        m :=sss + markText
        c.FillString(face, vg.Point{X: 0, Y: offset}, m)
        n += 1
    }

    jc := vgimg.PngCanvas{Canvas: c}
    buff := new(bytes.Buffer)
    jc.WriteTo(buff)
    img, _, err := image.Decode(buff)
    if err != nil {
        return nil, err
    }

    // get the center point of the image
    ctp := int(diagonal * vgimg.DefaultDPI / vg.Inch / 2)

    // cutout the marked image
    size := bounds.Size()
    bounds = image.Rect(ctp-size.X/2, ctp-size.Y/2, ctp+size.X/2, ctp+size.Y/2)
    rv := image.NewRGBA(bounds)
    draw.Draw(rv, bounds, img, bounds.Min, draw.Src)
    return rv, nil
}

func ifSpace(n int) bool {
    if n %2 == 1{
        return true
    }
    return false
}

func addSpace(ifSpace bool, space string)  string{
    if ifSpace {
        return space
    }
    return ""
}

// MarkingPicture for marking picture with text
func MarkingPicture(filepath, text string) (image.Image, error) {
    f, err := os.Open(filepath)
    if err != nil {
        return nil, err
    }
    defer f.Close()

    img, _, err := image.Decode(f)
    if err != nil {
        return nil, err
    }

    img, err = WaterMark(img, text)
    if err != nil {
        return nil, err
    }
    return img, nil
}

func writeTo(img image.Image, ext string) (rv *bytes.Buffer, err error) {
    ext = strings.ToLower(ext)
    rv = new(bytes.Buffer)
    switch ext {
    case ".jpg", ".jpeg":
        err = jpeg.Encode(rv, img, &jpeg.Options{Quality: 100})
    case ".png":
        err = png.Encode(rv, img)
    }
    return rv, err
}

func main() {
    text := "abc"

    if stat, err := os.Stat(target); err == nil && stat.IsDir() {
        files, _ := ioutil.ReadDir(target)
        for _, fn := range files {
            img, err := MarkingPicture(path.Join(target, fn.Name()), text)
            if err != nil {
                continue
            }

            ext := path.Ext(fn.Name())
            base := strings.Split(fn.Name(), ".")[0] + "_marked"
            f, err := os.Create(base + ext)
            if err != nil {
                panic(err)
            }

            buff, err := writeTo(img, ext)
            if err != nil {
                panic(err)
            }
            if _, err = buff.WriteTo(f); err != nil {
                panic(err)
            }
        }
    } else {
        img, err := MarkingPicture(target, text)
        if err != nil {
            panic(err)
        }

        ext := path.Ext(target)
        base := strings.Split(path.Base(target), ".")[0] + "_marked"
        f, err := os.Create(base + ext)
        if err != nil {
            panic(err)
        }

        buff, err := writeTo(img, ext)
        if err != nil {
            panic(err)
        }

        if _, err = buff.WriteTo(f); err != nil {
            panic(err)
        }
    }
}

Here is my png. transparent

sbinet commented 2 years ago

well, with the following code:

//go:build ignore

package main

import (
        "bytes"
        "image"
        "image/color"
        "image/draw"
        "image/jpeg"
        "image/png"
        "io/ioutil"
        "math"
        "math/rand"
        "os"
        "path"
        "strings"
        "time"

        "gonum.org/v1/plot/font"
        "gonum.org/v1/plot/font/liberation"
        "gonum.org/v1/plot/vg"
        "gonum.org/v1/plot/vg/vgimg"
)

func init() {
    rand.Seed(time.Now().UnixNano())
}

// WaterMark for adding a watermark on the image
func WaterMark(img image.Image, markText string) (image.Image, error) {
    // image's length to canvas's length
    bounds := img.Bounds()
    w := vg.Length(bounds.Max.X) * vg.Inch / vgimg.DefaultDPI
    h := vg.Length(bounds.Max.Y) * vg.Inch / vgimg.DefaultDPI
    diagonal := vg.Length(math.Sqrt(float64(w*w + h*h)))

    // create a canvas, which width and height are diagonal
    c := vgimg.New(diagonal, diagonal)

    // draw image on the center of canvas
    rect := vg.Rectangle{}
    rect.Min.X = diagonal/2 - w/2
    rect.Min.Y = diagonal/2 - h/2
    rect.Max.X = diagonal/2 + w/2
    rect.Max.Y = diagonal/2 + h/2
    c.DrawImage(rect, img)

    cache := font.NewCache(liberation.Collection())
    face := cache.Lookup(font.Font{Typeface: "Courier", Variant: "Serif"}, vg.Inch*0.15)

    // repeat the markText
    markTextWidth := face.Width(markText)
    space := ""
    for i := 0; i < len(markText); i++ {
        space += strings.Repeat(" ", 2)
    }
    unitText := markText
    for markTextWidth <= diagonal {
        markText += space + unitText
        markTextWidth = face.Width(markText)
    }

    // set the color of markText
    //c.SetColor(color.RGBA{225, 225, 225, 100})
    c.SetColor(color.RGBA{128, 42, 42, 100})

    // set a random angle between 0 and π/2
    //θ := math.Pi * rand.Float64() / 2
    c.Rotate(45)

    // set the lineHeight and add the markText

    //lineHeight := fontStyle.Extents().Height * 4
    lineHeight := face.Extents().Height * 8
    n := 0
    for offset := -2 * diagonal; offset < 2*diagonal; offset += lineHeight {
        sss := addSpace(ifSpace(n), space)
        m := sss + markText
        c.FillString(face, vg.Point{X: 0, Y: offset}, m)
        n += 1
    }

    jc := vgimg.PngCanvas{Canvas: c}
    buff := new(bytes.Buffer)
    _, err := jc.WriteTo(buff)
    if err != nil {
        return nil, err
    }

    img, _, err = image.Decode(buff)
    if err != nil {
        return nil, err
    }

    // get the center point of the image
    ctp := int(diagonal * vgimg.DefaultDPI / vg.Inch / 2)

    // cutout the marked image
    size := bounds.Size()
    bounds = image.Rect(ctp-size.X/2, ctp-size.Y/2, ctp+size.X/2, ctp+size.Y/2)
    rv := image.NewRGBA(bounds)
    draw.Draw(rv, bounds, img, bounds.Min, draw.Src)
    return rv, nil
}

func ifSpace(n int) bool {
    if n%2 == 1 {
        return true
    }
    return false
}

func addSpace(ifSpace bool, space string) string {
    if ifSpace {
        return space
    }
    return ""
}

// MarkingPicture for marking picture with text
func MarkingPicture(filepath, text string) (image.Image, error) {
    f, err := os.Open(filepath)
    if err != nil {
        return nil, err
    }
    defer f.Close()

    img, _, err := image.Decode(f)
    if err != nil {
        return nil, err
    }

    img, err = WaterMark(img, text)
    if err != nil {
        return nil, err
    }
    return img, nil
}

func writeTo(img image.Image, ext string) (rv *bytes.Buffer, err error) {
    ext = strings.ToLower(ext)
    rv = new(bytes.Buffer)
    switch ext {
    case ".jpg", ".jpeg":
        err = jpeg.Encode(rv, img, &jpeg.Options{Quality: 100})
    case ".png":
        err = png.Encode(rv, img)
    }
    return rv, err
}

func main() {
    text := "abc"
    target := "./plotter/testdata/gopher.png"
    if stat, err := os.Stat(target); err == nil && stat.IsDir() {
        files, _ := ioutil.ReadDir(target)
        for _, fn := range files {
            img, err := MarkingPicture(path.Join(target, fn.Name()), text)
            if err != nil {
                continue
            }

            ext := path.Ext(fn.Name())
            base := strings.Split(fn.Name(), ".")[0] + "_marked"
            f, err := os.Create(base + ext)
            if err != nil {
                panic(err)
            }

            buff, err := writeTo(img, ext)
            if err != nil {
                panic(err)
            }
            if _, err = buff.WriteTo(f); err != nil {
                panic(err)
            }
        }
    } else {
        img, err := MarkingPicture(target, text)
        if err != nil {
            panic(err)
        }

        ext := path.Ext(target)
        base := strings.Split(path.Base(target), ".")[0] + "_marked"
        f, err := os.Create(base + ext)
        if err != nil {
            panic(err)
        }

        buff, err := writeTo(img, ext)
        if err != nil {
            panic(err)
        }

        if _, err = buff.WriteTo(f); err != nil {
            panic(err)
        }
    }
}

I got this: gopher_marked

sbinet commented 2 years ago

that said, if you don't need the plotting features of gonum/plot (and "just" need to watermark an already existing image), perhaps another package might better suit your needs.

https://github.com/fogleman/gg comes to mind (even if it does sound a bit unmaintained)