├── .gitignore
├── testdata
├── arcs.ivg
├── arcs.png
├── blank.ivg
├── blank.png
├── cowbell.ivg
├── cowbell.png
├── favicon.ivg
├── favicon.png
├── gradient.ivg
├── gradient.png
├── elliptical.ivg
├── elliptical.png
├── lod-polygon.ivg
├── lod-polygon.png
├── favicon.pink.png
├── lod-polygon.64.png
├── action-info.hires.ivg
├── action-info.hires.png
├── action-info.lores.ivg
├── action-info.lores.png
├── video-005.primitive.ivg
├── video-005.primitive.png
├── blank.ivg.disassembly
├── action-info.svg
├── lod-polygon.ivg.disassembly
├── action-info.hires.ivg.disassembly
├── action-info.lores.ivg.disassembly
├── video-005.primitive.svg
├── README
├── elliptical.ivg.disassembly
├── cowbell.svg
├── arcs.ivg.disassembly
├── favicon.svg
└── gradient.ivg.disassembly
├── go.mod
├── mdicons
├── README.md
├── types.go
├── parsepath.go
├── parse.go
├── parsepathdata.go
├── parsedir.go
└── parsefile.go
├── raster
├── gio
│ ├── go.mod
│ ├── paint.go
│ ├── example
│ │ ├── arrow
│ │ │ └── main.go
│ │ ├── info
│ │ │ └── main.go
│ │ ├── blend
│ │ │ └── main.go
│ │ ├── gradients
│ │ │ └── main.go
│ │ ├── cowbell
│ │ │ └── main.go
│ │ └── favicon
│ │ │ └── main.go
│ ├── widget.go
│ ├── rasterizer.go
│ └── go.sum
├── gradient.go
├── img
│ └── rasterizer.go
├── logger.go
├── rasterizer.go
└── LICENSE
├── color_test.go
├── cmd
├── mdicons
│ ├── main.go
│ └── test
│ │ └── icons_test.go
└── disivg
│ └── main.go
├── doc.go
├── render
├── render_test.go
└── gradient.go
├── destination.go
├── LICENSE
├── ivg_test.go
├── example_test.go
├── decode
├── buffer.go
├── decode_test.go
└── buffer_test.go
├── go.sum
├── encode
├── buffer.go
├── buffer_test.go
└── encode_test.go
├── render_test.go
├── ivg.go
├── logger.go
├── README.md
├── color.go
└── generate
└── generate.go
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/testdata/arcs.ivg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/arcs.ivg
--------------------------------------------------------------------------------
/testdata/arcs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/arcs.png
--------------------------------------------------------------------------------
/testdata/blank.ivg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/blank.ivg
--------------------------------------------------------------------------------
/testdata/blank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/blank.png
--------------------------------------------------------------------------------
/testdata/cowbell.ivg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/cowbell.ivg
--------------------------------------------------------------------------------
/testdata/cowbell.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/cowbell.png
--------------------------------------------------------------------------------
/testdata/favicon.ivg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/favicon.ivg
--------------------------------------------------------------------------------
/testdata/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/favicon.png
--------------------------------------------------------------------------------
/testdata/gradient.ivg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/gradient.ivg
--------------------------------------------------------------------------------
/testdata/gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/gradient.png
--------------------------------------------------------------------------------
/testdata/elliptical.ivg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/elliptical.ivg
--------------------------------------------------------------------------------
/testdata/elliptical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/elliptical.png
--------------------------------------------------------------------------------
/testdata/lod-polygon.ivg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/lod-polygon.ivg
--------------------------------------------------------------------------------
/testdata/lod-polygon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/lod-polygon.png
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/reactivego/ivg
2 |
3 | go 1.17
4 |
5 | require golang.org/x/image v0.7.0
6 |
--------------------------------------------------------------------------------
/testdata/favicon.pink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/favicon.pink.png
--------------------------------------------------------------------------------
/testdata/lod-polygon.64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/lod-polygon.64.png
--------------------------------------------------------------------------------
/testdata/action-info.hires.ivg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/action-info.hires.ivg
--------------------------------------------------------------------------------
/testdata/action-info.hires.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/action-info.hires.png
--------------------------------------------------------------------------------
/testdata/action-info.lores.ivg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/action-info.lores.ivg
--------------------------------------------------------------------------------
/testdata/action-info.lores.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/action-info.lores.png
--------------------------------------------------------------------------------
/testdata/video-005.primitive.ivg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/video-005.primitive.ivg
--------------------------------------------------------------------------------
/testdata/video-005.primitive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reactivego/ivg/HEAD/testdata/video-005.primitive.png
--------------------------------------------------------------------------------
/testdata/blank.ivg.disassembly:
--------------------------------------------------------------------------------
1 | 89 49 56 47 IconVG Magic identifier
2 | 00 Number of metadata chunks: 0
3 |
--------------------------------------------------------------------------------
/testdata/action-info.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mdicons/README.md:
--------------------------------------------------------------------------------
1 | # Parse Library for Material Icons
2 |
3 | This library provides a specific parser for Material Icons as published by Google.
4 | The library is used by the cmd/mdicons command line utility to generate ivg icons in a .go file from the svg files found in a material icons folder structure.
5 |
6 |
--------------------------------------------------------------------------------
/raster/gio/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/reactivego/ivg/raster/gio
2 |
3 | go 1.20
4 |
5 | require (
6 | gioui.org v0.1.0
7 | github.com/reactivego/gio v0.0.4
8 | github.com/reactivego/ivg v0.1.2
9 | golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
10 | golang.org/x/image v0.9.0
11 | )
12 |
13 | require (
14 | eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d // indirect
15 | gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7 // indirect
16 | gioui.org/shader v1.0.6 // indirect
17 | github.com/go-text/typesetting v0.0.0-20230717141307-09c70c30a055 // indirect
18 | golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect
19 | golang.org/x/sys v0.10.0 // indirect
20 | golang.org/x/text v0.11.0 // indirect
21 | )
22 |
--------------------------------------------------------------------------------
/color_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package ivg_test
6 |
7 | import (
8 | "image/color"
9 | "testing"
10 |
11 | "github.com/reactivego/ivg"
12 | )
13 |
14 | func TestBlendColor(t *testing.T) {
15 | // This example comes from doc.go. Look for "orange" in the "Colors"
16 | // section.
17 | pal := [64]color.RGBA{
18 | 2: {0xff, 0xcc, 0x80, 0xff}, // "Material Design Orange 200".
19 | }
20 | cReg := [64]color.RGBA{}
21 | got := ivg.BlendColor(0x40, 0x7f, 0x82).Resolve(&pal, &cReg)
22 | want := color.RGBA{0x40, 0x33, 0x20, 0x40} // 25% opaque "Orange 200", alpha-premultiplied.
23 | if got != want {
24 | t.Errorf("\ngot %x\nwant %x", got, want)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/raster/gradient.go:
--------------------------------------------------------------------------------
1 | package raster
2 |
3 | import "image/color"
4 |
5 | // GradientConfig interface could be used in the future to extract the gradient
6 | // configuration of a source image and have it generated on the GPU.
7 | type GradientConfig interface {
8 | // GradientShape returns 0 for a linear gradient and 1 for a radial
9 | // gradient.
10 | GradientShape() int
11 | // SpreadMethod returns 0 for 'none', 1 for 'pad', 2 for 'reflect', 3 for
12 | // 'repeat'.
13 | SpreadMethod() int
14 | // StopColors returns the colors of the gradient stops.
15 | StopColors() []color.RGBA
16 | // StopOffsets returns the offsets of the gradient stops.
17 | StopOffsets() []float64
18 | // Transform is the pixel space to gradient space affine transformation
19 | // matrix.
20 | // | a b c |
21 | // | d e f |
22 | Transform() (a, b, c, d, e, f float64)
23 | }
24 |
--------------------------------------------------------------------------------
/cmd/mdicons/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "os"
8 |
9 | "github.com/reactivego/ivg/mdicons"
10 | )
11 |
12 | func main() {
13 | var pkg = flag.String("package", "icons", "the name of the package to generate")
14 | var test = flag.Bool("test", false, "pass this flag to generate data_test.go")
15 | var size = flag.Float64("size", 48, "width and height (in ideal vector space) of the "+
16 | "generated IVG graphic, regardless of the size of the input SVG")
17 | flag.Usage = func() {
18 | fmt.Fprintf(flag.CommandLine.Output(), "%[1]s is a tool for converting icons from svg to ivg.\n\n"+
19 | "Usage:\n\n"+
20 | " %[1]s [flags] directory\n\n"+
21 | "The flags are:\n\n", flag.CommandLine.Name())
22 | flag.PrintDefaults()
23 | fmt.Fprintln(flag.CommandLine.Output())
24 | }
25 | flag.Parse()
26 | if flag.NArg() != 1 {
27 | flag.Usage()
28 | os.Exit(2)
29 | }
30 | if err := mdicons.Parse(flag.Arg(0), *pkg, true, *test, float32(*size)); err != nil {
31 | log.Fatal(err.Error())
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/raster/gio/paint.go:
--------------------------------------------------------------------------------
1 | package gio
2 |
3 | import (
4 | "image"
5 | "image/draw"
6 |
7 | "gioui.org/op"
8 | "gioui.org/op/paint"
9 |
10 | "github.com/reactivego/ivg/decode"
11 | "github.com/reactivego/ivg/raster/img"
12 | "github.com/reactivego/ivg/render"
13 | )
14 |
15 | type PaintFunc func(*op.Ops, []byte, image.Rectangle, ...decode.DecodeOption)
16 |
17 | func GioPaint(ops *op.Ops, data []byte, rect image.Rectangle, opts ...decode.DecodeOption) {
18 | z := &Rasterizer{Ops: ops}
19 |
20 | r := &render.Renderer{}
21 | r.SetRasterizer(z, rect)
22 | decode.Decode(r, data, opts...)
23 | }
24 |
25 | func ImagePaint(ops *op.Ops, data []byte, rect image.Rectangle, opts ...decode.DecodeOption) {
26 | offset, bounds := rect.Min, image.Rectangle{Max: rect.Size()}
27 | z := &img.Rasterizer{Dst: image.NewRGBA(bounds), DrawOp: draw.Src}
28 |
29 | r := &render.Renderer{}
30 | r.SetRasterizer(z, bounds)
31 | decode.Decode(r, data, opts...)
32 |
33 | paint.NewImageOp(z.Dst).Add(ops)
34 | defer op.Offset(offset).Push(ops).Pop()
35 | paint.PaintOp{}.Add(ops)
36 | }
37 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | /*
6 | Package ivg provides rendering of IconVG icons.
7 |
8 | IconVG (github.com/google/iconvg) is a compact, binary format for simple
9 | vector graphics: icons, logos, glyphs and emoji.
10 |
11 | The code in this package does away with rendering the icon to an intermediate
12 | bitmap image and instead directly uses a vector Rasterizer interface.
13 | */
14 | package ivg
15 |
16 | // TODO: shapes (circles, rects) and strokes? Or can we assume that authoring
17 | // tools will convert shapes and strokes to paths?
18 |
19 | // TODO: mark somehow that a graphic (such as a back arrow) should be flipped
20 | // horizontally or its paths otherwise varied when presented in a Right-To-Left
21 | // context, such as among Arabic and Hebrew text? Or should that be the
22 | // responsibility of higher layers, selecting different IconVG graphics based
23 | // on context, the way they would select different PNG graphics.
24 |
25 | // TODO: hinting?
26 |
--------------------------------------------------------------------------------
/raster/gio/example/arrow/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package main
4 |
5 | import (
6 | "image/color"
7 | "log"
8 | "os"
9 |
10 | "gioui.org/app"
11 | "gioui.org/io/system"
12 | "gioui.org/layout"
13 | "gioui.org/op"
14 |
15 | raster "github.com/reactivego/ivg/raster/gio"
16 | )
17 |
18 | func main() {
19 | go Arrow()
20 | app.Main()
21 | }
22 |
23 | func Arrow() {
24 | window := app.NewWindow(
25 | app.Title("IVG - Arrow"),
26 | app.Size(768, 768),
27 | )
28 | widget, err := raster.Widget(AVPlayArrow, 48, 48, raster.WithColors(Amber400))
29 | if err != nil {
30 | log.Fatal(err)
31 | }
32 | ops := new(op.Ops)
33 | for next := range window.Events() {
34 | if event, ok := next.(system.FrameEvent); ok {
35 | gtx := layout.NewContext(ops, event)
36 | widget(gtx)
37 | event.Frame(ops)
38 | }
39 | }
40 | os.Exit(0)
41 | }
42 |
43 | var (
44 | // From "golang.org/x/exp/shiny/materialdesign/icons"
45 | AVPlayArrow = []byte{
46 | 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50,
47 | 0x50, 0xb0, 0xb0, 0xc0, 0x70, 0x64, 0xe9, 0xb8,
48 | 0x20, 0xac, 0x64, 0xe1,
49 | }
50 | // From "golang.org/x/exp/shiny/materialdesign/colors"
51 | Amber400 = color.RGBA{0xff, 0xca, 0x28, 0xff} // rgb(255, 202, 40)
52 | )
53 |
--------------------------------------------------------------------------------
/cmd/disivg/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "os"
8 | "path/filepath"
9 |
10 | "github.com/reactivego/ivg/decode"
11 | )
12 |
13 | func main() {
14 | var out = flag.String("o", "stdout", "the filename to write the disassembled IVG data to")
15 | flag.Usage = func() {
16 | fmt.Fprintf(flag.CommandLine.Output(), "%[1]s is a tool for disassembling IVG icons.\n\n"+
17 | "Usage:\n\n"+
18 | " %[1]s [flags] filepath\n\n"+
19 | "The flags are:\n\n", flag.CommandLine.Name())
20 | flag.PrintDefaults()
21 | fmt.Fprintln(flag.CommandLine.Output())
22 | }
23 | flag.Parse()
24 | if flag.NArg() != 1 {
25 | flag.Usage()
26 | os.Exit(2)
27 | }
28 | filename := flag.Arg(0)
29 | ivgData, err := os.ReadFile(filepath.FromSlash(filename))
30 | if err != nil {
31 | log.Fatalf("%s: ReadFile: %v", filename, err)
32 | }
33 | dis, err := decode.Disassemble(ivgData)
34 | if err != nil {
35 | log.Fatalf("%s: disassemble: %v", filename, err)
36 | }
37 | if out == nil || *out == "stdout" {
38 | _, err := os.Stdout.Write(dis)
39 | if err != nil {
40 | log.Fatalf("%s: Write: %v", *out, err)
41 | }
42 | } else if err := os.WriteFile(filepath.FromSlash(filepath.FromSlash(*out)), dis, 0666); err != nil {
43 | log.Fatalf("%s: WriteFile: %v", filename, err)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/render/render_test.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "bytes"
5 | "image"
6 | "image/color"
7 | "testing"
8 |
9 | "github.com/reactivego/ivg"
10 | "github.com/reactivego/ivg/raster/img"
11 | )
12 |
13 | func TestInvalidAlphaPremultipliedColor(t *testing.T) {
14 | // See http://golang.org/issue/39526 for some discussion.
15 |
16 | dst := image.NewRGBA(image.Rect(0, 0, 1, 1))
17 | rasterizer := img.NewRasterizer(dst)
18 | var z Renderer
19 | z.SetRasterizer(rasterizer, dst.Bounds())
20 | z.Reset(ivg.ViewBox{MinX: 0.0, MinY: 0.0, MaxX: 1.0, MaxY: 1.0}, ivg.DefaultPalette)
21 |
22 | // Fill the unit square with red.
23 | z.SetCReg(0, false, ivg.RGBAColor(color.RGBA{0x55, 0x00, 0x00, 0x66}))
24 | z.StartPath(0, 0.0, 0.0)
25 | z.AbsLineTo(1.0, 0.0)
26 | z.AbsLineTo(1.0, 1.0)
27 | z.AbsLineTo(0.0, 1.0)
28 | z.ClosePathEndPath()
29 |
30 | // Fill the unit square with an invalid (non-gradient) alpha-premultiplied
31 | // color (super-saturated green). This should be a no-op (and not crash).
32 | z.SetCReg(0, false, ivg.RGBAColor(color.RGBA{0x00, 0x99, 0x00, 0x88}))
33 | z.StartPath(0, 0.0, 0.0)
34 | z.AbsLineTo(1.0, 0.0)
35 | z.AbsLineTo(1.0, 1.0)
36 | z.AbsLineTo(0.0, 1.0)
37 | z.ClosePathEndPath()
38 |
39 | // We should see red.
40 | got := dst.Pix
41 | want := []byte{0x55, 0x00, 0x00, 0x66}
42 | if !bytes.Equal(got, want) {
43 | t.Errorf("got [% 02x], want [% 02x]", got, want)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/destination.go:
--------------------------------------------------------------------------------
1 | package ivg
2 |
3 | import "image/color"
4 |
5 | // Destination handles the actions decoded from an IconVG graphic's opcodes.
6 | //
7 | // When passed to Decode, the first method called (if any) will be Reset. No
8 | // methods will be called at all if an error is encountered in the encoded form
9 | // before the metadata is fully decoded.
10 | type Destination interface {
11 | Reset(viewbox ViewBox, palette [64]color.RGBA)
12 | CSel() uint8
13 | SetCSel(cSel uint8)
14 | NSel() uint8
15 | SetNSel(nSel uint8)
16 | SetCReg(adj uint8, incr bool, c Color)
17 | SetNReg(adj uint8, incr bool, f float32)
18 | SetLOD(lod0, lod1 float32)
19 |
20 | StartPath(adj uint8, x, y float32)
21 | ClosePathEndPath()
22 | ClosePathAbsMoveTo(x, y float32)
23 | ClosePathRelMoveTo(x, y float32)
24 |
25 | AbsHLineTo(x float32)
26 | RelHLineTo(x float32)
27 | AbsVLineTo(y float32)
28 | RelVLineTo(y float32)
29 | AbsLineTo(x, y float32)
30 | RelLineTo(x, y float32)
31 | AbsSmoothQuadTo(x, y float32)
32 | RelSmoothQuadTo(x, y float32)
33 | AbsQuadTo(x1, y1, x, y float32)
34 | RelQuadTo(x1, y1, x, y float32)
35 | AbsSmoothCubeTo(x2, y2, x, y float32)
36 | RelSmoothCubeTo(x2, y2, x, y float32)
37 | AbsCubeTo(x1, y1, x2, y2, x, y float32)
38 | RelCubeTo(x1, y1, x2, y2, x, y float32)
39 | AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)
40 | RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)
41 | }
42 |
--------------------------------------------------------------------------------
/raster/img/rasterizer.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package img
4 |
5 | import (
6 | "image"
7 | "image/draw"
8 |
9 | "golang.org/x/image/vector"
10 | )
11 |
12 | // Rasterizer that wraps an inner "golang.org/x/image/vector" Rasterizer. The
13 | // dst image normally passed to a call Draw is set as a field so Draw does not
14 | // have to take it as a parameter.
15 | type Rasterizer struct {
16 | vector.Rasterizer
17 |
18 | // Dst is the image that the Draw call uses as destination to draw into.
19 | Dst draw.Image
20 |
21 | // DrawOp is a Porter-Duff compositing operator that will be used for the
22 | // next call to the Draw method. After that call finishes, DrawOp is set to
23 | // draw.Over.
24 | DrawOp draw.Op
25 | }
26 |
27 | // NewRasterizer returns a rasterizer for dst image, with the dst size used to
28 | // reset the inner rasterizer.
29 | func NewRasterizer(dst draw.Image) *Rasterizer {
30 | r := &Rasterizer{Dst: dst}
31 | s := dst.Bounds().Size()
32 | r.Rasterizer.Reset(s.X, s.Y)
33 | return r
34 | }
35 |
36 | // Draw aligns r.Min in field Dst with sp in src and then replaces the
37 | // rectangle r in Dst with the result of drawing src on Dst. The current value
38 | // of the DrawOp field is used for drawing. But note, after drawing the DrawOp
39 | // is reset to draw.Over.
40 | func (z *Rasterizer) Draw(r image.Rectangle, src image.Image, sp image.Point) {
41 | z.Rasterizer.DrawOp = z.DrawOp
42 | z.Rasterizer.Draw(z.Dst, r, src, sp)
43 | z.DrawOp = draw.Over
44 | }
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 The Go Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/raster/gio/example/info/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package main
4 |
5 | import (
6 | "image/color"
7 | "log"
8 | "os"
9 |
10 | "gioui.org/app"
11 | "gioui.org/io/system"
12 | "gioui.org/layout"
13 | "gioui.org/op"
14 |
15 | "github.com/reactivego/ivg"
16 | "github.com/reactivego/ivg/encode"
17 | "github.com/reactivego/ivg/generate"
18 | raster "github.com/reactivego/ivg/raster/gio"
19 | )
20 |
21 | func main() {
22 | go Info()
23 | app.Main()
24 | }
25 |
26 | func Info() {
27 | window := app.NewWindow(
28 | app.Title("IVG - Info"),
29 | app.Size(768, 768),
30 | )
31 |
32 | data, err := InfoIVG()
33 | if err != nil {
34 | log.Fatal(err)
35 | }
36 |
37 | blue := color.NRGBA{0x21, 0x96, 0xf3, 0xff}
38 |
39 | widget, err := raster.Widget(data, 48, 48, raster.WithColors(blue))
40 | if err != nil {
41 | log.Fatal(err)
42 | }
43 |
44 | ops := new(op.Ops)
45 | for next := range window.Events() {
46 | if event, ok := next.(system.FrameEvent); ok {
47 | gtx := layout.NewContext(ops, event)
48 | widget(gtx)
49 | event.Frame(ops)
50 | }
51 | }
52 | os.Exit(0)
53 | }
54 |
55 | // InfoIVG generates ivg data bytes on the fly for the Info icon.
56 | func InfoIVG() ([]byte, error) {
57 | enc := &encode.Encoder{}
58 | gen := &generate.Generator{Destination: enc}
59 | gen.Reset(ivg.ViewBox{MinX: 0, MinY: 0, MaxX: 48, MaxY: 48}, ivg.DefaultPalette)
60 | gen.SetPathData("M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 "+
61 | "20-20S35.05 4 24 4zm2 30h-4V22h4v12zm0-16h-4v-4h4v4z", 0)
62 | return enc.Bytes()
63 | }
64 |
--------------------------------------------------------------------------------
/ivg_test.go:
--------------------------------------------------------------------------------
1 | package ivg_test
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/reactivego/ivg"
8 | "github.com/reactivego/ivg/decode"
9 | "github.com/reactivego/ivg/encode"
10 | "github.com/reactivego/ivg/generate"
11 | "github.com/reactivego/ivg/render"
12 | )
13 |
14 | func TestEncodeDecode(t *testing.T) {
15 | var e = &encode.Encoder{}
16 | e.HighResolutionCoordinates = true
17 | e.Reset(
18 | ivg.ViewBox{
19 | MinX: -24, MinY: -24,
20 | MaxX: +24, MaxY: +24,
21 | },
22 | ivg.DefaultPalette,
23 | )
24 |
25 | e.StartPath(0, 0, -20)
26 | e.AbsCubeTo(-11.05, -20, -20, -11.05, -20, 0)
27 | e.RelSmoothCubeTo(8.95, 20, 20, 20)
28 | e.RelSmoothCubeTo(20, -8.95, 20, -20)
29 | e.AbsSmoothCubeTo(11.05, -20, 0, -20)
30 | e.ClosePathRelMoveTo(2, 30)
31 | e.RelHLineTo(-4)
32 | e.AbsVLineTo(-2)
33 | e.RelHLineTo(4)
34 | e.RelVLineTo(12)
35 | e.ClosePathRelMoveTo(0, -16)
36 | e.RelHLineTo(-4)
37 | e.RelVLineTo(-4)
38 | e.RelHLineTo(4)
39 | e.RelVLineTo(4)
40 | e.ClosePathEndPath()
41 |
42 | expect, err := e.Bytes()
43 | if err != nil {
44 | t.Fatalf("encoding: %v", err)
45 | }
46 |
47 | e = &encode.Encoder{}
48 | e.HighResolutionCoordinates = true
49 | if err := decode.Decode(e, expect); err != nil {
50 | t.Fatalf("decoding: %v", err)
51 | }
52 | actual, err := e.Bytes()
53 | if err != nil {
54 | t.Fatalf("encoding: %v", err)
55 | } else {
56 | if len(expect) != len(actual) {
57 | t.Fatalf("len(actual)!=len(expect): %d %d", len(actual), len(expect))
58 | } else {
59 | if !bytes.Equal(expect, actual) {
60 | t.Fatal("actual!=expect")
61 | }
62 | }
63 | }
64 |
65 | var r = &render.Renderer{}
66 |
67 | var g generate.Generator
68 | g.SetDestination(e)
69 | g.SetDestination(r)
70 | }
71 |
--------------------------------------------------------------------------------
/mdicons/types.go:
--------------------------------------------------------------------------------
1 | package mdicons
2 |
3 | type SVG struct {
4 | Width float32 `xml:"width,attr"`
5 | Height float32 `xml:"height,attr"`
6 | ViewBox string `xml:"viewBox,attr"`
7 | Paths []Path `xml:"path"`
8 | // Some of the SVG files contain elements, not just
9 | // elements. IconVG doesn't have circles per se. Instead, we convert such
10 | // circles to be paired arcTo commands, tacked on to the first path.
11 | //
12 | // In general, this isn't correct if the circles and the path overlap, but
13 | // that doesn't happen in the specific case of the Material Design icons.
14 | Circles []Circle `xml:"circle"`
15 | }
16 |
17 | type Path struct {
18 | D string `xml:"d,attr"`
19 | Fill string `xml:"fill,attr"`
20 | FillOpacity *float32 `xml:"fill-opacity,attr"`
21 | Opacity *float32 `xml:"opacity,attr"`
22 | }
23 |
24 | type Circle struct {
25 | Cx float32 `xml:"cx,attr"`
26 | Cy float32 `xml:"cy,attr"`
27 | R float32 `xml:"r,attr"`
28 | }
29 |
30 | type Statistics struct {
31 | VarNames []string
32 | Failures []string
33 | TotalFiles int
34 | TotalIVGBytes int
35 | TotalPNG24Bytes int
36 | TotalPNG48Bytes int
37 | TotalSVGBytes int
38 | }
39 |
40 | func (s Statistics) Add(other Statistics) Statistics {
41 | return Statistics{
42 | VarNames: append(s.VarNames, other.VarNames...),
43 | Failures: append(s.Failures, other.Failures...),
44 | TotalFiles: s.TotalFiles + other.TotalFiles,
45 | TotalIVGBytes: s.TotalIVGBytes + other.TotalIVGBytes,
46 | TotalPNG24Bytes: s.TotalPNG24Bytes + other.TotalPNG24Bytes,
47 | TotalPNG48Bytes: s.TotalPNG48Bytes + other.TotalPNG48Bytes,
48 | TotalSVGBytes: s.TotalSVGBytes + other.TotalSVGBytes}
49 | }
50 |
--------------------------------------------------------------------------------
/mdicons/parsepath.go:
--------------------------------------------------------------------------------
1 | package mdicons
2 |
3 | import (
4 | "github.com/reactivego/ivg"
5 | "golang.org/x/image/math/f32"
6 | )
7 |
8 | func ParsePath(enc ivg.Destination, p *Path, adjs map[float32]uint8, size float32, offset f32.Vec2, outSize float32, circles []Circle) error {
9 | adj := uint8(0)
10 | opacity := float32(1)
11 | if p.Opacity != nil {
12 | opacity = *p.Opacity
13 | } else if p.FillOpacity != nil {
14 | opacity = *p.FillOpacity
15 | }
16 | if opacity != 1 {
17 | var ok bool
18 | if adj, ok = adjs[opacity]; !ok {
19 | adj = uint8(len(adjs) + 1)
20 | adjs[opacity] = adj
21 | // Set CREG[0-adj] to be a blend of transparent (0x7f) and the
22 | // first custom palette color (0x80).
23 | enc.SetCReg(adj, false, ivg.BlendColor(uint8(opacity*0xff), 0x7f, 0x80))
24 | }
25 | }
26 |
27 | needStartPath := true
28 | if p.D != "" {
29 | needStartPath = false
30 | if err := ParsePathData(enc, p.D, adj, size, offset, outSize); err != nil {
31 | return err
32 | }
33 | }
34 |
35 | for _, c := range circles {
36 | // Normalize.
37 | cx := c.Cx * outSize / size
38 | cx -= outSize/2 + offset[0]
39 | cy := c.Cy * outSize / size
40 | cy -= outSize/2 + offset[1]
41 | r := c.R * outSize / size
42 |
43 | if needStartPath {
44 | needStartPath = false
45 | enc.StartPath(adj, cx-r, cy)
46 | } else {
47 | enc.ClosePathAbsMoveTo(cx-r, cy)
48 | }
49 |
50 | // Convert a circle to two relative arcTo ops, each of 180 degrees.
51 | // We can't use one 360 degree arcTo as the start and end point
52 | // would be coincident and the computation is degenerate.
53 | enc.RelArcTo(r, r, 0, false, true, +2*r, 0)
54 | enc.RelArcTo(r, r, 0, false, true, -2*r, 0)
55 | }
56 |
57 | enc.ClosePathEndPath()
58 | return nil
59 | }
60 |
--------------------------------------------------------------------------------
/cmd/mdicons/test/icons_test.go:
--------------------------------------------------------------------------------
1 | package icons
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/base64"
6 | "encoding/json"
7 | "os"
8 | "strings"
9 | "testing"
10 | )
11 |
12 | // overwriteTestdataFiles is temporarily set to true when adding new
13 | // testdataTestCases.
14 | const overwriteTestdataFiles = false
15 |
16 | // TestOverwriteTestdataFilesIsFalse tests that any change to
17 | // overwriteTestdataFiles is only temporary. Programmers are assumed to run "go
18 | // test" before sending out for code review or committing code.
19 | func TestOverwriteTestdataFilesIsFalse(t *testing.T) {
20 | if overwriteTestdataFiles {
21 | t.Errorf("overwriteTestdataFiles is true; do not commit code changes")
22 | }
23 | }
24 |
25 | func TestHashes(t *testing.T) {
26 | got := make(map[string]string)
27 | for i := range list {
28 | checksum := md5.Sum(list[i].data)
29 | got[list[i].name] = base64.RawURLEncoding.EncodeToString(checksum[:])
30 | }
31 | if overwriteTestdataFiles {
32 | out, err := os.Create("icons.json")
33 | if err != nil {
34 | t.Fatal(err)
35 | }
36 | enc := json.NewEncoder(out)
37 | enc.SetIndent("", " ")
38 | err = enc.Encode(got)
39 | out.Close()
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 | }
44 | in, err := os.Open("icons.json")
45 | if err != nil {
46 | t.Fatal(err)
47 | }
48 | defer in.Close()
49 | dec := json.NewDecoder(in)
50 | expect := make(map[string]string)
51 | err = dec.Decode(&expect)
52 | if err != nil {
53 | t.Fatal(err)
54 | }
55 | for ke, ve := range expect {
56 | if vg, ok := got[ke]; !ok {
57 | t.Errorf("icon:%q expected, but not present", ke)
58 | } else {
59 | if !strings.EqualFold(ve, vg) {
60 | t.Errorf("icon:%q, md5 expected:%q, got:%q", ke, ve, vg)
61 | }
62 | }
63 | }
64 | for kg := range got {
65 | if _, ok := expect[kg]; !ok {
66 | t.Errorf("icon:%q present, but not expected", kg)
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/raster/logger.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package raster
4 |
5 | import (
6 | "fmt"
7 | "image"
8 | )
9 |
10 | type RasterizerLogger struct {
11 | Rasterizer
12 | }
13 |
14 | func (r *RasterizerLogger) Reset(w, h int) {
15 | fmt.Printf("raster.Reset(w:%d, h:%d)\n", w, h)
16 | r.Rasterizer.Reset(w, h)
17 | }
18 |
19 | func (r *RasterizerLogger) Size() image.Point {
20 | s := r.Rasterizer.Size()
21 | // fmt.Printf("raster.Size() = %#v\n", s)
22 | return s
23 | }
24 |
25 | func (r *RasterizerLogger) Bounds() image.Rectangle {
26 | b := r.Rasterizer.Bounds()
27 | // fmt.Printf("raster.Bounds() = %#v\n", b)
28 | return b
29 | }
30 |
31 | func (r *RasterizerLogger) Pen() (x, y float32) {
32 | x, y = r.Rasterizer.Pen()
33 | // fmt.Printf("raster.Pen() = (x:%.2f, y:%.2f)\n", x, y)
34 | return x, y
35 | }
36 |
37 | func (r *RasterizerLogger) MoveTo(ax, ay float32) {
38 | fmt.Printf("raster.MoveTo(ax:%.2f, ay:%.2f)\n", ax, ay)
39 | r.Rasterizer.MoveTo(ax, ay)
40 | }
41 |
42 | func (r *RasterizerLogger) LineTo(bx, by float32) {
43 | fmt.Printf("raster.LineTo(bx:%.2f, by:%.2f)\n", bx, by)
44 | r.Rasterizer.LineTo(bx, by)
45 | }
46 |
47 | func (r *RasterizerLogger) QuadTo(bx, by, cx, cy float32) {
48 | fmt.Printf("raster.QuadTo(bx:%.2f, by:%.2f, cx:%.2f, cy:%.2f)\n", bx, by, cx, cy)
49 | r.Rasterizer.QuadTo(bx, by, cx, cy)
50 | }
51 |
52 | func (r *RasterizerLogger) CubeTo(bx, by, cx, cy, dx, dy float32) {
53 | fmt.Printf("raster.CubeTo(bx:%.2f, by:%.2f, cx:%.2f, cy:%.2f, dx:%.2f, dy:%.2f)\n", bx, by, cx, cy, dx, dy)
54 | r.Rasterizer.CubeTo(bx, by, cx, cy, dx, dy)
55 | }
56 |
57 | func (r *RasterizerLogger) ClosePath() {
58 | fmt.Printf("raster.ClosePath()\n")
59 | r.Rasterizer.ClosePath()
60 | }
61 |
62 | func (rl *RasterizerLogger) Draw(r image.Rectangle, src image.Image, sp image.Point) {
63 | fmt.Printf("raster.Draw(r: %#v, src:
, sp: %#v)\n", r, sp)
64 | rl.Rasterizer.Draw(r, src, sp)
65 | }
66 |
--------------------------------------------------------------------------------
/testdata/lod-polygon.ivg.disassembly:
--------------------------------------------------------------------------------
1 | 89 49 56 47 IconVG Magic identifier
2 | 00 Number of metadata chunks: 0
3 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
4 | 48 -28
5 | 58 -20
6 | e8 V (absolute vertical lineTo)
7 | 48 -28
8 | e6 H (absolute horizontal lineTo)
9 | 58 -20
10 | e1 z (closePath); end path
11 | c7 Set LOD
12 | 00 +0
13 | a0 +80
14 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
15 | b8 +28
16 | 80 +0
17 | 01 L (absolute lineTo), 2 reps
18 | 64 -14
19 | 41 98 +24.25
20 | L (absolute lineTo), implicit
21 | 64 -14
22 | c1 67 -24.25
23 | e1 z (closePath); end path
24 | c7 Set LOD
25 | a0 +80
26 | 03 00 80 7f +Inf
27 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
28 | b8 +28
29 | 80 +0
30 | 03 L (absolute lineTo), 4 reps
31 | a9 88 +8.65625
32 | a1 9a +26.625
33 | L (absolute lineTo), implicit
34 | 59 69 -22.65625
35 | 75 90 +16.453125
36 | L (absolute lineTo), implicit
37 | 59 69 -22.65625
38 | 8d 6f -16.453125
39 | L (absolute lineTo), implicit
40 | a9 88 +8.65625
41 | 61 65 -26.625
42 | e1 z (closePath); end path
43 | c7 Set LOD
44 | 00 +0
45 | 03 00 80 7f +Inf
46 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
47 | b8 +28
48 | a8 +20
49 | e8 V (absolute vertical lineTo)
50 | b8 +28
51 | e6 H (absolute horizontal lineTo)
52 | a8 +20
53 | e1 z (closePath); end path
54 |
--------------------------------------------------------------------------------
/example_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package ivg_test
6 |
7 | import (
8 | "image"
9 | "image/draw"
10 | "log"
11 | "os"
12 | "path/filepath"
13 |
14 | "github.com/reactivego/ivg/decode"
15 | "github.com/reactivego/ivg/raster/img"
16 | "github.com/reactivego/ivg/render"
17 | )
18 |
19 | func Example() {
20 | ivgData, err := os.ReadFile(filepath.FromSlash("testdata/action-info.lores.ivg"))
21 | if err != nil {
22 | log.Fatal(err)
23 | }
24 |
25 | const width = 24
26 | dst := image.NewAlpha(image.Rect(0, 0, width, width))
27 | var z render.Renderer
28 | z.SetRasterizer(&img.Rasterizer{Dst: dst, DrawOp: draw.Src}, dst.Bounds())
29 | if err := decode.Decode(&z, ivgData); err != nil {
30 | log.Fatal(err)
31 | }
32 |
33 | const asciiArt = ".++8"
34 | buf := make([]byte, 0, width*(width+1))
35 | for y := 0; y < width; y++ {
36 | for x := 0; x < width; x++ {
37 | a := dst.AlphaAt(x, y).A
38 | buf = append(buf, asciiArt[a>>6])
39 | }
40 | buf = append(buf, '\n')
41 | }
42 | os.Stdout.Write(buf)
43 |
44 | // Output:
45 | // ........................
46 | // ........................
47 | // ........++8888++........
48 | // ......+8888888888+......
49 | // .....+888888888888+.....
50 | // ....+88888888888888+....
51 | // ...+8888888888888888+...
52 | // ...88888888..88888888...
53 | // ..+88888888..88888888+..
54 | // ..+888888888888888888+..
55 | // ..88888888888888888888..
56 | // ..888888888..888888888..
57 | // ..888888888..888888888..
58 | // ..888888888..888888888..
59 | // ..+88888888..88888888+..
60 | // ..+88888888..88888888+..
61 | // ...88888888..88888888...
62 | // ...+8888888888888888+...
63 | // ....+88888888888888+....
64 | // .....+888888888888+.....
65 | // ......+8888888888+......
66 | // ........++8888++........
67 | // ........................
68 | // ........................
69 | }
70 |
--------------------------------------------------------------------------------
/testdata/action-info.hires.ivg.disassembly:
--------------------------------------------------------------------------------
1 | 89 49 56 47 IconVG Magic identifier
2 | 02 Number of metadata chunks: 1
3 | 0a Metadata chunk length: 5
4 | 00 Metadata Identifier: 0 (viewBox)
5 | 50 -24
6 | 50 -24
7 | b0 +24
8 | b0 +24
9 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
10 | 80 +0
11 | 58 -20
12 | a0 C (absolute cubeTo), 1 reps
13 | cf cc 30 c1 -11.049999
14 | 58 -20
15 | 58 -20
16 | cf cc 30 c1 -11.049999
17 | 58 -20
18 | 80 +0
19 | 91 s (relative smooth cubeTo), 2 reps
20 | 37 33 0f 41 +8.950001
21 | a8 +20
22 | a8 +20
23 | a8 +20
24 | s (relative smooth cubeTo), implicit
25 | a8 +20
26 | 37 33 0f c1 -8.950001
27 | a8 +20
28 | 58 -20
29 | 80 S (absolute smooth cubeTo), 1 reps
30 | cf cc 30 41 +11.049999
31 | 58 -20
32 | 80 +0
33 | 58 -20
34 | e3 z (closePath); m (relative moveTo)
35 | 84 +2
36 | bc +30
37 | e7 h (relative horizontal lineTo)
38 | 78 -4
39 | e8 V (absolute vertical lineTo)
40 | 7c -2
41 | e7 h (relative horizontal lineTo)
42 | 88 +4
43 | e9 v (relative vertical lineTo)
44 | 98 +12
45 | e3 z (closePath); m (relative moveTo)
46 | 80 +0
47 | 60 -16
48 | e7 h (relative horizontal lineTo)
49 | 78 -4
50 | e9 v (relative vertical lineTo)
51 | 78 -4
52 | e7 h (relative horizontal lineTo)
53 | 88 +4
54 | e9 v (relative vertical lineTo)
55 | 88 +4
56 | e1 z (closePath); end path
57 |
--------------------------------------------------------------------------------
/testdata/action-info.lores.ivg.disassembly:
--------------------------------------------------------------------------------
1 | 89 49 56 47 IconVG Magic identifier
2 | 02 Number of metadata chunks: 1
3 | 0a Metadata chunk length: 5
4 | 00 Metadata Identifier: 0 (viewBox)
5 | 50 -24
6 | 50 -24
7 | b0 +24
8 | b0 +24
9 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
10 | 80 +0
11 | 58 -20
12 | a0 C (absolute cubeTo), 1 reps
13 | f5 74 -11.046875
14 | 58 -20
15 | 58 -20
16 | f5 74 -11.046875
17 | 58 -20
18 | 80 +0
19 | 91 s (relative smooth cubeTo), 2 reps
20 | f5 88 +8.953125
21 | a8 +20
22 | a8 +20
23 | a8 +20
24 | s (relative smooth cubeTo), implicit
25 | a8 +20
26 | 0d 77 -8.953125
27 | a8 +20
28 | 58 -20
29 | 80 S (absolute smooth cubeTo), 1 reps
30 | 0d 8b +11.046875
31 | 58 -20
32 | 80 +0
33 | 58 -20
34 | e3 z (closePath); m (relative moveTo)
35 | 84 +2
36 | bc +30
37 | e7 h (relative horizontal lineTo)
38 | 78 -4
39 | e8 V (absolute vertical lineTo)
40 | 7c -2
41 | e7 h (relative horizontal lineTo)
42 | 88 +4
43 | e9 v (relative vertical lineTo)
44 | 98 +12
45 | e3 z (closePath); m (relative moveTo)
46 | 80 +0
47 | 60 -16
48 | e7 h (relative horizontal lineTo)
49 | 78 -4
50 | e9 v (relative vertical lineTo)
51 | 78 -4
52 | e7 h (relative horizontal lineTo)
53 | 88 +4
54 | e9 v (relative vertical lineTo)
55 | 88 +4
56 | e1 z (closePath); end path
57 |
--------------------------------------------------------------------------------
/raster/rasterizer.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | // Package raster provides rasterizers for 2-D vector graphics. Sub-directory
4 | // vec provides an implementation based on golang.org/x/image/vector.
5 | package raster
6 |
7 | import (
8 | "image"
9 | )
10 |
11 | // Rasterizer is a 2-D vector graphics rasterizer.
12 | type Rasterizer interface {
13 | // Reset resets a Rasterizer as if it was just returned by NewRasterizer.
14 | // This includes setting z.DrawOp to draw.Over.
15 | Reset(w, h int)
16 | // Size returns the width and height passed to NewRasterizer or Reset.
17 | Size() image.Point
18 | // Bounds returns the rectangle from (0, 0) to the width and height passed to
19 | // Reset.
20 | Bounds() image.Rectangle
21 | // Pen returns the location of the path-drawing pen: the last argument to the
22 | // most recent XxxTo call.
23 | Pen() (x, y float32)
24 | // MoveTo starts a new path and moves the pen to (ax, ay). The coordinates
25 | // are allowed to be out of the Rasterizer's bounds.
26 | MoveTo(ax, ay float32)
27 | // LineTo adds a line segment, from the pen to (bx, by), and moves the pen to
28 | // (bx, by). The coordinates are allowed to be out of the Rasterizer's
29 | // bounds.
30 | LineTo(bx, by float32)
31 | // QuadTo adds a quadratic Bézier segment, from the pen via (bx, by) to (cx,
32 | // cy), and moves the pen to (cx, cy). The coordinates are allowed to be out
33 | // of the Rasterizer's bounds.
34 | QuadTo(bx, by, cx, cy float32)
35 | // CubeTo adds a cubic Bézier segment, from the pen via (bx, by) and (cx, cy)
36 | // to (dx, dy), and moves the pen to (dx, dy). The coordinates are allowed to
37 | // be out of the Rasterizer's bounds.
38 | CubeTo(bx, by, cx, cy, dx, dy float32)
39 | // ClosePath closes the current path.
40 | ClosePath()
41 | // Draw aligns r.Min in z with sp in src and then replaces the rectangle r in
42 | // z with the result of a Porter-Duff composition. The vector paths
43 | // previously added via the XxxTo calls become the mask for drawing src onto
44 | // z.
45 | Draw(r image.Rectangle, src image.Image, sp image.Point)
46 | }
47 |
--------------------------------------------------------------------------------
/raster/gio/widget.go:
--------------------------------------------------------------------------------
1 | package gio
2 |
3 | import (
4 | "image"
5 | "image/color"
6 |
7 | "gioui.org/layout"
8 | "gioui.org/op"
9 | "gioui.org/unit"
10 | "github.com/reactivego/ivg"
11 | "github.com/reactivego/ivg/decode"
12 | )
13 |
14 | type Option = func(*option)
15 |
16 | type option struct {
17 | Paint PaintFunc
18 | Options []decode.DecodeOption
19 | }
20 |
21 | func WithImageBackend() Option {
22 | return func(o *option) {
23 | o.Paint = ImagePaint
24 | }
25 | }
26 |
27 | func WithColors(colors ...color.Color) Option {
28 | return func(o *option) {
29 | for idx, c := range colors {
30 | o.Options = append(o.Options, decode.WithColorAt(idx, c))
31 | }
32 | }
33 | }
34 |
35 | // Widget creates a layout widget for rendering IconVG vector graphics data. It supports two rendering
36 | // backends: a default Gio clip.Path implementation and an optional image-based raster backend
37 | // (enabled via WithImageBackend()). The widget handles aspect ratio preservation following the
38 | // SVG specification's "xMidYMid meet" behavior, which scales the image to fit the viewport while
39 | // maintaining proportions and centering it both horizontally and vertically.
40 | //
41 | // The data parameter accepts the raw IconVG bytes, while width and height specify the desired
42 | // dimensions in device-independent pixels (Dp). Additional rendering options can be provided
43 | // through the variadic options parameter.
44 | func Widget(data []byte, width, height unit.Dp, options ...Option) (layout.Widget, error) {
45 | viewBox, err := decode.DecodeViewBox(data)
46 | if err != nil {
47 | return nil, err
48 | }
49 | o := &option{Paint: GioPaint}
50 | for _, f := range options {
51 | f(o)
52 | }
53 | lastSize := image.Point{}
54 | callOp := op.CallOp{}
55 | widget := func(gtx layout.Context) layout.Dimensions {
56 | newSize := gtx.Constraints.Constrain(image.Pt(gtx.Dp(width), gtx.Dp(height)))
57 | minx, miny, maxx, maxy := viewBox.AspectMeet(float32(newSize.X), float32(newSize.Y), ivg.Mid, ivg.Mid)
58 | rect := image.Rect(int(minx), int(miny), int(maxx), int(maxy))
59 | if newSize != lastSize {
60 | lastSize = newSize
61 | ops := new(op.Ops)
62 | macro := op.Record(ops)
63 | o.Paint(ops, data, rect, o.Options...)
64 | callOp = macro.Stop()
65 | }
66 | callOp.Add(gtx.Ops)
67 | return layout.Dimensions{Size: newSize}
68 | }
69 | return widget, nil
70 | }
71 |
--------------------------------------------------------------------------------
/raster/gio/example/blend/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package main
4 |
5 | import (
6 | "image"
7 | "image/color"
8 | "image/draw"
9 | "os"
10 |
11 | "golang.org/x/image/vector"
12 |
13 | "gioui.org/app"
14 | "gioui.org/io/system"
15 | "gioui.org/op"
16 | "gioui.org/op/clip"
17 | "gioui.org/op/paint"
18 | )
19 |
20 | func main() {
21 | go Blend()
22 | app.Main()
23 | }
24 |
25 | func Blend() {
26 | window := app.NewWindow(
27 | app.Title("IVG - Blend"),
28 | app.Size(480, 480),
29 | )
30 | ops := new(op.Ops)
31 | for next := range window.Events() {
32 | if event, ok := next.(system.FrameEvent); ok {
33 | ops.Reset()
34 | dx, dy := event.Size.X, event.Size.Y
35 |
36 | // This app demonstrates blending of a translucent highlight color on top of an opaque
37 | // background color. The upper half of the window is filled using "gioui.org/op/paint"
38 | // operations, while the lower half of the window is filled using "golang.org/x/image/vector".
39 | // Both halves should look identical.
40 |
41 | yellow := color.NRGBA{0xfc, 0xe9, 0x4f, 0xff}
42 | highlight := color.NRGBA{0xfd, 0xee, 0x74, 0x7f}
43 |
44 | // Upper half of the window is painted using "gioui.org/op/paint"
45 | // to blend a translucent highlight color over an opaque yellow
46 | // background color.
47 | paint.ColorOp{Color: yellow}.Add(ops)
48 | cstack := clip.Rect(image.Rect(0, 0, dx, dy/2)).Push(ops)
49 | paint.PaintOp{}.Add(ops)
50 | paint.ColorOp{Color: highlight}.Add(ops)
51 | paint.PaintOp{}.Add(ops)
52 | cstack.Pop()
53 |
54 | // Lower half of the window is painted using
55 | // "golang.org/x/image/vector".
56 | RGBA := func(c color.Color) color.RGBA {
57 | return color.RGBAModel.Convert(c).(color.RGBA)
58 | }
59 | z := vector.NewRasterizer(dx, dy/2)
60 | z.MoveTo(0, 0)
61 | z.LineTo(float32(dx), 0)
62 | z.LineTo(float32(dx), float32(dy/2))
63 | z.LineTo(0, float32(dy/2))
64 | z.ClosePath()
65 | dst := image.NewRGBA(z.Bounds())
66 | src := image.NewUniform(RGBA(yellow))
67 | z.DrawOp = draw.Src
68 | z.Draw(dst, dst.Bounds(), src, src.Bounds().Min)
69 | src = image.NewUniform(RGBA(highlight))
70 | z.DrawOp = draw.Over
71 | z.Draw(dst, dst.Bounds(), src, src.Bounds().Min)
72 | paint.NewImageOp(dst).Add(ops)
73 | lower := image.Rect(0, dy/2, dx, dy)
74 | tstack := op.Offset(lower.Min).Push(ops)
75 | cstack = clip.Rect(lower.Sub(lower.Min)).Push(ops)
76 | paint.PaintOp{}.Add(ops)
77 | cstack.Pop()
78 | tstack.Pop()
79 |
80 | event.Frame(ops)
81 | }
82 | }
83 | os.Exit(0)
84 | }
85 |
--------------------------------------------------------------------------------
/testdata/video-005.primitive.svg:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/raster/LICENSE:
--------------------------------------------------------------------------------
1 | This project is dual-licensed under the UNLICENSE or
2 | the MIT license with the SPDX identifier:
3 |
4 | SPDX-License-Identifier: Unlicense OR MIT
5 |
6 | You may use the project under the terms of either license.
7 |
8 | Both licenses are reproduced below.
9 |
10 | ----
11 | The MIT License (MIT)
12 |
13 | Copyright (c) 2019 The Gio authors
14 |
15 | Permission is hereby granted, free of charge, to any person obtaining a copy
16 | of this software and associated documentation files (the "Software"), to deal
17 | in the Software without restriction, including without limitation the rights
18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19 | copies of the Software, and to permit persons to whom the Software is
20 | furnished to do so, subject to the following conditions:
21 |
22 | The above copyright notice and this permission notice shall be included in
23 | all copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31 | THE SOFTWARE.
32 | ---
33 |
34 |
35 |
36 | ---
37 | The UNLICENSE
38 |
39 | This is free and unencumbered software released into the public domain.
40 |
41 | Anyone is free to copy, modify, publish, use, compile, sell, or
42 | distribute this software, either in source code form or as a compiled
43 | binary, for any purpose, commercial or non-commercial, and by any
44 | means.
45 |
46 | In jurisdictions that recognize copyright laws, the author or authors
47 | of this software dedicate any and all copyright interest in the
48 | software to the public domain. We make this dedication for the benefit
49 | of the public at large and to the detriment of our heirs and
50 | successors. We intend this dedication to be an overt act of
51 | relinquishment in perpetuity of all present and future rights to this
52 | software under copyright law.
53 |
54 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
55 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
56 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
57 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
58 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
59 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
60 | OTHER DEALINGS IN THE SOFTWARE.
61 |
62 | For more information, please refer to
63 | ---
--------------------------------------------------------------------------------
/mdicons/parse.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package mdicons
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "go/format"
11 | "os"
12 | "sort"
13 | )
14 |
15 | func Parse(mdicons string, pkg string, genData, genDataTest bool, outSize float32) error {
16 | f, err := os.Open(mdicons)
17 | if err != nil {
18 | return fmt.Errorf("%v\n\nDid you pass a directory to -mdicons ?", err)
19 | }
20 | defer f.Close()
21 | infos, err := f.Readdir(-1)
22 | if err != nil {
23 | return err
24 | }
25 | names := []string{}
26 | for _, info := range infos {
27 | if !info.IsDir() {
28 | continue
29 | }
30 | name := info.Name()
31 | if name[0] == '.' {
32 | continue
33 | }
34 | names = append(names, name)
35 | }
36 | sort.Strings(names)
37 |
38 | out := &bytes.Buffer{}
39 |
40 | // Generate data.go.
41 | if genData {
42 | out.WriteString(fmt.Sprintf("// generated by mdicons; DO NOT EDIT\n\npackage %s\n\n", pkg))
43 |
44 | stats := Statistics{}
45 | for _, name := range names {
46 | if stat, err := ParseDir(mdicons, name, outSize, out); err != nil {
47 | return err
48 | } else {
49 | stats = stats.Add(stat)
50 | }
51 | }
52 |
53 | fmt.Fprintf(out,
54 | "// In total, %d SVG bytes in %d files (%d PNG bytes at 24px * 24px,\n"+
55 | "// %d PNG bytes at 48px * 48px) converted to %d IconVG bytes.\n",
56 | stats.TotalSVGBytes, stats.TotalFiles, stats.TotalPNG24Bytes, stats.TotalPNG48Bytes, stats.TotalIVGBytes)
57 |
58 | if len(stats.Failures) != 0 {
59 | out.WriteString("\n/*\nFAILURES:\n\n")
60 | for _, failure := range stats.Failures {
61 | out.WriteString(failure)
62 | out.WriteByte('\n')
63 | }
64 | out.WriteString("\n*/")
65 | }
66 |
67 | raw := out.Bytes()
68 | formatted, err := format.Source(raw)
69 | if err != nil {
70 | return fmt.Errorf("gofmt failed: %v\n\nGenerated code:\n%s", err, raw)
71 | }
72 | if err := os.WriteFile("data.go", formatted, 0644); err != nil {
73 | return fmt.Errorf("WriteFile failed: %s", err)
74 | }
75 |
76 | // Generate data_test.go.
77 | if genDataTest {
78 | out.Reset()
79 | out.WriteString(fmt.Sprintf("// generated by mdicons; DO NOT EDIT\n\npackage %s\n\n", pkg))
80 | out.WriteString("var list = []struct{ name string; data []byte } {\n")
81 | for _, v := range stats.VarNames {
82 | fmt.Fprintf(out, "{%q, %s},\n", v, v)
83 | }
84 | out.WriteString("}\n\n")
85 | raw := out.Bytes()
86 | formatted, err := format.Source(raw)
87 | if err != nil {
88 | return fmt.Errorf("gofmt failed: %v\n\nGenerated code:\n%s", err, raw)
89 | }
90 | if err := os.WriteFile("data_test.go", formatted, 0644); err != nil {
91 | return fmt.Errorf("WriteFile failed: %s", err)
92 | }
93 | }
94 | }
95 | return nil
96 | }
97 |
--------------------------------------------------------------------------------
/testdata/README:
--------------------------------------------------------------------------------
1 | action-info.svg comes from the Material Design icon set. See
2 | action/svg/production/ic_info_48px.svg in the
3 | github.com/google/material-design-icons repository at the tag 3.0.2.
4 |
5 | action-info.{lo,hi}res.ivg are low- and high-resolution IconVG versions of that
6 | SVG file. Low resolution means that coordinates are quantized to 1/64th of a
7 | unit; the graphic's size is 48 by 48 units. High resolution means that
8 | coordinates are represented by all but the 2 least significant bits of a
9 | float32. Each low resolution coordinate is encoded in either 1 or 2 bytes. Each
10 | high resolution coordinate is encoded in either 1, 2 or 4 bytes.
11 |
12 | action-info.{lo,hi}res.ivg.disassembly are disassemblies of those IconVG files.
13 |
14 | action-info.{lo,hi}res.png are renderings of those IconVG files.
15 |
16 |
17 |
18 | arcs.ivg is inspired by the two examples at
19 | https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
20 |
21 | arcs.ivg.disassembly is a disassembly of that IconVG file.
22 |
23 | arcs.png is a rendering of that IconVG file.
24 |
25 |
26 |
27 | blank.ivg is a blank, square graphic.
28 |
29 | blank.ivg.disassembly is a disassembly of that IconVG file.
30 |
31 | blank.png is a rendering of that IconVG file.
32 |
33 |
34 |
35 | cowbell.svg is an original artwork by nigeltao@golang.org.
36 |
37 | cowbell.ivg is an IconVG version of that SVG file.
38 |
39 | cowbell.ivg.disassembly is a disassembly of that IconVG file.
40 |
41 | cowbell.png is a rendering of that IconVG file.
42 |
43 |
44 |
45 | elliptical.ivg was created manually.
46 |
47 | elliptical.ivg.disassembly is a disassembly of that IconVG file.
48 |
49 | elliptical.png is a rendering of that IconVG file.
50 |
51 |
52 |
53 | favicon.svg is based on doc/gopher/favicon.svg from the Go 1.7 release, after
54 | using Inkscape to convert strokes and circles to paths, and saving it as an
55 | "Optimized SVG".
56 |
57 | favicon.ivg is an IconVG version of that SVG file.
58 |
59 | favicon.ivg.disassembly is a disassembly of that IconVG file.
60 |
61 | favicon.png and favicon.pink.png are renderings of that IconVG file.
62 |
63 |
64 |
65 | gradient.ivg was created manually.
66 |
67 | gradient.ivg.disassembly is a disassembly of that IconVG file.
68 |
69 | gradient.png is a rendering of that IconVG file.
70 |
71 |
72 |
73 | lod-polygon.ivg was created manually.
74 |
75 | lod-polygon.ivg.disassembly is a disassembly of that IconVG file.
76 |
77 | lod-polygon.png and lod-polygon.64.png are renderings of that IconVG file.
78 |
79 |
80 |
81 | video-005.jpeg comes from an old version of the Go repository. See
82 | https://codereview.appspot.com/5758047/
83 |
84 | video-005.primitive.svg was based on running github.com/fogleman/primitive on a
85 | 256x192 scaled version of video-005.jpeg.
86 |
87 | video-005.primitive.ivg is an IconVG version of that SVG file.
88 |
89 | video-005.primitive.ivg.disassembly is a disassembly of that IconVG file.
90 |
91 | video-005.primitive.png is a rendering of that IconVG file.
92 |
--------------------------------------------------------------------------------
/testdata/elliptical.ivg.disassembly:
--------------------------------------------------------------------------------
1 | 89 49 56 47 IconVG Magic identifier
2 | 00 Number of metadata chunks: 0
3 | 98 Set CREG[CSEL-0] to a 4 byte color
4 | 02 8a ca 00 gradient (NSTOPS=2, CBASE=10, NBASE=10, radial, reflect)
5 | 0a Set CSEL = 10
6 | 4a Set NSEL = 10
7 | ae Set NREG[NSEL-6] to a real number
8 | af aa aa bc -0.020833336
9 | bd Set NREG[NSEL-5] to a zero-to-one number
10 | 0a 0.041666668
11 | ac Set NREG[NSEL-4] to a real number
12 | 00 0
13 | ab Set NREG[NSEL-3] to a real number
14 | 8b 88 08 3d 0.03333333
15 | aa Set NREG[NSEL-2] to a real number
16 | 00 0
17 | b9 Set NREG[NSEL-1] to a zero-to-one number
18 | a0 0.6666667
19 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
20 | 4b RGBA c00000ff
21 | af Set NREG[NSEL-0] to a real number; NSEL++
22 | 00 0
23 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
24 | 03 RGBA 0000c0ff
25 | af Set NREG[NSEL-0] to a real number; NSEL++
26 | 02 1
27 | 00 Set CSEL = 0
28 | 40 Set NSEL = 0
29 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
30 | 40 -32
31 | 40 -32
32 | e6 H (absolute horizontal lineTo)
33 | c0 +32
34 | e8 V (absolute vertical lineTo)
35 | c0 +32
36 | e6 H (absolute horizontal lineTo)
37 | 40 -32
38 | e1 z (closePath); end path
39 | 80 Set CREG[CSEL-0] to a 1 byte color
40 | 7c RGBA ffffffff
41 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
42 | 56 -21
43 | 6c -10
44 | 02 L (absolute lineTo), 3 reps
45 | 58 -20
46 | 6a -11
47 | L (absolute lineTo), implicit
48 | 5a -19
49 | 6c -10
50 | L (absolute lineTo), implicit
51 | 58 -20
52 | 6e -9
53 | e1 z (closePath); end path
54 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
55 | 56 -21
56 | 9c +14
57 | 02 L (absolute lineTo), 3 reps
58 | 58 -20
59 | 9a +13
60 | L (absolute lineTo), implicit
61 | 5a -19
62 | 9c +14
63 | L (absolute lineTo), implicit
64 | 58 -20
65 | 9e +15
66 | e1 z (closePath); end path
67 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
68 | 92 +9
69 | 8a +5
70 | 02 L (absolute lineTo), 3 reps
71 | 94 +10
72 | 88 +4
73 | L (absolute lineTo), implicit
74 | 96 +11
75 | 8a +5
76 | L (absolute lineTo), implicit
77 | 94 +10
78 | 8c +6
79 | e1 z (closePath); end path
80 |
--------------------------------------------------------------------------------
/decode/buffer.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package decode
6 |
7 | import (
8 | "image/color"
9 | "math"
10 |
11 | "github.com/reactivego/ivg"
12 | )
13 |
14 | // buffer holds an encoded IconVG graphic.
15 | //
16 | // The decodeXxx methods return the decoded value and an integer n, the number
17 | // of bytes that value was encoded in. They return n == 0 if an error occured.
18 | type buffer []byte
19 |
20 | func (b buffer) decodeNatural() (u uint32, n int) {
21 | if len(b) < 1 {
22 | return 0, 0
23 | }
24 | x := b[0]
25 | if x&0x01 == 0 {
26 | return uint32(x) >> 1, 1
27 | }
28 | if x&0x02 == 0 {
29 | if len(b) >= 2 {
30 | y := uint16(b[0]) | uint16(b[1])<<8
31 | return uint32(y) >> 2, 2
32 | }
33 | return 0, 0
34 | }
35 | if len(b) >= 4 {
36 | y := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
37 | return y >> 2, 4
38 | }
39 | return 0, 0
40 | }
41 |
42 | func (b buffer) decodeReal() (f float32, n int) {
43 | switch u, n := b.decodeNatural(); n {
44 | case 0:
45 | return 0, n
46 | case 1:
47 | return float32(u), n
48 | case 2:
49 | return float32(u), n
50 | default:
51 | return math.Float32frombits(u << 2), n
52 | }
53 | }
54 |
55 | func (b buffer) decodeCoordinate() (f float32, n int) {
56 | switch u, n := b.decodeNatural(); n {
57 | case 0:
58 | return 0, n
59 | case 1:
60 | return float32(int32(u) - 64), n
61 | case 2:
62 | return float32(int32(u)-64*128) / 64, n
63 | default:
64 | return math.Float32frombits(u << 2), n
65 | }
66 | }
67 |
68 | func (b buffer) decodeZeroToOne() (f float32, n int) {
69 | switch u, n := b.decodeNatural(); n {
70 | case 0:
71 | return 0, n
72 | case 1:
73 | return float32(u) / 120, n
74 | case 2:
75 | return float32(u) / 15120, n
76 | default:
77 | return math.Float32frombits(u << 2), n
78 | }
79 | }
80 |
81 | func (b buffer) decodeColor1() (c ivg.Color, n int) {
82 | if len(b) < 1 {
83 | return ivg.Color{}, 0
84 | }
85 | return ivg.DecodeColor1(b[0]), 1
86 | }
87 |
88 | func (b buffer) decodeColor2() (c ivg.Color, n int) {
89 | if len(b) < 2 {
90 | return ivg.Color{}, 0
91 | }
92 | return ivg.RGBAColor(color.RGBA{
93 | R: 0x11 * (b[0] >> 4),
94 | G: 0x11 * (b[0] & 0x0f),
95 | B: 0x11 * (b[1] >> 4),
96 | A: 0x11 * (b[1] & 0x0f),
97 | }), 2
98 | }
99 |
100 | func (b buffer) decodeColor3Direct() (c ivg.Color, n int) {
101 | if len(b) < 3 {
102 | return ivg.Color{}, 0
103 | }
104 | return ivg.RGBAColor(color.RGBA{
105 | R: b[0],
106 | G: b[1],
107 | B: b[2],
108 | A: 0xff,
109 | }), 3
110 | }
111 |
112 | func (b buffer) decodeColor4() (c ivg.Color, n int) {
113 | if len(b) < 4 {
114 | return ivg.Color{}, 0
115 | }
116 | return ivg.RGBAColor(color.RGBA{
117 | R: b[0],
118 | G: b[1],
119 | B: b[2],
120 | A: b[3],
121 | }), 4
122 | }
123 |
124 | func (b buffer) decodeColor3Indirect() (c ivg.Color, n int) {
125 | if len(b) < 3 {
126 | return ivg.Color{}, 0
127 | }
128 | return ivg.BlendColor(b[0], b[1], b[2]), 3
129 | }
130 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
2 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
3 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
4 | golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
5 | golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
6 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
7 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
8 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
9 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
10 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
11 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
12 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
13 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
14 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
15 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
16 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
17 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
18 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
19 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
20 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
21 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
22 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
23 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
24 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
25 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
26 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
27 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
28 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
29 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
30 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
31 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
32 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
33 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
34 |
--------------------------------------------------------------------------------
/mdicons/parsepathdata.go:
--------------------------------------------------------------------------------
1 | package mdicons
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strings"
7 |
8 | "github.com/reactivego/ivg"
9 | "golang.org/x/image/math/f32"
10 | )
11 |
12 | func ParsePathData(enc ivg.Destination, pathData string, adj uint8, size float32, offset f32.Vec2, outSize float32) error {
13 | pathData = strings.TrimSuffix(pathData, "z")
14 | r := strings.NewReader(pathData)
15 |
16 | var args [6]float32
17 | op, relative := byte(0), false
18 | for started := false; ; started = true {
19 | b, err := r.ReadByte()
20 | if err == io.EOF {
21 | return nil
22 | }
23 | if err != nil {
24 | return err
25 | }
26 |
27 | switch {
28 | case b == ' ':
29 | continue
30 | case 'A' <= b && b <= 'Z':
31 | op, relative = b, false
32 | case 'a' <= b && b <= 'z':
33 | op, relative = b, true
34 | default:
35 | r.UnreadByte()
36 | }
37 |
38 | n := 0
39 | switch op {
40 | case 'H', 'h', 'V', 'v':
41 | n = 1
42 | case 'L', 'l', 'M', 'm', 'T', 't':
43 | n = 2
44 | case 'Q', 'q', 'S', 's':
45 | n = 4
46 | case 'C', 'c':
47 | n = 6
48 | case 'Z', 'z':
49 | default:
50 | return fmt.Errorf("unknown opcode %c", b)
51 | }
52 |
53 | scan(&args, r, n)
54 | normalize(&args, n, op, size, offset, outSize, relative)
55 |
56 | switch op {
57 | case 'H':
58 | enc.AbsHLineTo(args[0])
59 | case 'h':
60 | enc.RelHLineTo(args[0])
61 | case 'V':
62 | enc.AbsVLineTo(args[0])
63 | case 'v':
64 | enc.RelVLineTo(args[0])
65 | case 'L':
66 | enc.AbsLineTo(args[0], args[1])
67 | case 'l':
68 | enc.RelLineTo(args[0], args[1])
69 | case 'M':
70 | if !started {
71 | enc.StartPath(adj, args[0], args[1])
72 | } else {
73 | enc.ClosePathAbsMoveTo(args[0], args[1])
74 | }
75 | case 'm':
76 | enc.ClosePathRelMoveTo(args[0], args[1])
77 | case 'T':
78 | enc.AbsSmoothQuadTo(args[0], args[1])
79 | case 't':
80 | enc.RelSmoothQuadTo(args[0], args[1])
81 | case 'Q':
82 | enc.AbsQuadTo(args[0], args[1], args[2], args[3])
83 | case 'q':
84 | enc.RelQuadTo(args[0], args[1], args[2], args[3])
85 | case 'S':
86 | enc.AbsSmoothCubeTo(args[0], args[1], args[2], args[3])
87 | case 's':
88 | enc.RelSmoothCubeTo(args[0], args[1], args[2], args[3])
89 | case 'C':
90 | enc.AbsCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
91 | case 'c':
92 | enc.RelCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
93 | }
94 | }
95 | }
96 |
97 | func scan(args *[6]float32, r *strings.Reader, n int) {
98 | for i := 0; i < n; i++ {
99 | for {
100 | if b, _ := r.ReadByte(); b != ' ' {
101 | r.UnreadByte()
102 | break
103 | }
104 | }
105 | fmt.Fscanf(r, "%f", &args[i])
106 | }
107 | }
108 |
109 | func normalize(args *[6]float32, n int, op byte, size float32, offset f32.Vec2, outSize float32, relative bool) {
110 | for i := 0; i < n; i++ {
111 | args[i] *= outSize / size
112 | if relative {
113 | continue
114 | }
115 | args[i] -= outSize / 2
116 | switch {
117 | case n != 1:
118 | args[i] -= offset[i&0x01]
119 | case op == 'H':
120 | args[i] -= offset[0]
121 | case op == 'V':
122 | args[i] -= offset[1]
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/mdicons/parsedir.go:
--------------------------------------------------------------------------------
1 | package mdicons
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "os"
7 | "path"
8 | "path/filepath"
9 | "sort"
10 | "strings"
11 | )
12 |
13 | var skippedFiles = map[[2]string]bool{
14 | // ic_play_circle_filled_white_48px.svg is just the same as
15 | // ic_play_circle_filled_48px.svg with an explicit fill="#fff".
16 | {"av", "ic_play_circle_filled_white_48px.svg"}: true,
17 | }
18 |
19 | func ParseDir(mdicons, dirName string, outSize float32, out *bytes.Buffer) (Statistics, error) {
20 | var stats Statistics
21 |
22 | fqPNGDirName := filepath.FromSlash(path.Join(mdicons, dirName, "1x_web"))
23 | fqSVGDirName := filepath.FromSlash(path.Join(mdicons, dirName, "svg/production"))
24 | f, err := os.Open(fqSVGDirName)
25 | if err != nil {
26 | return stats, nil
27 | }
28 | defer f.Close()
29 |
30 | infos, err := f.Readdir(-1)
31 | if err != nil {
32 | return stats, err
33 | }
34 | baseNames, fileNames, sizes := []string{}, map[string]string{}, map[string]int{}
35 | for _, info := range infos {
36 | name := info.Name()
37 |
38 | if !strings.HasPrefix(name, "ic_") || skippedFiles[[2]string{dirName, name}] {
39 | continue
40 | }
41 | size := 0
42 | switch {
43 | case strings.HasSuffix(name, "_12px.svg"):
44 | size = 12
45 | case strings.HasSuffix(name, "_18px.svg"):
46 | size = 18
47 | case strings.HasSuffix(name, "_24px.svg"):
48 | size = 24
49 | case strings.HasSuffix(name, "_36px.svg"):
50 | size = 36
51 | case strings.HasSuffix(name, "_48px.svg"):
52 | size = 48
53 | default:
54 | continue
55 | }
56 |
57 | baseName := name[3 : len(name)-9]
58 | if prevSize, ok := sizes[baseName]; ok {
59 | if size > prevSize {
60 | fileNames[baseName] = name
61 | sizes[baseName] = size
62 | }
63 | } else {
64 | fileNames[baseName] = name
65 | sizes[baseName] = size
66 | baseNames = append(baseNames, baseName)
67 | }
68 | }
69 |
70 | sort.Strings(baseNames)
71 | for _, baseName := range baseNames {
72 | fileName := fileNames[baseName]
73 | stat, err := ParseFile(filepath.Join(fqSVGDirName, fileName), dirName, baseName, float32(sizes[baseName]), outSize, out)
74 | if err == ErrSkip {
75 | continue
76 | }
77 | if err != nil {
78 | stat.Failures = append(stat.Failures, fmt.Sprintf("%v/svg/production/%v: %v", dirName, fileName, err))
79 | continue
80 | }
81 | stats = stats.Add(stat)
82 | totalPNG24Bytes, fail := pngSize(fqPNGDirName, dirName, baseName, 24)
83 | if fail != "" {
84 | stats.Failures = append(stats.Failures, fail)
85 | } else {
86 | stats.TotalPNG24Bytes += totalPNG24Bytes
87 | }
88 | totalPNG48Bytes, fail := pngSize(fqPNGDirName, dirName, baseName, 48)
89 | if fail != "" {
90 | stats.Failures = append(stats.Failures, fail)
91 | } else {
92 | stats.TotalPNG48Bytes += totalPNG48Bytes
93 | }
94 | }
95 |
96 | return stats, nil
97 | }
98 |
99 | func pngSize(fqPNGDirName, dirName, baseName string, targetSize int) (int, string) {
100 | for _, size := range [...]int{48, 24, 18} {
101 | if size > targetSize {
102 | continue
103 | }
104 | fInfo, err := os.Stat(filepath.Join(fqPNGDirName,
105 | fmt.Sprintf("ic_%s_black_%ddp.png", baseName, size)))
106 | if err != nil {
107 | continue
108 | }
109 | return int(fInfo.Size()), ""
110 | }
111 | return 0, fmt.Sprintf("no PNG found for %s/1x_web/ic_%s_black_{48,24,18}dp.png", dirName, baseName)
112 | }
113 |
--------------------------------------------------------------------------------
/raster/gio/rasterizer.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package gio
4 |
5 | import (
6 | "image"
7 | "image/color"
8 | "image/draw"
9 | "math"
10 |
11 | "gioui.org/f32"
12 | "gioui.org/op"
13 | "gioui.org/op/clip"
14 | "gioui.org/op/paint"
15 |
16 | "github.com/reactivego/ivg/raster"
17 | )
18 |
19 | var (
20 | MinInf = float32(math.Inf(-1))
21 | MaxInf = float32(math.Inf(1))
22 | )
23 |
24 | type Rasterizer struct {
25 | Ops *op.Ops
26 |
27 | size image.Point
28 | path *clip.Path
29 | clipOp clip.Op
30 | minX, minY float32
31 | maxX, maxY float32
32 | }
33 |
34 | func NewRasterizer(ops *op.Ops, w, h int) *Rasterizer {
35 | v := &Rasterizer{
36 | Ops: ops,
37 | size: image.Pt(w, h),
38 | minX: MaxInf,
39 | minY: MaxInf,
40 | maxX: MinInf,
41 | maxY: MinInf,
42 | }
43 | return v
44 | }
45 |
46 | func (v *Rasterizer) Path() *clip.Path {
47 | if v.path == nil {
48 | v.path = new(clip.Path)
49 | if v.Ops == nil {
50 | v.Ops = new(op.Ops)
51 | }
52 | v.path.Begin(v.Ops)
53 | }
54 | return v.path
55 | }
56 |
57 | func (v *Rasterizer) Op() clip.Op {
58 | if v.path != nil {
59 | v.clipOp = clip.Outline{Path: v.path.End()}.Op()
60 | v.path = nil
61 | }
62 | return v.clipOp
63 | }
64 |
65 | func (v *Rasterizer) Reset(w, h int) {
66 | v.size = image.Pt(w, h)
67 | v.minX, v.minY = MaxInf, MaxInf
68 | v.maxX, v.maxY = MinInf, MinInf
69 | v.Op()
70 | }
71 |
72 | func (v *Rasterizer) Size() image.Point {
73 | return v.size
74 | }
75 |
76 | func (v *Rasterizer) Bounds() image.Rectangle {
77 | return image.Rectangle{Max: v.size}
78 | }
79 |
80 | func (v *Rasterizer) Pen() (x, y float32) {
81 | pos := v.path.Pos()
82 | return pos.X, pos.Y
83 | }
84 |
85 | func (v *Rasterizer) To(x, y float32) f32.Point {
86 | if x < v.minX {
87 | v.minX = float32(math.Floor(float64(x)))
88 | }
89 | if x > v.maxX {
90 | v.maxX = float32(math.Ceil(float64(x)))
91 | }
92 | if y < v.minY {
93 | v.minY = float32(math.Floor(float64(y)))
94 | }
95 | if y > v.maxY {
96 | v.maxY = float32(math.Ceil(float64(y)))
97 | }
98 | return f32.Pt(x, y)
99 | }
100 |
101 | func (v *Rasterizer) MoveTo(ax, ay float32) {
102 | v.Path().MoveTo(v.To(ax, ay))
103 | }
104 |
105 | func (v *Rasterizer) LineTo(bx, by float32) {
106 | v.Path().LineTo(v.To(bx, by))
107 | }
108 |
109 | func (v *Rasterizer) QuadTo(bx, by, cx, cy float32) {
110 | v.Path().QuadTo(f32.Pt(bx, by), v.To(cx, cy))
111 | }
112 |
113 | func (v *Rasterizer) CubeTo(bx, by, cx, cy, dx, dy float32) {
114 | v.Path().CubeTo(f32.Pt(bx, by), f32.Pt(cx, cy), v.To(dx, dy))
115 | }
116 |
117 | func (v *Rasterizer) ClosePath() {
118 | v.Path().Close()
119 | }
120 |
121 | func (v *Rasterizer) Draw(r image.Rectangle, src image.Image, sp image.Point) {
122 | clip := v.Op()
123 | tstack := op.Offset(r.Min).Push(v.Ops)
124 | cstack := clip.Push(v.Ops)
125 | switch source := src.(type) {
126 | case raster.GradientConfig:
127 | // TODO: If the gradient contains translucent colors we probably still must
128 | // convert the pixels using the RGBAModel from this package.
129 | gradient := image.NewRGBA(image.Rect(0, 0, r.Dx(), r.Dy()))
130 | destrect := image.Rect(int(v.minX), int(v.minY), int(v.maxX), int(v.maxY))
131 | draw.Draw(gradient, destrect, src, destrect.Min.Add(sp), draw.Src)
132 | paint.NewImageOp(gradient).Add(v.Ops)
133 | case *image.Uniform:
134 | c := color.NRGBAModel.Convert(source.C).(color.NRGBA)
135 | paint.ColorOp{Color: c}.Add(v.Ops)
136 | default:
137 | paint.NewImageOp(src).Add(v.Ops)
138 | }
139 | paint.PaintOp{}.Add(v.Ops)
140 | cstack.Pop()
141 | tstack.Pop()
142 | }
143 |
--------------------------------------------------------------------------------
/encode/buffer.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package encode
6 |
7 | import (
8 | "math"
9 |
10 | "github.com/reactivego/ivg"
11 | )
12 |
13 | // buffer holds an encoded IconVG graphic.
14 | //
15 | // The encodeXxx methods append to the buffer, modifying the slice in place.
16 | type buffer []byte
17 |
18 | func (b *buffer) encodeNatural(u uint32) {
19 | if u < 1<<7 {
20 | u = (u << 1)
21 | *b = append(*b, uint8(u))
22 | return
23 | }
24 | if u < 1<<14 {
25 | u = (u << 2) | 1
26 | *b = append(*b, uint8(u), uint8(u>>8))
27 | return
28 | }
29 | u = (u << 2) | 3
30 | *b = append(*b, uint8(u), uint8(u>>8), uint8(u>>16), uint8(u>>24))
31 | }
32 |
33 | func (b *buffer) encodeReal(f float32) int {
34 | if u := uint32(f); float32(u) == f && u < 1<<14 {
35 | if u < 1<<7 {
36 | u = (u << 1)
37 | *b = append(*b, uint8(u))
38 | return 1
39 | }
40 | u = (u << 2) | 1
41 | *b = append(*b, uint8(u), uint8(u>>8))
42 | return 2
43 | }
44 | b.encode4ByteReal(f)
45 | return 4
46 | }
47 |
48 | func (b *buffer) encode4ByteReal(f float32) {
49 | u := math.Float32bits(f)
50 |
51 | // Round the fractional bits (the low 23 bits) to the nearest multiple of
52 | // 4, being careful not to overflow into the upper bits.
53 | v := u & 0x007fffff
54 | if v < 0x007ffffe {
55 | v += 2
56 | }
57 | u = (u & 0xff800000) | v
58 |
59 | // A 4 byte encoding has the low two bits set.
60 | u |= 0x03
61 | *b = append(*b, uint8(u), uint8(u>>8), uint8(u>>16), uint8(u>>24))
62 | }
63 |
64 | func (b *buffer) encodeCoordinate(f float32) int {
65 | if i := int32(f); -64 <= i && i < +64 && float32(i) == f {
66 | u := uint32(i + 64)
67 | u = (u << 1)
68 | *b = append(*b, uint8(u))
69 | return 1
70 | }
71 | if i := int32(f * 64); -128*64 <= i && i < +128*64 && float32(i) == f*64 {
72 | u := uint32(i + 128*64)
73 | u = (u << 2) | 1
74 | *b = append(*b, uint8(u), uint8(u>>8))
75 | return 2
76 | }
77 | b.encode4ByteReal(f)
78 | return 4
79 | }
80 |
81 | func (b *buffer) encodeAngle(f float32) int {
82 | // Normalize f to the range [0, 1).
83 | g := float64(f)
84 | g -= math.Floor(g)
85 | return b.encodeZeroToOne(float32(g))
86 | }
87 |
88 | func (b *buffer) encodeZeroToOne(f float32) int {
89 | if u := uint32(f * 15120); float32(u) == f*15120 && u < 15120 {
90 | if u%126 == 0 {
91 | u = ((u / 126) << 1)
92 | *b = append(*b, uint8(u))
93 | return 1
94 | }
95 | u = (u << 2) | 1
96 | *b = append(*b, uint8(u), uint8(u>>8))
97 | return 2
98 | }
99 | b.encode4ByteReal(f)
100 | return 4
101 | }
102 |
103 | func (b *buffer) encodeColor1(c ivg.Color) {
104 | if x, ok := c.Encode1(); ok {
105 | *b = append(*b, x)
106 | return
107 | }
108 | // Default to opaque black.
109 | *b = append(*b, 0x00)
110 | }
111 |
112 | func (b *buffer) encodeColor2(c ivg.Color) {
113 | if x, ok := c.Encode2(); ok {
114 | *b = append(*b, x[0], x[1])
115 | return
116 | }
117 | // Default to opaque black.
118 | *b = append(*b, 0x00, 0x0f)
119 | }
120 |
121 | func (b *buffer) encodeColor3Direct(c ivg.Color) {
122 | if x, ok := c.Encode3Direct(); ok {
123 | *b = append(*b, x[0], x[1], x[2])
124 | return
125 | }
126 | // Default to opaque black.
127 | *b = append(*b, 0x00, 0x00, 0x00)
128 | }
129 |
130 | func (b *buffer) encodeColor4(c ivg.Color) {
131 | if x, ok := c.Encode4(); ok {
132 | *b = append(*b, x[0], x[1], x[2], x[3])
133 | return
134 | }
135 | // Default to opaque black.
136 | *b = append(*b, 0x00, 0x00, 0x00, 0xff)
137 | }
138 |
139 | func (b *buffer) encodeColor3Indirect(c ivg.Color) {
140 | if x, ok := c.Encode3Indirect(); ok {
141 | *b = append(*b, x[0], x[1], x[2])
142 | return
143 | }
144 | // Default to opaque black.
145 | *b = append(*b, 0x00, 0x00, 0x00)
146 | }
147 |
--------------------------------------------------------------------------------
/testdata/cowbell.svg:
--------------------------------------------------------------------------------
1 |
2 |
26 |
--------------------------------------------------------------------------------
/decode/decode_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package decode
6 |
7 | import (
8 | "bytes"
9 | "image/color"
10 | "os"
11 | "path/filepath"
12 | "runtime"
13 | "strings"
14 | "testing"
15 |
16 | "github.com/reactivego/ivg"
17 | "github.com/reactivego/ivg/encode"
18 | )
19 |
20 | // overwriteTestdataFiles is temporarily set to true when adding new
21 | // testdataTestCases.
22 | const overwriteTestdataFiles = false
23 |
24 | func diffLines(t *testing.T, got, want string) {
25 | gotLines := strings.Split(got, "\n")
26 | wantLines := strings.Split(want, "\n")
27 | for i := 1; ; i++ {
28 | if len(gotLines) == 0 {
29 | t.Errorf("line %d:\ngot %q\nwant %q", i, "", wantLines[0])
30 | return
31 | }
32 | if len(wantLines) == 0 {
33 | t.Errorf("line %d:\ngot %q\nwant %q", i, gotLines[0], "")
34 | return
35 | }
36 | g, w := gotLines[0], wantLines[0]
37 | gotLines = gotLines[1:]
38 | wantLines = wantLines[1:]
39 | if g != w {
40 | t.Errorf("line %d:\ngot %q\nwant %q", i, g, w)
41 | return
42 | }
43 | }
44 | }
45 |
46 | var testdataTestCases = []struct {
47 | filename string
48 | variants string
49 | }{
50 | {"../testdata/action-info.lores", ""},
51 | {"../testdata/action-info.hires", ""},
52 | {"../testdata/arcs", ""},
53 | {"../testdata/blank", ""},
54 | {"../testdata/cowbell", ""},
55 | {"../testdata/elliptical", ""},
56 | {"../testdata/favicon", ";pink"},
57 | {"../testdata/gradient", ""},
58 | {"../testdata/lod-polygon", ";64"},
59 | {"../testdata/video-005.primitive", ""},
60 | }
61 |
62 | func TestDisassembly(t *testing.T) {
63 | for _, tc := range testdataTestCases {
64 | ivgData, err := os.ReadFile(filepath.FromSlash(tc.filename) + ".ivg")
65 | if err != nil {
66 | t.Errorf("%s: ReadFile: %v", tc.filename, err)
67 | continue
68 | }
69 | got, err := Disassemble(ivgData)
70 | if err != nil {
71 | t.Errorf("%s: disassemble: %v", tc.filename, err)
72 | continue
73 | }
74 | wantFilename := filepath.FromSlash(tc.filename) + ".ivg.disassembly"
75 | if overwriteTestdataFiles {
76 | if err := os.WriteFile(filepath.FromSlash(wantFilename), got, 0666); err != nil {
77 | t.Errorf("%s: WriteFile: %v", tc.filename, err)
78 | }
79 | continue
80 | }
81 | want, err := os.ReadFile(wantFilename)
82 | if err != nil {
83 | t.Errorf("%s: ReadFile: %v", tc.filename, err)
84 | continue
85 | }
86 | if !bytes.Equal(got, want) {
87 | t.Errorf("%s: got:\n%s\nwant:\n%s", tc.filename, got, want)
88 | diffLines(t, string(got), string(want))
89 | }
90 | }
91 | }
92 |
93 | // The IconVG decoder and encoder are expected to be completely deterministic,
94 | // so check that we get the original bytes after a decode + encode round-trip.
95 | func TestDecodeEncodeRoundTrip(t *testing.T) {
96 | for _, tc := range testdataTestCases {
97 | ivgData, err := os.ReadFile(filepath.FromSlash(tc.filename) + ".ivg")
98 | if err != nil {
99 | t.Errorf("%s: ReadFile: %v", tc.filename, err)
100 | continue
101 | }
102 | var e resolutionPreservingEncoder
103 | e.HighResolutionCoordinates = strings.HasSuffix(tc.filename, ".hires")
104 | if err := Decode(&e, ivgData); err != nil {
105 | t.Errorf("%s: Decode: %v", tc.filename, err)
106 | continue
107 | }
108 | got, err := e.Bytes()
109 | if err != nil {
110 | t.Errorf("%s: Encoder.Bytes: %v", tc.filename, err)
111 | continue
112 | }
113 | if want := ivgData; !bytes.Equal(got, want) {
114 | t.Errorf("%s:\ngot %d bytes (on GOOS=%s GOARCH=%s, using compiler %q):\n% x\nwant %d bytes:\n% x",
115 | tc.filename, len(got), runtime.GOOS, runtime.GOARCH, runtime.Compiler, got, len(want), want)
116 | gotDisasm, err1 := Disassemble(got)
117 | wantDisasm, err2 := Disassemble(want)
118 | if err1 == nil && err2 == nil {
119 | diffLines(t, string(gotDisasm), string(wantDisasm))
120 | }
121 | }
122 | }
123 | }
124 |
125 | // resolutionPreservingEncoder is an Encoder
126 | // whose Reset method keeps prior resolution.
127 | type resolutionPreservingEncoder struct {
128 | encode.Encoder
129 | }
130 |
131 | // Reset resets the Encoder for the given Metadata.
132 | //
133 | // Unlike Encoder.Reset, it leaves the value
134 | // of e.HighResolutionCoordinates unmodified.
135 | func (e *resolutionPreservingEncoder) Reset(viewbox ivg.ViewBox, palette [64]color.RGBA) {
136 | orig := e.HighResolutionCoordinates
137 | e.Encoder.Reset(viewbox, palette)
138 | e.HighResolutionCoordinates = orig
139 | }
140 |
--------------------------------------------------------------------------------
/render_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package ivg_test
6 |
7 | import (
8 | "fmt"
9 | "image"
10 | "image/color"
11 | "image/draw"
12 | "image/png"
13 | "os"
14 | "path/filepath"
15 | "strings"
16 | "testing"
17 |
18 | "github.com/reactivego/ivg/decode"
19 | "github.com/reactivego/ivg/raster/img"
20 | "github.com/reactivego/ivg/render"
21 | )
22 |
23 | // overwriteTestdataFiles is temporarily set to true when adding new
24 | // testdataTestCases.
25 | const overwriteTestdataFiles = false
26 |
27 | func encodePNG(dstFilename string, src image.Image) error {
28 | f, err := os.Create(dstFilename)
29 | if err != nil {
30 | return err
31 | }
32 | encErr := png.Encode(f, src)
33 | closeErr := f.Close()
34 | if encErr != nil {
35 | return encErr
36 | }
37 | return closeErr
38 | }
39 |
40 | func decodePNG(srcFilename string) (image.Image, error) {
41 | f, err := os.Open(srcFilename)
42 | if err != nil {
43 | return nil, err
44 | }
45 | defer f.Close()
46 | return png.Decode(f)
47 | }
48 |
49 | func checkApproxEqual(m0, m1 image.Image) error {
50 | diff := func(a, b uint32) uint32 {
51 | if a < b {
52 | return b - a
53 | }
54 | return a - b
55 | }
56 |
57 | bounds0 := m0.Bounds()
58 | bounds1 := m1.Bounds()
59 | if bounds0 != bounds1 {
60 | return fmt.Errorf("bounds differ: got %v, want %v", bounds0, bounds1)
61 | }
62 | for y := bounds0.Min.Y; y < bounds0.Max.Y; y++ {
63 | for x := bounds0.Min.X; x < bounds0.Max.X; x++ {
64 | r0, g0, b0, a0 := m0.At(x, y).RGBA()
65 | r1, g1, b1, a1 := m1.At(x, y).RGBA()
66 |
67 | // TODO: be more principled in picking this magic threshold, other
68 | // than what the difference is, in practice, in x/image/vector's
69 | // fixed and floating point rasterizer?
70 | const D = 0xffff * 12 / 100 // Diff threshold of 12%.
71 |
72 | if diff(r0, r1) > D || diff(g0, g1) > D || diff(b0, b1) > D || diff(a0, a1) > D {
73 | return fmt.Errorf("at (%d, %d):\n"+
74 | "got RGBA %#04x, %#04x, %#04x, %#04x\n"+
75 | "want RGBA %#04x, %#04x, %#04x, %#04x",
76 | x, y, r0, g0, b0, a0, r1, g1, b1, a1)
77 | }
78 | }
79 | }
80 | return nil
81 | }
82 |
83 | var testdataTestCases = []struct {
84 | filename string
85 | variants string
86 | }{
87 | {"testdata/action-info.lores", ""},
88 | {"testdata/action-info.hires", ""},
89 | {"testdata/arcs", ""},
90 | {"testdata/blank", ""},
91 | {"testdata/cowbell", ""},
92 | {"testdata/elliptical", ""},
93 | {"testdata/favicon", ";pink"},
94 | {"testdata/gradient", ""},
95 | {"testdata/lod-polygon", ";64"},
96 | {"testdata/video-005.primitive", ""},
97 | }
98 |
99 | func TestRenderer(t *testing.T) {
100 | for _, tc := range testdataTestCases {
101 | ivgData, err := os.ReadFile(filepath.FromSlash(tc.filename) + ".ivg")
102 | if err != nil {
103 | t.Errorf("%s: ReadFile: %v", tc.filename, err)
104 | continue
105 | }
106 | vb, err := decode.DecodeViewBox(ivgData)
107 | if err != nil {
108 | t.Errorf("%s: DecodeViewBox: %v", tc.filename, err)
109 | continue
110 | }
111 |
112 | for _, variant := range strings.Split(tc.variants, ";") {
113 | length := 256
114 | if variant == "64" {
115 | length = 64
116 | }
117 | width, height := length, length
118 | if dx, dy := vb.Size(); dx < dy {
119 | width = int(float32(length) * dx / dy)
120 | } else {
121 | height = int(float32(length) * dy / dx)
122 | }
123 |
124 | opts := []decode.DecodeOption{}
125 | if variant == "pink" {
126 | pink := color.RGBA{0xfe, 0x76, 0xea, 0xff}
127 | opts = append(opts, decode.WithColorAt(0, pink))
128 | }
129 |
130 | bounds := image.Rect(0, 0, width, height)
131 | got := image.NewRGBA(bounds)
132 | var z render.Renderer
133 | z.SetRasterizer(&img.Rasterizer{Dst: got, DrawOp: draw.Src}, got.Bounds())
134 | if err := decode.Decode(&z, ivgData, opts...); err != nil {
135 | t.Errorf("%s %q variant: Decode: %v", tc.filename, variant, err)
136 | continue
137 | }
138 |
139 | wantFilename := filepath.FromSlash(tc.filename)
140 | if variant != "" {
141 | wantFilename += "." + variant
142 | }
143 | wantFilename += ".png"
144 | if overwriteTestdataFiles {
145 | if err := encodePNG(filepath.FromSlash(wantFilename), got); err != nil {
146 | t.Errorf("%s %q variant: encodePNG: %v", tc.filename, variant, err)
147 | }
148 | continue
149 | }
150 | want, err := decodePNG(wantFilename)
151 | if err != nil {
152 | t.Errorf("%s %q variant: decodePNG: %v", tc.filename, variant, err)
153 | continue
154 | }
155 | if err := checkApproxEqual(got, want); err != nil {
156 | t.Errorf("%s %q variant: %v", tc.filename, variant, err)
157 | continue
158 | }
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/testdata/arcs.ivg.disassembly:
--------------------------------------------------------------------------------
1 | 89 49 56 47 IconVG Magic identifier
2 | 00 Number of metadata chunks: 0
3 | 81 Set CREG[CSEL-1] to a 1 byte color
4 | 64 RGBA ff0000ff
5 | 82 Set CREG[CSEL-2] to a 1 byte color
6 | 78 RGBA ffff00ff
7 | 83 Set CREG[CSEL-3] to a 1 byte color
8 | 00 RGBA 000000ff
9 | 84 Set CREG[CSEL-4] to a 1 byte color
10 | 02 RGBA 000080ff
11 | c1 Start path, filled with CREG[CSEL-1]; M (absolute moveTo)
12 | 6c -10
13 | 80 +0
14 | e7 h (relative horizontal lineTo)
15 | 62 -15
16 | d0 a (relative arcTo), 1 reps
17 | 9e +15
18 | 9e +15
19 | 00 0 × 360 degrees (0 degrees)
20 | 02 0x1 (largeArc=1, sweep=0)
21 | 9e +15
22 | 62 -15
23 | e1 z (closePath); end path
24 | c2 Start path, filled with CREG[CSEL-2]; M (absolute moveTo)
25 | 64 -14
26 | 78 -4
27 | e9 v (relative vertical lineTo)
28 | 62 -15
29 | d0 a (relative arcTo), 1 reps
30 | 9e +15
31 | 9e +15
32 | 00 0 × 360 degrees (0 degrees)
33 | 00 0x0 (largeArc=0, sweep=0)
34 | 62 -15
35 | 9e +15
36 | e1 z (closePath); end path
37 | c3 Start path, filled with CREG[CSEL-3]; M (absolute moveTo)
38 | 62 -15
39 | bc +30
40 | 20 l (relative lineTo), 1 reps
41 | 8a +5
42 | 81 7d -2.5
43 | d0 a (relative arcTo), 1 reps
44 | 81 82 +2.5
45 | 81 82 +2.5
46 | dc 0.9166667 × 360 degrees (330 degrees)
47 | 04 0x2 (largeArc=0, sweep=1)
48 | 8a +5
49 | 81 7d -2.5
50 | 20 l (relative lineTo), 1 reps
51 | 8a +5
52 | 81 7d -2.5
53 | d0 a (relative arcTo), 1 reps
54 | 81 82 +2.5
55 | 8a +5
56 | dc 0.9166667 × 360 degrees (330 degrees)
57 | 04 0x2 (largeArc=0, sweep=1)
58 | 8a +5
59 | 81 7d -2.5
60 | 20 l (relative lineTo), 1 reps
61 | 8a +5
62 | 81 7d -2.5
63 | d0 a (relative arcTo), 1 reps
64 | 81 82 +2.5
65 | 81 87 +7.5
66 | dc 0.9166667 × 360 degrees (330 degrees)
67 | 04 0x2 (largeArc=0, sweep=1)
68 | 8a +5
69 | 81 7d -2.5
70 | 20 l (relative lineTo), 1 reps
71 | 8a +5
72 | 81 7d -2.5
73 | d0 a (relative arcTo), 1 reps
74 | 81 82 +2.5
75 | 94 +10
76 | dc 0.9166667 × 360 degrees (330 degrees)
77 | 04 0x2 (largeArc=0, sweep=1)
78 | 8a +5
79 | 81 7d -2.5
80 | 20 l (relative lineTo), 1 reps
81 | 8a +5
82 | 81 7d -2.5
83 | e8 V (absolute vertical lineTo)
84 | bc +30
85 | e1 z (closePath); end path
86 | c4 Start path, filled with CREG[CSEL-4]; M (absolute moveTo)
87 | 94 +10
88 | 48 -28
89 | d0 a (relative arcTo), 1 reps
90 | 8c +6
91 | 86 +3
92 | 00 0 × 360 degrees (0 degrees)
93 | 00 0x0 (largeArc=0, sweep=0)
94 | 8c +6
95 | 86 +3
96 | e1 z (closePath); end path
97 | c4 Start path, filled with CREG[CSEL-4]; M (absolute moveTo)
98 | a4 +18
99 | 48 -28
100 | d0 a (relative arcTo), 1 reps
101 | 8c +6
102 | 86 +3
103 | 00 0 × 360 degrees (0 degrees)
104 | 04 0x2 (largeArc=0, sweep=1)
105 | 8c +6
106 | 86 +3
107 | e1 z (closePath); end path
108 | c4 Start path, filled with CREG[CSEL-4]; M (absolute moveTo)
109 | 94 +10
110 | 58 -20
111 | d0 a (relative arcTo), 1 reps
112 | 8c +6
113 | 86 +3
114 | 00 0 × 360 degrees (0 degrees)
115 | 02 0x1 (largeArc=1, sweep=0)
116 | 8c +6
117 | 86 +3
118 | e1 z (closePath); end path
119 | c4 Start path, filled with CREG[CSEL-4]; M (absolute moveTo)
120 | a4 +18
121 | 58 -20
122 | d0 a (relative arcTo), 1 reps
123 | 8c +6
124 | 86 +3
125 | 00 0 × 360 degrees (0 degrees)
126 | 06 0x3 (largeArc=1, sweep=1)
127 | 8c +6
128 | 86 +3
129 | e1 z (closePath); end path
130 |
--------------------------------------------------------------------------------
/raster/gio/example/gradients/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "image"
8 | "image/color"
9 | "log"
10 | "os"
11 | "time"
12 |
13 | "golang.org/x/exp/shiny/materialdesign/colornames"
14 |
15 | "gioui.org/app"
16 | "gioui.org/io/pointer"
17 | "gioui.org/io/system"
18 | "gioui.org/layout"
19 | "gioui.org/op"
20 | "gioui.org/op/clip"
21 | "gioui.org/op/paint"
22 | "gioui.org/text"
23 |
24 | "github.com/reactivego/gio"
25 | "github.com/reactivego/gio/style"
26 |
27 | "github.com/reactivego/ivg"
28 | "github.com/reactivego/ivg/encode"
29 | "github.com/reactivego/ivg/generate"
30 | raster "github.com/reactivego/ivg/raster/gio"
31 | )
32 |
33 | func main() {
34 | go Gradients()
35 | app.Main()
36 | }
37 |
38 | func Gradients() {
39 | window := app.NewWindow(
40 | app.Title("IVG - Gradients"),
41 | app.Size(768, 768),
42 | )
43 |
44 | grey300 := color.NRGBAModel.Convert(colornames.Grey300).(color.NRGBA)
45 | grey800 := color.NRGBAModel.Convert(colornames.Grey800).(color.NRGBA)
46 | black := color.NRGBA{A: 255}
47 |
48 | data, err := GradientsIVG()
49 | if err != nil {
50 | log.Fatal(err)
51 | }
52 |
53 | ops := new(op.Ops)
54 | shaper := text.NewShaper(style.FontFaces())
55 | backend := "Gio"
56 | for next := range window.Events() {
57 | if event, ok := next.(system.FrameEvent); ok {
58 | gtx := layout.NewContext(ops, event)
59 |
60 | // backdrop
61 | pointer.InputOp{Tag: backend, Types: pointer.Release}.Add(gtx.Ops)
62 | for _, next := range event.Queue.Events(backend) {
63 | if event, ok := next.(pointer.Event); ok {
64 | if event.Type == pointer.Release {
65 | backend = map[string]string{"Gio": "Img", "Img": "Gio"}[backend]
66 | }
67 | }
68 | }
69 | paint.Fill(gtx.Ops, grey800)
70 |
71 | layout.UniformInset(12).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
72 | size := gtx.Constraints.Max
73 | paint.FillShape(ops, grey300, clip.Rect(image.Rectangle{Max: size}).Op())
74 | start := time.Now()
75 | var widget layout.Widget
76 | switch backend {
77 | case "Gio":
78 | widget, _ = raster.Widget(data, 48, 48)
79 | case "Img":
80 | widget, _ = raster.Widget(data, 48, 48, raster.WithImageBackend())
81 | }
82 | widget(gtx)
83 | msg := fmt.Sprintf("%s (%v)", backend, time.Since(start).Round(time.Microsecond))
84 | text := gio.Text(shaper, style.H5, 0.0, 0.0, black, msg)
85 | text(gtx)
86 | return layout.Dimensions{Size: size}
87 | })
88 |
89 | event.Frame(ops)
90 | }
91 | }
92 | os.Exit(0)
93 | }
94 |
95 | func GradientsIVG() ([]byte, error) {
96 | enc := &encode.Encoder{}
97 | gen := generate.Generator{}
98 | gen.SetDestination(enc)
99 |
100 | viewbox := ivg.ViewBox{
101 | MinX: -32, MinY: -32,
102 | MaxX: +32, MaxY: +32}
103 | gen.Reset(viewbox, ivg.DefaultPalette)
104 |
105 | rgb := []generate.GradientStop{
106 | {Offset: 0.00, Color: color.RGBA{0xff, 0x00, 0x00, 0xff}},
107 | {Offset: 0.25, Color: color.RGBA{0x00, 0xff, 0x00, 0xff}},
108 | {Offset: 0.50, Color: color.RGBA{0x00, 0x00, 0xff, 0xff}},
109 | {Offset: 1.00, Color: color.RGBA{0x00, 0x00, 0x00, 0xff}},
110 | }
111 | cmy := []generate.GradientStop{
112 | {Offset: 0.00, Color: color.RGBA{0x00, 0xff, 0xff, 0xff}},
113 | {Offset: 0.25, Color: color.RGBA{0xff, 0xff, 0xff, 0xff}},
114 | {Offset: 0.50, Color: color.RGBA{0xff, 0x00, 0xff, 0xff}},
115 | {Offset: 0.75, Color: color.RGBA{0x00, 0x00, 0x00, 0x00}},
116 | {Offset: 1.00, Color: color.RGBA{0xff, 0xff, 0x00, 0xff}},
117 | }
118 |
119 | x1, y1 := float32(-12), float32(-30)
120 | x2, y2 := float32(+12), float32(-18)
121 | minX, minY := float32(-30), float32(-30)
122 | maxX, maxY := float32(+30), float32(-18)
123 |
124 | gen.SetLinearGradient(x1, y1, x2, y2, generate.GradientSpreadNone, rgb)
125 | gen.StartPath(0, minX, minY)
126 | gen.AbsHLineTo(maxX)
127 | gen.AbsVLineTo(maxY)
128 | gen.AbsHLineTo(minX)
129 | gen.ClosePathEndPath()
130 |
131 | x1, y1 = -12, -14
132 | x2, y2 = +12, -2
133 | minY = -14
134 | maxY = -2
135 |
136 | gen.SetLinearGradient(x1, y1, x2, y2, generate.GradientSpreadPad, cmy)
137 | gen.StartPath(0, minX, minY)
138 | gen.AbsHLineTo(maxX)
139 | gen.AbsVLineTo(maxY)
140 | gen.AbsHLineTo(minX)
141 | gen.ClosePathEndPath()
142 |
143 | cx, cy := float32(-8), float32(+8)
144 | rx, ry := float32(0), float32(+16)
145 | minY = +2
146 | maxY = +14
147 |
148 | gen.SetCircularGradient(cx, cy, rx, ry, generate.GradientSpreadReflect, rgb)
149 | gen.StartPath(0, minX, minY)
150 | gen.AbsHLineTo(maxX)
151 | gen.AbsVLineTo(maxY)
152 | gen.AbsHLineTo(minX)
153 | gen.ClosePathEndPath()
154 |
155 | cx, cy = -8, +24
156 | rx, ry = 0, 16
157 | minY = +18
158 | maxY = +30
159 |
160 | gen.SetCircularGradient(cx, cy, rx, ry, generate.GradientSpreadRepeat, cmy)
161 | gen.StartPath(0, minX, minY)
162 | gen.AbsHLineTo(maxX)
163 | gen.AbsVLineTo(maxY)
164 | gen.AbsHLineTo(minX)
165 | gen.ClosePathEndPath()
166 |
167 | return enc.Bytes()
168 | }
169 |
--------------------------------------------------------------------------------
/mdicons/parsefile.go:
--------------------------------------------------------------------------------
1 | package mdicons
2 |
3 | import (
4 | "bytes"
5 | "encoding/xml"
6 | "errors"
7 | "fmt"
8 | "os"
9 | "strconv"
10 | "strings"
11 |
12 | "github.com/reactivego/ivg"
13 | "github.com/reactivego/ivg/encode"
14 | "golang.org/x/image/math/f32"
15 | )
16 |
17 | var skippedPaths = map[string]string{
18 | // hardware/svg/production/ic_scanner_48px.svg contains a filled white
19 | // rectangle that is overwritten by the subsequent path.
20 | //
21 | // See https://github.com/google/material-design-icons/issues/490
22 | //
23 | // Matches
24 | "M16 34h22v4H16z": "#fff",
25 |
26 | // device/svg/production/ic_airplanemode_active_48px.svg and
27 | // maps/svg/production/ic_flight_48px.svg contain a degenerate path that
28 | // contains only one moveTo op.
29 | //
30 | // See https://github.com/google/material-design-icons/issues/491
31 | //
32 | // Matches
33 | "M20.36 18": "",
34 | }
35 |
36 | // ErrSkip is returned to deliberately skip generating an icon.
37 | //
38 | // When manually debugging one particular icon, it can be useful to add
39 | // something like:
40 | //
41 | // if baseName != "check_box" { return errSkip }
42 | //
43 | // at the top of func ParseFile.
44 | var ErrSkip = errors.New("skipping SVG to IconVG conversion")
45 |
46 | func ParseFile(fqSVGName, dirName, baseName string, size float32, outSize float32, out *bytes.Buffer) (Statistics, error) {
47 | stat := Statistics{}
48 |
49 | svgData, err := os.ReadFile(fqSVGName)
50 | if err != nil {
51 | return stat, err
52 | }
53 |
54 | varName := upperCase(dirName)
55 | for _, s := range strings.Split(baseName, "_") {
56 | varName += upperCase(s)
57 | }
58 | fmt.Fprintf(out, "var %s = []byte{", varName)
59 | defer fmt.Fprintf(out, "\n}\n\n")
60 | stat.VarNames = []string{varName}
61 |
62 | var enc encode.Encoder
63 | var dest ivg.Destination = &enc
64 | dest.Reset(
65 | ivg.ViewBox{
66 | MinX: -24, MinY: -24,
67 | MaxX: +24, MaxY: +24},
68 | ivg.DefaultPalette)
69 |
70 | g := &SVG{}
71 | if err := xml.Unmarshal(svgData, g); err != nil {
72 | return stat, err
73 | }
74 |
75 | var vbx, vby float32
76 | for i, v := range strings.Split(g.ViewBox, " ") {
77 | f, err := strconv.ParseFloat(v, 32)
78 | if err != nil {
79 | return stat, err
80 | }
81 | switch i {
82 | case 0:
83 | vbx = float32(f)
84 | case 1:
85 | vby = float32(f)
86 | }
87 | }
88 | offset := f32.Vec2{
89 | vbx * outSize / size,
90 | vby * outSize / size,
91 | }
92 |
93 | // adjs maps from opacity to a cReg adj value.
94 | adjs := map[float32]uint8{}
95 |
96 | for _, p := range g.Paths {
97 | if fill, ok := skippedPaths[p.D]; ok && fill == p.Fill {
98 | continue
99 | }
100 | if err := ParsePath(dest, &p, adjs, size, offset, outSize, g.Circles); err != nil {
101 | return stat, err
102 | }
103 | g.Circles = nil
104 | }
105 |
106 | if len(g.Circles) != 0 {
107 | if err := ParsePath(dest, &Path{}, adjs, size, offset, outSize, g.Circles); err != nil {
108 | return stat, err
109 | }
110 | g.Circles = nil
111 | }
112 |
113 | ivgData, err := enc.Bytes()
114 | if err != nil {
115 | return stat, err
116 | }
117 | for i, x := range ivgData {
118 | if i&0x0f == 0x00 {
119 | out.WriteByte('\n')
120 | }
121 | fmt.Fprintf(out, "%#02x, ", x)
122 | }
123 |
124 | stat.TotalFiles += 1
125 | stat.TotalSVGBytes += len(svgData)
126 | stat.TotalIVGBytes += len(ivgData)
127 | return stat, nil
128 | }
129 |
130 | func upperCase(s string) string {
131 | if a, ok := acronyms[s]; ok {
132 | return a
133 | }
134 | if c := s[0]; 'a' <= c && c <= 'z' {
135 | return string(c-0x20) + s[1:]
136 | }
137 | return s
138 | }
139 |
140 | var acronyms = map[string]string{
141 | "3d": "3D",
142 | "ac": "AC",
143 | "adb": "ADB",
144 | "airplanemode": "AirplaneMode",
145 | "atm": "ATM",
146 | "av": "AV",
147 | "ccw": "CCW",
148 | "cw": "CW",
149 | "din": "DIN",
150 | "dns": "DNS",
151 | "dvr": "DVR",
152 | "eta": "ETA",
153 | "ev": "EV",
154 | "gif": "GIF",
155 | "gps": "GPS",
156 | "hd": "HD",
157 | "hdmi": "HDMI",
158 | "hdr": "HDR",
159 | "http": "HTTP",
160 | "https": "HTTPS",
161 | "iphone": "IPhone",
162 | "iso": "ISO",
163 | "jpeg": "JPEG",
164 | "markunread": "MarkUnread",
165 | "mms": "MMS",
166 | "nfc": "NFC",
167 | "ondemand": "OnDemand",
168 | "pdf": "PDF",
169 | "phonelink": "PhoneLink",
170 | "png": "PNG",
171 | "rss": "RSS",
172 | "rv": "RV",
173 | "sd": "SD",
174 | "sim": "SIM",
175 | "sip": "SIP",
176 | "sms": "SMS",
177 | "streetview": "StreetView",
178 | "svideo": "SVideo",
179 | "textdirection": "TextDirection",
180 | "textsms": "TextSMS",
181 | "timelapse": "TimeLapse",
182 | "toc": "TOC",
183 | "tv": "TV",
184 | "usb": "USB",
185 | "vpn": "VPN",
186 | "wb": "WB",
187 | "wc": "WC",
188 | "whatshot": "WhatsHot",
189 | "wifi": "WiFi",
190 | }
191 |
--------------------------------------------------------------------------------
/raster/gio/go.sum:
--------------------------------------------------------------------------------
1 | eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
2 | eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
3 | gioui.org v0.1.0 h1:fEDY5A4+epOdzjCBYSUC4BzvjWqsjfqf5D6mskbthOs=
4 | gioui.org v0.1.0/go.mod h1:a3hz8FyrPMkt899D9YrxMGtyRzpPrJpz1Lzbssn81vI=
5 | gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
6 | gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7 h1:tNJdnP5CgM39PRc+KWmBRRYX/zJ+rd5XaYxY5d5veqA=
7 | gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
8 | gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y=
9 | gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
10 | github.com/go-text/typesetting v0.0.0-20230717141307-09c70c30a055 h1:aUv3DYpk2eraRoyB7QZkyxgTVF7DrWcUui93iFRYO+8=
11 | github.com/go-text/typesetting v0.0.0-20230717141307-09c70c30a055/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
12 | github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
13 | github.com/reactivego/gio v0.0.4 h1:dgcsnRoJHKy9hJnxypYtuUUWnLxD4Ydc10iMPbjAVL8=
14 | github.com/reactivego/gio v0.0.4/go.mod h1:kEJ+x0lwcefl2RIBuoBSQbHKdLENug9BuAUtqr3uDFk=
15 | github.com/reactivego/ivg v0.1.2 h1:dzLUhWOgG2mWo7QsSIeUpqjTAGzvKvMb5DHUUnOUypc=
16 | github.com/reactivego/ivg v0.1.2/go.mod h1:p6ifMIDLXFxiXHz8My5I+rh7sczhLhld0lVCTBnhN1Q=
17 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
18 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
19 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
20 | golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
21 | golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
22 | golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
23 | golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
24 | golang.org/x/image v0.9.0 h1:QrzfX26snvCM20hIhBwuHI/ThTg18b/+kcKdXHvnR+g=
25 | golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
26 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
27 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
28 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
29 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
30 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
31 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
32 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
33 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
34 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
35 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
36 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
37 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
38 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
39 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
40 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
41 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
42 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
43 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
44 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
45 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
46 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
47 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
48 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
49 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
50 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
51 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
52 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
53 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
54 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
55 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
56 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
57 |
--------------------------------------------------------------------------------
/ivg.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package ivg
6 |
7 | import (
8 | "image/color"
9 | )
10 |
11 | const Magic = "\x89IVG"
12 |
13 | var MagicBytes = []byte(Magic)
14 |
15 | const (
16 | MidViewBox = 0
17 | MidSuggestedPalette = 1
18 | )
19 |
20 | const (
21 | // Min aligns min of ViewBox with min of rect
22 | Min = 0.0
23 | // Mid aligns mid of ViewBox with mid of rect
24 | Mid = 0.5
25 | // Max aligns max of ViewBox with max of rect
26 | Max = 1.0
27 | )
28 |
29 | // ViewBox is a Rectangle
30 | type ViewBox struct {
31 | MinX, MinY, MaxX, MaxY float32
32 | }
33 |
34 | // Size returns the ViewBox's size in both dimensions. An IconVG graphic is
35 | // scalable; these dimensions do not necessarily map 1:1 to pixels.
36 | func (v ViewBox) Size() (dx, dy float32) {
37 | return v.MaxX - v.MinX, v.MaxY - v.MinY
38 | }
39 |
40 | // AspectMeet fits the ViewBox inside a rectangle of size dx,dy maintaining its aspect ratio.
41 | // The ax, ay argument determine the position of the resized viewbox in the
42 | // given rectangle. For example ax = Mid, ay = Mid will position the resized
43 | // viewbox always in the middle of the rectangle.
44 | func (v ViewBox) AspectMeet(dx, dy float32, ax, ay float32) (MinX, MinY, MaxX, MaxY float32) {
45 | vdx, vdy := v.Size()
46 | vbAR := vdx / vdy
47 | vdx, vdy = dx, dy
48 | if vdx/vdy < vbAR {
49 | vdy = vdx / vbAR
50 | } else {
51 | vdx = vdy * vbAR
52 | }
53 | minX := (dx - vdx) * ax
54 | maxX := minX + vdx
55 | minY := (dy - vdy) * ay
56 | maxY := minY + vdy
57 | return minX, minY, maxX, maxY
58 | }
59 |
60 | // AspectSlice fills the rectangle of size dx,dy maintaining the ViewBox's aspect ratio.
61 | // The ax,ay arguments determine the position of the resized viewbox in the given
62 | // rectangle. For example ax = Mid, ay = Mid will position the resized viewbox
63 | // always in the middle of the rectangle
64 | func (v ViewBox) AspectSlice(dx, dy float32, ax, ay float32) (MinX, MinY, MaxX, MaxY float32) {
65 | vdx, vdy := v.Size()
66 | vbAR := vdx / vdy
67 | vdx, vdy = dx, dy
68 | if vdx/vdy < vbAR {
69 | vdx = vdy * vbAR
70 | } else {
71 | vdy = vdx / vbAR
72 | }
73 | minX := (dx - vdx) * ax
74 | maxX := minX + vdx
75 | minY := (dy - vdy) * ay
76 | maxY := minY + vdy
77 | return minX, minY, maxX, maxY
78 | }
79 |
80 | // Metadata is an IconVG's metadata.
81 | type Metadata struct {
82 | ViewBox ViewBox
83 |
84 | // Palette is a 64 color palette. When encoding, it is the suggested
85 | // palette to place within the IconVG graphic. When decoding, it is either
86 | // the optional palette passed to Decode, or if no optional palette was
87 | // given, the suggested palette within the IconVG graphic.
88 | Palette [64]color.RGBA
89 | }
90 |
91 | // DefaultViewBox is the default ViewBox. Its values should not be modified.
92 | var DefaultViewBox = ViewBox{
93 | MinX: -32, MinY: -32,
94 | MaxX: +32, MaxY: +32,
95 | }
96 |
97 | // DefaultPalette is the default Palette. Its values should not be modified.
98 | var DefaultPalette = [64]color.RGBA{
99 | {0x00, 0x00, 0x00, 0xff},
100 | {0x00, 0x00, 0x00, 0xff},
101 | {0x00, 0x00, 0x00, 0xff},
102 | {0x00, 0x00, 0x00, 0xff},
103 | {0x00, 0x00, 0x00, 0xff},
104 | {0x00, 0x00, 0x00, 0xff},
105 | {0x00, 0x00, 0x00, 0xff},
106 | {0x00, 0x00, 0x00, 0xff},
107 | {0x00, 0x00, 0x00, 0xff},
108 | {0x00, 0x00, 0x00, 0xff},
109 | {0x00, 0x00, 0x00, 0xff},
110 | {0x00, 0x00, 0x00, 0xff},
111 | {0x00, 0x00, 0x00, 0xff},
112 | {0x00, 0x00, 0x00, 0xff},
113 | {0x00, 0x00, 0x00, 0xff},
114 | {0x00, 0x00, 0x00, 0xff},
115 | {0x00, 0x00, 0x00, 0xff},
116 | {0x00, 0x00, 0x00, 0xff},
117 | {0x00, 0x00, 0x00, 0xff},
118 | {0x00, 0x00, 0x00, 0xff},
119 | {0x00, 0x00, 0x00, 0xff},
120 | {0x00, 0x00, 0x00, 0xff},
121 | {0x00, 0x00, 0x00, 0xff},
122 | {0x00, 0x00, 0x00, 0xff},
123 | {0x00, 0x00, 0x00, 0xff},
124 | {0x00, 0x00, 0x00, 0xff},
125 | {0x00, 0x00, 0x00, 0xff},
126 | {0x00, 0x00, 0x00, 0xff},
127 | {0x00, 0x00, 0x00, 0xff},
128 | {0x00, 0x00, 0x00, 0xff},
129 | {0x00, 0x00, 0x00, 0xff},
130 | {0x00, 0x00, 0x00, 0xff},
131 | {0x00, 0x00, 0x00, 0xff},
132 | {0x00, 0x00, 0x00, 0xff},
133 | {0x00, 0x00, 0x00, 0xff},
134 | {0x00, 0x00, 0x00, 0xff},
135 | {0x00, 0x00, 0x00, 0xff},
136 | {0x00, 0x00, 0x00, 0xff},
137 | {0x00, 0x00, 0x00, 0xff},
138 | {0x00, 0x00, 0x00, 0xff},
139 | {0x00, 0x00, 0x00, 0xff},
140 | {0x00, 0x00, 0x00, 0xff},
141 | {0x00, 0x00, 0x00, 0xff},
142 | {0x00, 0x00, 0x00, 0xff},
143 | {0x00, 0x00, 0x00, 0xff},
144 | {0x00, 0x00, 0x00, 0xff},
145 | {0x00, 0x00, 0x00, 0xff},
146 | {0x00, 0x00, 0x00, 0xff},
147 | {0x00, 0x00, 0x00, 0xff},
148 | {0x00, 0x00, 0x00, 0xff},
149 | {0x00, 0x00, 0x00, 0xff},
150 | {0x00, 0x00, 0x00, 0xff},
151 | {0x00, 0x00, 0x00, 0xff},
152 | {0x00, 0x00, 0x00, 0xff},
153 | {0x00, 0x00, 0x00, 0xff},
154 | {0x00, 0x00, 0x00, 0xff},
155 | {0x00, 0x00, 0x00, 0xff},
156 | {0x00, 0x00, 0x00, 0xff},
157 | {0x00, 0x00, 0x00, 0xff},
158 | {0x00, 0x00, 0x00, 0xff},
159 | {0x00, 0x00, 0x00, 0xff},
160 | {0x00, 0x00, 0x00, 0xff},
161 | {0x00, 0x00, 0x00, 0xff},
162 | {0x00, 0x00, 0x00, 0xff},
163 | }
164 |
165 | // DefaultMetadata combines the default ViewBox and the default Palette.
166 | var DefaultMetadata = Metadata{
167 | ViewBox: DefaultViewBox,
168 | Palette: DefaultPalette,
169 | }
170 |
--------------------------------------------------------------------------------
/decode/buffer_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package decode
6 |
7 | import (
8 | "image/color"
9 | "math"
10 | "testing"
11 |
12 | "github.com/reactivego/ivg"
13 | )
14 |
15 | var naturalTestCases = []struct {
16 | in buffer
17 | want uint32
18 | wantN int
19 | }{{
20 | buffer{},
21 | 0,
22 | 0,
23 | }, {
24 | buffer{0x28},
25 | 20,
26 | 1,
27 | }, {
28 | buffer{0x59},
29 | 0,
30 | 0,
31 | }, {
32 | buffer{0x59, 0x83},
33 | 8406,
34 | 2,
35 | }, {
36 | buffer{0x07, 0x00, 0x80},
37 | 0,
38 | 0,
39 | }, {
40 | buffer{0x07, 0x00, 0x80, 0x3f},
41 | 266338305,
42 | 4,
43 | }}
44 |
45 | func TestDecodeNatural(t *testing.T) {
46 | for _, tc := range naturalTestCases {
47 | got, gotN := tc.in.decodeNatural()
48 | if got != tc.want || gotN != tc.wantN {
49 | t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
50 | }
51 | }
52 | }
53 |
54 | var realTestCases = []struct {
55 | in buffer
56 | want float32
57 | wantN int
58 | }{{
59 | buffer{0x28},
60 | 20,
61 | 1,
62 | }, {
63 | buffer{0x59, 0x83},
64 | 8406,
65 | 2,
66 | }, {
67 | buffer{0x07, 0x00, 0x80, 0x3f},
68 | 1.000000476837158203125,
69 | 4,
70 | }}
71 |
72 | func TestDecodeReal(t *testing.T) {
73 | for _, tc := range realTestCases {
74 | got, gotN := tc.in.decodeReal()
75 | if got != tc.want || gotN != tc.wantN {
76 | t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
77 | }
78 | }
79 | }
80 |
81 | var coordinateTestCases = []struct {
82 | in buffer
83 | want float32
84 | wantN int
85 | }{{
86 | buffer{0x8e},
87 | 7,
88 | 1,
89 | }, {
90 | buffer{0x81, 0x87},
91 | 7.5,
92 | 2,
93 | }, {
94 | buffer{0x03, 0x00, 0xf0, 0x40},
95 | 7.5,
96 | 4,
97 | }, {
98 | buffer{0x07, 0x00, 0xf0, 0x40},
99 | 7.5000019073486328125,
100 | 4,
101 | }}
102 |
103 | func TestDecodeCoordinate(t *testing.T) {
104 | for _, tc := range coordinateTestCases {
105 | got, gotN := tc.in.decodeCoordinate()
106 | if got != tc.want || gotN != tc.wantN {
107 | t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
108 | }
109 | }
110 | }
111 |
112 | func trunc(x float32) float32 {
113 | u := math.Float32bits(x)
114 | u &^= 0x03
115 | return math.Float32frombits(u)
116 | }
117 |
118 | var zeroToOneTestCases = []struct {
119 | in buffer
120 | want float32
121 | wantN int
122 | }{{
123 | buffer{0x0a},
124 | 1.0 / 24,
125 | 1,
126 | }, {
127 | buffer{0x41, 0x1a},
128 | 1.0 / 9,
129 | 2,
130 | }, {
131 | buffer{0x63, 0x0b, 0x36, 0x3b},
132 | trunc(1.0 / 360),
133 | 4,
134 | }}
135 |
136 | func TestDecodeZeroToOne(t *testing.T) {
137 | for _, tc := range zeroToOneTestCases {
138 | got, gotN := tc.in.decodeZeroToOne()
139 | if got != tc.want || gotN != tc.wantN {
140 | t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
141 | }
142 | }
143 | }
144 |
145 | var colorTestCases = []struct {
146 | in buffer
147 | decode func(buffer) (ivg.Color, int)
148 | want ivg.Color
149 | wantN int
150 | }{{
151 | buffer{},
152 | buffer.decodeColor1,
153 | ivg.Color{},
154 | 0,
155 | }, {
156 | buffer{0x00},
157 | buffer.decodeColor1,
158 | ivg.RGBAColor(color.RGBA{0x00, 0x00, 0x00, 0xff}),
159 | 1,
160 | }, {
161 | buffer{0x30},
162 | buffer.decodeColor1,
163 | ivg.RGBAColor(color.RGBA{0x40, 0xff, 0xc0, 0xff}),
164 | 1,
165 | }, {
166 | buffer{0x7c},
167 | buffer.decodeColor1,
168 | ivg.RGBAColor(color.RGBA{0xff, 0xff, 0xff, 0xff}),
169 | 1,
170 | }, {
171 | buffer{0x7d},
172 | buffer.decodeColor1,
173 | ivg.RGBAColor(color.RGBA{0xc0, 0xc0, 0xc0, 0xc0}),
174 | 1,
175 | }, {
176 | buffer{0x7e},
177 | buffer.decodeColor1,
178 | ivg.RGBAColor(color.RGBA{0x80, 0x80, 0x80, 0x80}),
179 | 1,
180 | }, {
181 | buffer{0x7f},
182 | buffer.decodeColor1,
183 | ivg.RGBAColor(color.RGBA{0x00, 0x00, 0x00, 0x00}),
184 | 1,
185 | }, {
186 | buffer{0x80},
187 | buffer.decodeColor1,
188 | ivg.PaletteIndexColor(0x00),
189 | 1,
190 | }, {
191 | buffer{0xbf},
192 | buffer.decodeColor1,
193 | ivg.PaletteIndexColor(0x3f),
194 | 1,
195 | }, {
196 | buffer{0xc0},
197 | buffer.decodeColor1,
198 | ivg.CRegColor(0x00),
199 | 1,
200 | }, {
201 | buffer{0xff},
202 | buffer.decodeColor1,
203 | ivg.CRegColor(0x3f),
204 | 1,
205 | }, {
206 | buffer{0x01},
207 | buffer.decodeColor2,
208 | ivg.Color{},
209 | 0,
210 | }, {
211 | buffer{0x38, 0x0f},
212 | buffer.decodeColor2,
213 | ivg.RGBAColor(color.RGBA{0x33, 0x88, 0x00, 0xff}),
214 | 2,
215 | }, {
216 | buffer{0x00, 0x02},
217 | buffer.decodeColor3Direct,
218 | ivg.Color{},
219 | 0,
220 | }, {
221 | buffer{0x30, 0x66, 0x07},
222 | buffer.decodeColor3Direct,
223 | ivg.RGBAColor(color.RGBA{0x30, 0x66, 0x07, 0xff}),
224 | 3,
225 | }, {
226 | buffer{0x00, 0x00, 0x03},
227 | buffer.decodeColor4,
228 | ivg.Color{},
229 | 0,
230 | }, {
231 | buffer{0x30, 0x66, 0x07, 0x80},
232 | buffer.decodeColor4,
233 | ivg.RGBAColor(color.RGBA{0x30, 0x66, 0x07, 0x80}),
234 | 4,
235 | }, {
236 | buffer{0x00, 0x04},
237 | buffer.decodeColor3Indirect,
238 | ivg.Color{},
239 | 0,
240 | }, {
241 | buffer{0x40, 0x7f, 0x82},
242 | buffer.decodeColor3Indirect,
243 | ivg.BlendColor(0x40, 0x7f, 0x82),
244 | 3,
245 | }}
246 |
247 | func TestDecodeColor(t *testing.T) {
248 | for _, tc := range colorTestCases {
249 | got, gotN := tc.decode(tc.in)
250 | if got != tc.want || gotN != tc.wantN {
251 | t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
252 | }
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/encode/buffer_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package encode
6 |
7 | import (
8 | "image/color"
9 | "math"
10 | "testing"
11 |
12 | "github.com/reactivego/ivg"
13 | )
14 |
15 | var naturalTestCases = []struct {
16 | in buffer
17 | want uint32
18 | wantN int
19 | }{{
20 | buffer{},
21 | 0,
22 | 0,
23 | }, {
24 | buffer{0x28},
25 | 20,
26 | 1,
27 | }, {
28 | buffer{0x59},
29 | 0,
30 | 0,
31 | }, {
32 | buffer{0x59, 0x83},
33 | 8406,
34 | 2,
35 | }, {
36 | buffer{0x07, 0x00, 0x80},
37 | 0,
38 | 0,
39 | }, {
40 | buffer{0x07, 0x00, 0x80, 0x3f},
41 | 266338305,
42 | 4,
43 | }}
44 |
45 | func TestEncodeNatural(t *testing.T) {
46 | for _, tc := range naturalTestCases {
47 | if tc.wantN == 0 {
48 | continue
49 | }
50 | var b buffer
51 | b.encodeNatural(tc.want)
52 | if got, want := string(b), string(tc.in); got != want {
53 | t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
54 | }
55 | }
56 | }
57 |
58 | var realTestCases = []struct {
59 | in buffer
60 | want float32
61 | wantN int
62 | }{{
63 | buffer{0x28},
64 | 20,
65 | 1,
66 | }, {
67 | buffer{0x59, 0x83},
68 | 8406,
69 | 2,
70 | }, {
71 | buffer{0x07, 0x00, 0x80, 0x3f},
72 | 1.000000476837158203125,
73 | 4,
74 | }}
75 |
76 | func TestEncodeReal(t *testing.T) {
77 | for _, tc := range realTestCases {
78 | var b buffer
79 | b.encodeReal(tc.want)
80 | if got, want := string(b), string(tc.in); got != want {
81 | t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
82 | }
83 | }
84 | }
85 |
86 | var coordinateTestCases = []struct {
87 | in buffer
88 | want float32
89 | wantN int
90 | }{{
91 | buffer{0x8e},
92 | 7,
93 | 1,
94 | }, {
95 | buffer{0x81, 0x87},
96 | 7.5,
97 | 2,
98 | }, {
99 | buffer{0x03, 0x00, 0xf0, 0x40},
100 | 7.5,
101 | 4,
102 | }, {
103 | buffer{0x07, 0x00, 0xf0, 0x40},
104 | 7.5000019073486328125,
105 | 4,
106 | }}
107 |
108 | func TestEncodeCoordinate(t *testing.T) {
109 | for _, tc := range coordinateTestCases {
110 | if tc.want == 7.5 && tc.wantN == 4 {
111 | // 7.5 can be encoded in fewer than 4 bytes.
112 | continue
113 | }
114 | var b buffer
115 | b.encodeCoordinate(tc.want)
116 | if got, want := string(b), string(tc.in); got != want {
117 | t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
118 | }
119 | }
120 | }
121 |
122 | func trunc(x float32) float32 {
123 | u := math.Float32bits(x)
124 | u &^= 0x03
125 | return math.Float32frombits(u)
126 | }
127 |
128 | var zeroToOneTestCases = []struct {
129 | in buffer
130 | want float32
131 | wantN int
132 | }{{
133 | buffer{0x0a},
134 | 1.0 / 24,
135 | 1,
136 | }, {
137 | buffer{0x41, 0x1a},
138 | 1.0 / 9,
139 | 2,
140 | }, {
141 | buffer{0x63, 0x0b, 0x36, 0x3b},
142 | trunc(1.0 / 360),
143 | 4,
144 | }}
145 |
146 | func TestEncodeZeroToOne(t *testing.T) {
147 | for _, tc := range zeroToOneTestCases {
148 | var b buffer
149 | b.encodeZeroToOne(tc.want)
150 | if got, want := string(b), string(tc.in); got != want {
151 | t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
152 | }
153 | }
154 | }
155 |
156 | var colorTestCases = []struct {
157 | in buffer
158 | encode func(*buffer, ivg.Color)
159 | want ivg.Color
160 | wantN int
161 | }{{
162 | buffer{},
163 | (*buffer).encodeColor1,
164 | ivg.Color{},
165 | 0,
166 | }, {
167 | buffer{0x00},
168 | (*buffer).encodeColor1,
169 | ivg.RGBAColor(color.RGBA{0x00, 0x00, 0x00, 0xff}),
170 | 1,
171 | }, {
172 | buffer{0x30},
173 | (*buffer).encodeColor1,
174 | ivg.RGBAColor(color.RGBA{0x40, 0xff, 0xc0, 0xff}),
175 | 1,
176 | }, {
177 | buffer{0x7c},
178 | (*buffer).encodeColor1,
179 | ivg.RGBAColor(color.RGBA{0xff, 0xff, 0xff, 0xff}),
180 | 1,
181 | }, {
182 | buffer{0x7d},
183 | (*buffer).encodeColor1,
184 | ivg.RGBAColor(color.RGBA{0xc0, 0xc0, 0xc0, 0xc0}),
185 | 1,
186 | }, {
187 | buffer{0x7e},
188 | (*buffer).encodeColor1,
189 | ivg.RGBAColor(color.RGBA{0x80, 0x80, 0x80, 0x80}),
190 | 1,
191 | }, {
192 | buffer{0x7f},
193 | (*buffer).encodeColor1,
194 | ivg.RGBAColor(color.RGBA{0x00, 0x00, 0x00, 0x00}),
195 | 1,
196 | }, {
197 | buffer{0x80},
198 | (*buffer).encodeColor1,
199 | ivg.PaletteIndexColor(0x00),
200 | 1,
201 | }, {
202 | buffer{0xbf},
203 | (*buffer).encodeColor1,
204 | ivg.PaletteIndexColor(0x3f),
205 | 1,
206 | }, {
207 | buffer{0xc0},
208 | (*buffer).encodeColor1,
209 | ivg.CRegColor(0x00),
210 | 1,
211 | }, {
212 | buffer{0xff},
213 | (*buffer).encodeColor1,
214 | ivg.CRegColor(0x3f),
215 | 1,
216 | }, {
217 | buffer{0x01},
218 | (*buffer).encodeColor2,
219 | ivg.Color{},
220 | 0,
221 | }, {
222 | buffer{0x38, 0x0f},
223 | (*buffer).encodeColor2,
224 | ivg.RGBAColor(color.RGBA{0x33, 0x88, 0x00, 0xff}),
225 | 2,
226 | }, {
227 | buffer{0x00, 0x02},
228 | (*buffer).encodeColor3Direct,
229 | ivg.Color{},
230 | 0,
231 | }, {
232 | buffer{0x30, 0x66, 0x07},
233 | (*buffer).encodeColor3Direct,
234 | ivg.RGBAColor(color.RGBA{0x30, 0x66, 0x07, 0xff}),
235 | 3,
236 | }, {
237 | buffer{0x00, 0x00, 0x03},
238 | (*buffer).encodeColor4,
239 | ivg.Color{},
240 | 0,
241 | }, {
242 | buffer{0x30, 0x66, 0x07, 0x80},
243 | (*buffer).encodeColor4,
244 | ivg.RGBAColor(color.RGBA{0x30, 0x66, 0x07, 0x80}),
245 | 4,
246 | }, {
247 | buffer{0x00, 0x04},
248 | (*buffer).encodeColor3Indirect,
249 | ivg.Color{},
250 | 0,
251 | }, {
252 | buffer{0x40, 0x7f, 0x82},
253 | (*buffer).encodeColor3Indirect,
254 | ivg.BlendColor(0x40, 0x7f, 0x82),
255 | 3,
256 | }}
257 |
258 | func TestEncodeColor(t *testing.T) {
259 | for _, tc := range colorTestCases {
260 | if tc.wantN == 0 {
261 | continue
262 | }
263 | var b buffer
264 | tc.encode(&b, tc.want)
265 | if got, want := string(b), string(tc.in); got != want {
266 | t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
267 | }
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/testdata/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/testdata/gradient.ivg.disassembly:
--------------------------------------------------------------------------------
1 | 89 49 56 47 IconVG Magic identifier
2 | 00 Number of metadata chunks: 0
3 | 98 Set CREG[CSEL-0] to a 4 byte color
4 | 04 0a 8a 00 gradient (NSTOPS=4, CBASE=10, NBASE=10, linear, none)
5 | 0a Set CSEL = 10
6 | 4a Set NSEL = 10
7 | ae Set NREG[NSEL-6] to a real number
8 | 8b 88 08 3d 0.03333333
9 | ad Set NREG[NSEL-5] to a real number
10 | 8b 88 88 3c 0.016666666
11 | ac Set NREG[NSEL-4] to a real number
12 | 6b 66 66 3f 0.9000001
13 | ab Set NREG[NSEL-3] to a real number
14 | 00 0
15 | aa Set NREG[NSEL-2] to a real number
16 | 00 0
17 | a9 Set NREG[NSEL-1] to a real number
18 | 00 0
19 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
20 | 64 RGBA ff0000ff
21 | af Set NREG[NSEL-0] to a real number; NSEL++
22 | 00 0
23 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
24 | 14 RGBA 00ff00ff
25 | bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
26 | 3c 0.25
27 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
28 | 04 RGBA 0000ffff
29 | bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
30 | 78 0.5
31 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
32 | 00 RGBA 000000ff
33 | af Set NREG[NSEL-0] to a real number; NSEL++
34 | 02 1
35 | 00 Set CSEL = 0
36 | 40 Set NSEL = 0
37 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
38 | 44 -30
39 | 44 -30
40 | e6 H (absolute horizontal lineTo)
41 | bc +30
42 | e8 V (absolute vertical lineTo)
43 | 5c -18
44 | e6 H (absolute horizontal lineTo)
45 | 44 -30
46 | e1 z (closePath); end path
47 | 98 Set CREG[CSEL-0] to a 4 byte color
48 | 05 4a 8a 00 gradient (NSTOPS=5, CBASE=10, NBASE=10, linear, pad)
49 | 0a Set CSEL = 10
50 | 4a Set NSEL = 10
51 | ae Set NREG[NSEL-6] to a real number
52 | 8b 88 08 3d 0.03333333
53 | ad Set NREG[NSEL-5] to a real number
54 | 8b 88 88 3c 0.016666666
55 | ac Set NREG[NSEL-4] to a real number
56 | 27 22 22 3f 0.63333344
57 | ab Set NREG[NSEL-3] to a real number
58 | 00 0
59 | aa Set NREG[NSEL-2] to a real number
60 | 00 0
61 | a9 Set NREG[NSEL-1] to a real number
62 | 00 0
63 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
64 | 18 RGBA 00ffffff
65 | af Set NREG[NSEL-0] to a real number; NSEL++
66 | 00 0
67 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
68 | 7c RGBA ffffffff
69 | bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
70 | 3c 0.25
71 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
72 | 68 RGBA ff00ffff
73 | bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
74 | 78 0.5
75 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
76 | 7f RGBA 00000000
77 | bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
78 | b4 0.75
79 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
80 | 78 RGBA ffff00ff
81 | af Set NREG[NSEL-0] to a real number; NSEL++
82 | 02 1
83 | 00 Set CSEL = 0
84 | 40 Set NSEL = 0
85 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
86 | 44 -30
87 | 64 -14
88 | e6 H (absolute horizontal lineTo)
89 | bc +30
90 | e8 V (absolute vertical lineTo)
91 | 7c -2
92 | e6 H (absolute horizontal lineTo)
93 | 44 -30
94 | e1 z (closePath); end path
95 | 98 Set CREG[CSEL-0] to a 4 byte color
96 | 04 8a ca 00 gradient (NSTOPS=4, CBASE=10, NBASE=10, radial, reflect)
97 | 0a Set CSEL = 10
98 | 4a Set NSEL = 10
99 | b6 Set NREG[NSEL-6] to a coordinate number
100 | 11 80 0.0625
101 | ad Set NREG[NSEL-5] to a real number
102 | 00 0
103 | bc Set NREG[NSEL-4] to a zero-to-one number
104 | 78 0.5
105 | ab Set NREG[NSEL-3] to a real number
106 | 00 0
107 | b2 Set NREG[NSEL-2] to a coordinate number
108 | 11 80 0.0625
109 | b1 Set NREG[NSEL-1] to a coordinate number
110 | 81 7f -0.5
111 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
112 | 64 RGBA ff0000ff
113 | af Set NREG[NSEL-0] to a real number; NSEL++
114 | 00 0
115 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
116 | 14 RGBA 00ff00ff
117 | bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
118 | 3c 0.25
119 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
120 | 04 RGBA 0000ffff
121 | bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
122 | 78 0.5
123 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
124 | 00 RGBA 000000ff
125 | af Set NREG[NSEL-0] to a real number; NSEL++
126 | 02 1
127 | 00 Set CSEL = 0
128 | 40 Set NSEL = 0
129 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
130 | 44 -30
131 | 84 +2
132 | e6 H (absolute horizontal lineTo)
133 | bc +30
134 | e8 V (absolute vertical lineTo)
135 | 9c +14
136 | e6 H (absolute horizontal lineTo)
137 | 44 -30
138 | e1 z (closePath); end path
139 | 98 Set CREG[CSEL-0] to a 4 byte color
140 | 05 ca ca 00 gradient (NSTOPS=5, CBASE=10, NBASE=10, radial, repeat)
141 | 0a Set CSEL = 10
142 | 4a Set NSEL = 10
143 | b6 Set NREG[NSEL-6] to a coordinate number
144 | 11 80 0.0625
145 | ad Set NREG[NSEL-5] to a real number
146 | 00 0
147 | bc Set NREG[NSEL-4] to a zero-to-one number
148 | 78 0.5
149 | ab Set NREG[NSEL-3] to a real number
150 | 00 0
151 | b2 Set NREG[NSEL-2] to a coordinate number
152 | 11 80 0.0625
153 | b1 Set NREG[NSEL-1] to a coordinate number
154 | 81 7e -1.5
155 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
156 | 18 RGBA 00ffffff
157 | af Set NREG[NSEL-0] to a real number; NSEL++
158 | 00 0
159 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
160 | 7c RGBA ffffffff
161 | bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
162 | 3c 0.25
163 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
164 | 68 RGBA ff00ffff
165 | bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
166 | 78 0.5
167 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
168 | 7f RGBA 00000000
169 | bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
170 | b4 0.75
171 | 87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
172 | 78 RGBA ffff00ff
173 | af Set NREG[NSEL-0] to a real number; NSEL++
174 | 02 1
175 | 00 Set CSEL = 0
176 | 40 Set NSEL = 0
177 | c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
178 | 44 -30
179 | a4 +18
180 | e6 H (absolute horizontal lineTo)
181 | bc +30
182 | e8 V (absolute vertical lineTo)
183 | bc +30
184 | e6 H (absolute horizontal lineTo)
185 | 44 -30
186 | e1 z (closePath); end path
187 |
--------------------------------------------------------------------------------
/raster/gio/example/cowbell/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "image"
8 | "image/color"
9 | "log"
10 | "os"
11 | "time"
12 |
13 | "golang.org/x/exp/shiny/materialdesign/colornames"
14 |
15 | "gioui.org/app"
16 | "gioui.org/io/system"
17 | "gioui.org/layout"
18 | "gioui.org/op"
19 | "gioui.org/op/clip"
20 | "gioui.org/op/paint"
21 | "gioui.org/text"
22 | "gioui.org/widget"
23 |
24 | "github.com/reactivego/gio"
25 | "github.com/reactivego/gio/style"
26 |
27 | "github.com/reactivego/ivg"
28 | "github.com/reactivego/ivg/encode"
29 | "github.com/reactivego/ivg/generate"
30 | raster "github.com/reactivego/ivg/raster/gio"
31 | )
32 |
33 | func main() {
34 | go Cowbell()
35 | app.Main()
36 | }
37 |
38 | func Cowbell() {
39 | window := app.NewWindow(
40 | app.Title("IVG - Cowbell"),
41 | app.Size(768, 768),
42 | )
43 |
44 | grey300 := color.NRGBAModel.Convert(colornames.Grey300).(color.NRGBA)
45 | grey800 := color.NRGBAModel.Convert(colornames.Grey800).(color.NRGBA)
46 | black := color.NRGBA{A: 255}
47 |
48 | data, err := CowbellIVG()
49 | if err != nil {
50 | log.Fatal(err)
51 | }
52 |
53 | ops := new(op.Ops)
54 | shaper := text.NewShaper(style.FontFaces())
55 | backdrop := widget.Clickable{}
56 | backend := "Gio"
57 | for next := range window.Events() {
58 | if event, ok := next.(system.FrameEvent); ok {
59 | gtx := layout.NewContext(ops, event)
60 |
61 | backdrop.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
62 | size := gtx.Constraints.Max
63 | paint.Fill(ops, grey800)
64 | return layout.Dimensions{Size: size}
65 | })
66 | for range backdrop.Clicks() {
67 | backend = map[string]string{"Img": "Gio", "Gio": "Img"}[backend]
68 | }
69 |
70 | layout.UniformInset(12).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
71 | size := gtx.Constraints.Max
72 | paint.FillShape(ops, grey300, clip.Rect(image.Rectangle{Max: size}).Op())
73 | start := time.Now()
74 | var widget layout.Widget
75 | switch backend {
76 | case "Gio":
77 | widget, _ = raster.Widget(data, 48, 48)
78 | case "Img":
79 | widget, _ = raster.Widget(data, 48, 48, raster.WithImageBackend())
80 | }
81 | widget(gtx)
82 | msg := fmt.Sprintf("%s (%v)", backend, time.Since(start).Round(time.Microsecond))
83 | text := gio.Text(shaper, style.H5, 0.0, 0.0, black, msg)
84 | text(gtx)
85 | return layout.Dimensions{Size: event.Size}
86 | })
87 | event.Frame(ops)
88 | }
89 | }
90 | os.Exit(0)
91 | }
92 |
93 | func CowbellIVG() ([]byte, error) {
94 | enc := &encode.Encoder{}
95 | gen := generate.Generator{}
96 | gen.SetDestination(enc)
97 |
98 | viewbox := ivg.ViewBox{
99 | MinX: 0, MinY: 0,
100 | MaxX: +48, MaxY: +48,
101 | }
102 | gen.Reset(viewbox, ivg.DefaultPalette)
103 |
104 | type Gradient struct {
105 | radial bool
106 |
107 | // Linear gradient coefficients.
108 | x1, y1 float32
109 | x2, y2 float32
110 | tx, ty float32
111 |
112 | // Radial gradient coefficients.
113 | cx, cy, r float32
114 | transform generate.Aff3
115 |
116 | stops []generate.GradientStop
117 | }
118 |
119 | gradients := []Gradient{{
120 | // The 0th element is unused.
121 | }, {
122 | radial: true,
123 | cx: -102.14,
124 | cy: 20.272,
125 | r: 18.012,
126 | transform: generate.Aff3{
127 | .33050, -.50775, 65.204,
128 | .17296, .97021, 16.495,
129 | },
130 | stops: []generate.GradientStop{
131 | {Offset: 0, Color: color.RGBA{0xed, 0xd4, 0x00, 0xff}},
132 | {Offset: 1, Color: color.RGBA{0xfc, 0xe9, 0x4f, 0xff}},
133 | },
134 | }, {
135 | radial: true,
136 | cx: -97.856,
137 | cy: 26.719,
138 | r: 18.61,
139 | transform: generate.Aff3{
140 | .35718, -.11527, 51.072,
141 | .044280, .92977, 7.6124,
142 | },
143 | stops: []generate.GradientStop{
144 | {Offset: 0, Color: color.RGBA{0xed, 0xd4, 0x00, 0xff}},
145 | {Offset: 1, Color: color.RGBA{0xfc, 0xe9, 0x4f, 0xff}},
146 | },
147 | }, {
148 | x1: -16.183,
149 | y1: 35.723,
150 | x2: -18.75,
151 | y2: 29.808,
152 | tx: 48.438,
153 | ty: -.22321,
154 | stops: []generate.GradientStop{
155 | {Offset: 0, Color: color.RGBA{0x39, 0x21, 0x00, 0xff}},
156 | {Offset: 1, Color: color.RGBA{0x0f, 0x08, 0x00, 0xff}},
157 | },
158 | }}
159 |
160 | type Path struct {
161 | c color.RGBA
162 | g int
163 | d string
164 | }
165 |
166 | paths := []Path{{
167 | g: 2,
168 | d: "m5.6684 17.968l.265-4.407 13.453 19.78.301 8.304-14.019-23.677z",
169 | }, {
170 | g: 1,
171 | d: "m19.299 33.482l-13.619-19.688 3.8435-2.684.0922-2.1237 4.7023-2.26 2.99 1.1274 4.56-1.4252 20.719 16.272-23.288 10.782z",
172 | }, {
173 | c: color.RGBA{0xfd * 127 / 255, 0xee * 127 / 255, 0x74 * 127 / 255, 127},
174 | d: "m19.285 32.845l-13.593-19.079 3.995-2.833.1689-2.0377 1.9171-.8635 18.829 18.965-11.317 5.848z",
175 | }, {
176 | c: color.RGBA{0xc4, 0xa0, 0x00, 0xff},
177 | d: "m19.211 40.055c-.11-.67-.203-2.301-.205-3.624l-.003-2.406-2.492-3.769c-3.334-5.044-11.448-17.211-9.6752-14.744.3211.447 1.6961 2.119 2.1874 2.656.4914.536 1.3538 1.706 1.9158 2.6 2.276 3.615 8.232 12.056 8.402 12.056.1 0 10.4-5.325 11.294-5.678.894-.354 11.25-4.542 11.45-4.342.506.506 1.27 7.466.761 8.08-.392.473-5.06 3.672-10.256 6.121-5.195 2.45-11.984 4.269-12.594 4.269-.421 0-.639-.338-.785-1.219z",
178 | }, {
179 | g: 3,
180 | d: "m19.825 33.646c.422-.68 10.105-5.353 10.991-5.753s9.881-4.123 10.468-4.009c.512.099.844 6.017.545 6.703-.23.527-8.437 4.981-9.516 5.523-1.225.616-11.642 4.705-12.145 4.369-.553-.368-.707-6.245-.343-6.833z",
181 | }, {
182 | c: color.RGBA{0x00, 0x00, 0x00, 0xff},
183 | d: "m21.982 5.8789-4.865 1.457-2.553-1.1914-5.3355 2.5743l-.015625.29688-.097656 1.8672-4.1855 2.7383.36719 4.5996.054687.0957s3.2427 5.8034 6.584 11.654c1.6707 2.9255 3.3645 5.861 4.6934 8.0938.66442 1.1164 1.2366 2.0575 1.6719 2.7363.21761.33942.40065.6121.54883.81641.07409.10215.13968.18665.20312.25976.06345.07312.07886.13374.27148.22461.27031.12752.38076.06954.54102.04883.16025-.02072.34015-.05724.55078-.10938.42126-.10427.95998-.26728 1.584-.4707 1.248-.40685 2.8317-.97791 4.3926-1.5586 3.1217-1.1614 6.1504-2.3633 6.1504-2.3633l.02539-.0098.02539-.01367s2.5368-1.3591 5.1211-2.8027c1.2922-.72182 2.5947-1.4635 3.6055-2.0723.50539-.30438.93732-.57459 1.2637-.79688.16318-.11114.29954-.21136.41211-.30273.11258-.09138.19778-.13521.30273-.32617.16048-.292.13843-.48235.1543-.78906s.01387-.68208.002-1.1094c-.02384-.8546-.09113-1.9133-.17188-2.9473-.161-2.067-.373-4.04-.373-4.04l-.021-.211-20.907-16.348zm-.209 1.1055 20.163 15.766c.01984.1875.19779 1.8625.34961 3.8066.08004 1.025.14889 2.0726.17188 2.8965.01149.41192.01156.76817-.002 1.0293-.01351.26113-.09532.47241-.0332.35938.05869-.10679.01987-.0289-.05664.0332s-.19445.14831-.34375.25c-.29859.20338-.72024.46851-1.2168.76758-.99311.59813-2.291 1.3376-3.5781 2.0566-2.5646 1.4327-5.0671 2.7731-5.0859 2.7832-.03276.01301-3.0063 1.1937-6.0977 2.3438-1.5542.5782-3.1304 1.1443-4.3535 1.543-.61154.19936-1.1356.35758-1.5137.45117-.18066.04472-.32333.07255-.41992.08594-.02937-.03686-.05396-.06744-.0957-.125-.128-.176-.305-.441-.517-.771-.424-.661-.993-1.594-1.655-2.705-1.323-2.223-3.016-5.158-4.685-8.08-3.3124-5.8-6.4774-11.465-6.5276-11.555l-.3008-3.787 4.1134-2.692.109-2.0777 4.373-2.1133 2.469 1.1523 4.734-1.4179z",
184 | }}
185 |
186 | inv := func(x *generate.Aff3) generate.Aff3 {
187 | invDet := 1 / (x[0]*x[4] - x[1]*x[3])
188 | return generate.Aff3{
189 | +x[4] * invDet,
190 | -x[1] * invDet,
191 | (x[1]*x[5] - x[2]*x[4]) * invDet,
192 | -x[3] * invDet,
193 | +x[0] * invDet,
194 | (x[2]*x[3] - x[0]*x[5]) * invDet,
195 | }
196 | }
197 |
198 | for _, path := range paths {
199 | switch {
200 | case path.c != (color.RGBA{}):
201 | gen.SetCReg(0, false, ivg.RGBAColor(path.c))
202 | case path.g != 0:
203 | g := gradients[path.g]
204 | if g.radial {
205 | iform := inv(&g.transform)
206 | iform[2] -= g.cx
207 | iform[5] -= g.cy
208 | for i := range iform {
209 | iform[i] /= g.r
210 | }
211 | gen.SetGradient(generate.GradientShapeRadial, generate.GradientSpreadPad, g.stops, iform)
212 | } else {
213 | x1 := g.x1 + g.tx
214 | y1 := g.y1 + g.ty
215 | x2 := g.x2 + g.tx
216 | y2 := g.y2 + g.ty
217 | gen.SetLinearGradient(x1, y1, x2, y2, generate.GradientSpreadPad, g.stops)
218 | }
219 | default:
220 | continue
221 | }
222 | gen.SetPathData(path.d, 0)
223 | }
224 |
225 | return enc.Bytes()
226 | }
227 |
--------------------------------------------------------------------------------
/encode/encode_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package encode
6 |
7 | import (
8 | "bytes"
9 | "image/color"
10 | "math"
11 | "os"
12 | "path/filepath"
13 | "runtime"
14 | "testing"
15 |
16 | "github.com/reactivego/ivg"
17 | )
18 |
19 | // overwriteTestdataFiles is temporarily set to true when adding new
20 | // testdataTestCases.
21 | const overwriteTestdataFiles = false
22 |
23 | // TestOverwriteTestdataFilesIsFalse tests that any change to
24 | // overwriteTestdataFiles is only temporary. Programmers are assumed to run "go
25 | // test" before sending out for code review or committing code.
26 | func TestOverwriteTestdataFilesIsFalse(t *testing.T) {
27 | if overwriteTestdataFiles {
28 | t.Errorf("overwriteTestdataFiles is true; do not commit code changes")
29 | }
30 | }
31 |
32 | func testEncode(t *testing.T, e *Encoder, wantFilename string) {
33 | got, err := e.Bytes()
34 | if err != nil {
35 | t.Fatalf("encoding: %v", err)
36 | }
37 | if overwriteTestdataFiles {
38 | if err := os.WriteFile(filepath.FromSlash(wantFilename), got, 0666); err != nil {
39 | t.Fatalf("WriteFile: %v", err)
40 | }
41 | return
42 | }
43 | want, err := os.ReadFile(filepath.FromSlash(wantFilename))
44 | if err != nil {
45 | t.Fatalf("ReadFile: %v", err)
46 | }
47 | if !bytes.Equal(got, want) {
48 | // The IconVG encoder is expected to be completely deterministic across all
49 | // platforms and Go compilers, so check that we get exactly the right bytes.
50 | //
51 | // If we get slightly different bytes on some supported platform (for example,
52 | // a new GOOS/GOARCH port, or a different but spec-compliant Go compiler) due
53 | // to non-determinism in floating-point math, the encoder needs to be fixed.
54 | //
55 | // See golang.org/issue/43219#issuecomment-748531069.
56 | t.Fatalf("\ngot %d bytes (on GOOS=%s GOARCH=%s, using compiler %q):\n% x\nwant %d bytes:\n% x",
57 | len(got), runtime.GOOS, runtime.GOARCH, runtime.Compiler, got, len(want), want)
58 | }
59 | }
60 |
61 | func TestEncodeActionInfo(t *testing.T) {
62 | for _, res := range []string{"lores", "hires"} {
63 | var e Encoder
64 | e.Reset(
65 | ivg.ViewBox{
66 | MinX: -24, MinY: -24,
67 | MaxX: +24, MaxY: +24,
68 | },
69 | ivg.DefaultPalette,
70 | )
71 | e.HighResolutionCoordinates = res == "hires"
72 |
73 | e.StartPath(0, 0, -20)
74 | e.AbsCubeTo(-11.05, -20, -20, -11.05, -20, 0)
75 | e.RelSmoothCubeTo(8.95, 20, 20, 20)
76 | e.RelSmoothCubeTo(20, -8.95, 20, -20)
77 | e.AbsSmoothCubeTo(11.05, -20, 0, -20)
78 | e.ClosePathRelMoveTo(2, 30)
79 | e.RelHLineTo(-4)
80 | e.AbsVLineTo(-2)
81 | e.RelHLineTo(4)
82 | e.RelVLineTo(12)
83 | e.ClosePathRelMoveTo(0, -16)
84 | e.RelHLineTo(-4)
85 | e.RelVLineTo(-4)
86 | e.RelHLineTo(4)
87 | e.RelVLineTo(4)
88 | e.ClosePathEndPath()
89 |
90 | testEncode(t, &e, "../testdata/action-info."+res+".ivg")
91 | }
92 | }
93 |
94 | func TestEncodeArcs(t *testing.T) {
95 | var e Encoder
96 |
97 | e.SetCReg(1, false, ivg.RGBAColor(color.RGBA{0xff, 0x00, 0x00, 0xff}))
98 | e.SetCReg(2, false, ivg.RGBAColor(color.RGBA{0xff, 0xff, 0x00, 0xff}))
99 | e.SetCReg(3, false, ivg.RGBAColor(color.RGBA{0x00, 0x00, 0x00, 0xff}))
100 | e.SetCReg(4, false, ivg.RGBAColor(color.RGBA{0x00, 0x00, 0x80, 0xff}))
101 |
102 | e.StartPath(1, -10, 0)
103 | e.RelHLineTo(-15)
104 | e.RelArcTo(15, 15, 0, true, false, 15, -15)
105 | e.ClosePathEndPath()
106 |
107 | e.StartPath(2, -14, -4)
108 | e.RelVLineTo(-15)
109 | e.RelArcTo(15, 15, 0, false, false, -15, 15)
110 | e.ClosePathEndPath()
111 |
112 | const thirtyDegrees = 30.0 / 360
113 | e.StartPath(3, -15, 30)
114 | e.RelLineTo(5.0, -2.5)
115 | e.RelArcTo(2.5, 2.5, -thirtyDegrees, false, true, 5.0, -2.5)
116 | e.RelLineTo(5.0, -2.5)
117 | e.RelArcTo(2.5, 5.0, -thirtyDegrees, false, true, 5.0, -2.5)
118 | e.RelLineTo(5.0, -2.5)
119 | e.RelArcTo(2.5, 7.5, -thirtyDegrees, false, true, 5.0, -2.5)
120 | e.RelLineTo(5.0, -2.5)
121 | e.RelArcTo(2.5, 10.0, -thirtyDegrees, false, true, 5.0, -2.5)
122 | e.RelLineTo(5.0, -2.5)
123 | e.AbsVLineTo(30)
124 | e.ClosePathEndPath()
125 |
126 | for largeArc := 0; largeArc <= 1; largeArc++ {
127 | for sweep := 0; sweep <= 1; sweep++ {
128 | e.StartPath(4, 10+8*float32(sweep), -28+8*float32(largeArc))
129 | e.RelArcTo(6, 3, 0, largeArc != 0, sweep != 0, 6, 3)
130 | e.ClosePathEndPath()
131 | }
132 | }
133 |
134 | testEncode(t, &e, "../testdata/arcs.ivg")
135 | }
136 |
137 | func TestEncodeBlank(t *testing.T) {
138 | var e Encoder
139 | testEncode(t, &e, "../testdata/blank.ivg")
140 | }
141 |
142 | func TestEncodeLODPolygon(t *testing.T) {
143 | var e Encoder
144 |
145 | poly := func(n int) {
146 | const r = 28
147 | angle := 2 * math.Pi / float64(n)
148 | e.StartPath(0, r, 0)
149 | for i := 1; i < n; i++ {
150 | e.AbsLineTo(
151 | float32(r*math.Cos(angle*float64(i))),
152 | float32(r*math.Sin(angle*float64(i))),
153 | )
154 | }
155 | e.ClosePathEndPath()
156 | }
157 |
158 | e.StartPath(0, -28, -20)
159 | e.AbsVLineTo(-28)
160 | e.AbsHLineTo(-20)
161 | e.ClosePathEndPath()
162 |
163 | e.SetLOD(0, 80)
164 | poly(3)
165 |
166 | e.SetLOD(80, positiveInfinity)
167 | poly(5)
168 |
169 | e.SetLOD(0, positiveInfinity)
170 | e.StartPath(0, +28, +20)
171 | e.AbsVLineTo(+28)
172 | e.AbsHLineTo(+20)
173 | e.ClosePathEndPath()
174 |
175 | testEncode(t, &e, "../testdata/lod-polygon.ivg")
176 | }
177 |
178 | var video005PrimitiveSVGData = []struct {
179 | r, g, b uint32
180 | x0, y0 int
181 | x1, y1 int
182 | x2, y2 int
183 | }{
184 | {0x17, 0x06, 0x05, 162, 207, 271, 186, 195, -16},
185 | {0xe9, 0xf5, 0xf8, -16, 179, 140, -11, 16, -8},
186 | {0x00, 0x04, 0x27, 97, 96, 221, 21, 214, 111},
187 | {0x89, 0xd9, 0xff, 262, -6, 271, 104, 164, -16},
188 | {0x94, 0xbd, 0xc5, 204, 104, 164, 207, 59, 104},
189 | {0xd4, 0x81, 0x3d, -16, 36, 123, 195, -16, 194},
190 | {0x00, 0x00, 0x00, 164, 19, 95, 77, 138, 13},
191 | {0x39, 0x11, 0x19, 50, 143, 115, 185, -4, 165},
192 | {0x00, 0x3d, 0x81, 86, 109, 53, 76, 90, 24},
193 | {0xfc, 0xc6, 0x9c, 31, 161, 80, 105, -16, 28},
194 | {0x9e, 0xdd, 0xff, 201, -7, 31, -16, 2, 60},
195 | {0x01, 0x20, 0x39, 132, 85, 240, -5, 173, 130},
196 | {0xfd, 0xbc, 0x8f, 193, 127, 231, 94, 250, 124},
197 | {0x43, 0x06, 0x00, 251, 207, 237, 83, 271, 97},
198 | {0x80, 0xbf, 0xee, 117, 134, 88, 177, 90, 28},
199 | {0x00, 0x00, 0x00, 127, 38, 172, 68, 223, 55},
200 | {0x19, 0x0e, 0x16, 201, 204, 161, 101, 271, 192},
201 | {0xf6, 0xaa, 0x71, 201, 164, 226, 141, 261, 152},
202 | {0xe0, 0x36, 0x00, -16, -2, 29, -16, -6, 58},
203 | {0xff, 0xe4, 0xba, 146, 45, 118, 75, 148, 76},
204 | {0x00, 0x00, 0x12, 118, 44, 107, 109, 100, 51},
205 | {0xbd, 0xd5, 0xe4, 271, 41, 253, -16, 211, 89},
206 | {0x52, 0x00, 0x00, 87, 127, 83, 150, 55, 111},
207 | {0x00, 0xb3, 0xa1, 124, 185, 135, 207, 194, 176},
208 | {0x22, 0x00, 0x00, 59, 151, 33, 124, 52, 169},
209 | {0xbe, 0xcb, 0xcb, 149, 42, 183, -16, 178, 47},
210 | {0xff, 0xd4, 0xb1, 211, 119, 184, 100, 182, 124},
211 | {0xff, 0xe1, 0x39, 73, 207, 140, 180, -13, 187},
212 | {0xa7, 0xb0, 0xad, 122, 181, 200, 182, 93, 82},
213 | {0x00, 0x00, 0x00, 271, 168, 170, 185, 221, 207},
214 | }
215 |
216 | func TestEncodeVideo005Primitive(t *testing.T) {
217 | // The division by 4 is because the SVG width is 256 units and the IconVG
218 | // width is 64 (from -32 to +32).
219 | //
220 | // The subtraction by 0.5 is because the SVG file contains the line:
221 | //
222 | scaleX := func(i int) float32 { return float32(i)/4 - (32 - 0.5/4) }
223 | scaleY := func(i int) float32 { return float32(i)/4 - (24 - 0.5/4) }
224 |
225 | var e Encoder
226 | e.Reset(
227 | ivg.ViewBox{
228 | MinX: -32, MinY: -24,
229 | MaxX: +32, MaxY: +24},
230 | ivg.DefaultPalette,
231 | )
232 |
233 | e.SetCReg(0, false, ivg.RGBAColor(color.RGBA{0x7c, 0x7e, 0x7c, 0xff}))
234 | e.StartPath(0, -32, -24)
235 | e.AbsHLineTo(+32)
236 | e.AbsVLineTo(+24)
237 | e.AbsHLineTo(-32)
238 | e.ClosePathEndPath()
239 |
240 | for _, v := range video005PrimitiveSVGData {
241 | e.SetCReg(0, false, ivg.RGBAColor(color.RGBA{
242 | uint8(v.r * 128 / 255),
243 | uint8(v.g * 128 / 255),
244 | uint8(v.b * 128 / 255),
245 | 128,
246 | }))
247 | e.StartPath(0, scaleX(v.x0), scaleY(v.y0))
248 | e.AbsLineTo(scaleX(v.x1), scaleY(v.y1))
249 | e.AbsLineTo(scaleX(v.x2), scaleY(v.y2))
250 | e.ClosePathEndPath()
251 | }
252 |
253 | testEncode(t, &e, "../testdata/video-005.primitive.ivg")
254 | }
255 |
--------------------------------------------------------------------------------
/logger.go:
--------------------------------------------------------------------------------
1 | package ivg
2 |
3 | import (
4 | "fmt"
5 | "image/color"
6 | )
7 |
8 | type DestinationLogger struct {
9 | Destination
10 | Alt bool
11 | }
12 |
13 | func (d *DestinationLogger) Reset(viewbox ViewBox, palette [64]color.RGBA) {
14 | if !d.Alt {
15 | fmt.Printf("Reset(viewbox:%#v, colors:%#v)\n", viewbox, palette)
16 | } else {
17 | fmt.Printf("dst.Reset(%#v, %#v)\n", viewbox, palette)
18 | }
19 | if d.Destination != nil {
20 | d.Destination.Reset(viewbox, palette)
21 | }
22 | }
23 |
24 | func (d *DestinationLogger) SetCSel(cSel uint8) {
25 | if !d.Alt {
26 | fmt.Printf("SetCSel(cSel:%d)\n", cSel)
27 | } else {
28 | fmt.Printf("dst.SetCSel(%d)\n", cSel)
29 | }
30 | if d.Destination != nil {
31 | d.Destination.SetCSel(cSel)
32 | }
33 | }
34 |
35 | func (d *DestinationLogger) SetNSel(nSel uint8) {
36 | if !d.Alt {
37 | fmt.Printf("SetNSel(nSel:%d)\n", nSel)
38 | } else {
39 | fmt.Printf("dst.SetNSel(%d)\n", nSel)
40 | }
41 | if d.Destination != nil {
42 | d.Destination.SetNSel(nSel)
43 | }
44 | }
45 |
46 | func (d *DestinationLogger) SetCReg(adj uint8, incr bool, c Color) {
47 | if !d.Alt {
48 | fmt.Printf("SetCReg(adj:%d, incr:%t, c:%#v)\n", adj, incr, c)
49 | } else {
50 | fmt.Printf("dst.SetCReg(%d, %t, %#v)\n", adj, incr, c)
51 | }
52 | if d.Destination != nil {
53 | d.Destination.SetCReg(adj, incr, c)
54 | }
55 | }
56 |
57 | func (d *DestinationLogger) SetNReg(adj uint8, incr bool, f float32) {
58 | if !d.Alt {
59 | fmt.Printf("SetNReg(adj:%d, incr:%t, f:%.2f)\n", adj, incr, f)
60 | } else {
61 | fmt.Printf("dst.SetNReg(%d, %t, %.2f)\n", adj, incr, f)
62 | }
63 | if d.Destination != nil {
64 | d.Destination.SetNReg(adj, incr, f)
65 | }
66 | }
67 |
68 | func (d *DestinationLogger) SetLOD(lod0, lod1 float32) {
69 | if !d.Alt {
70 | fmt.Printf("SetLOD(lod0:%.2f, lod1:%.2f)\n", lod0, lod1)
71 | } else {
72 | fmt.Printf("dst.SetLOD(%.2f, %.2f)\n", lod0, lod1)
73 | }
74 | if d.Destination != nil {
75 | d.Destination.SetLOD(lod0, lod1)
76 | }
77 | }
78 |
79 | func (d *DestinationLogger) StartPath(adj uint8, x, y float32) {
80 | if !d.Alt {
81 | fmt.Printf("StartPath(adj:%d, x:%.2f, y:%.2f)\n", adj, x, y)
82 | } else {
83 | fmt.Printf("dst.StartPath(%d, %.2f, %.2f)\n", adj, x, y)
84 | }
85 | if d.Destination != nil {
86 | d.Destination.StartPath(adj, x, y)
87 | }
88 | }
89 |
90 | func (d *DestinationLogger) ClosePathEndPath() {
91 | if !d.Alt {
92 | fmt.Println("ClosePathEndPath()")
93 | } else {
94 | fmt.Println("dst.ClosePathEndPath()")
95 | }
96 | if d.Destination != nil {
97 | d.Destination.ClosePathEndPath()
98 | }
99 | }
100 |
101 | func (d *DestinationLogger) ClosePathAbsMoveTo(x, y float32) {
102 | if !d.Alt {
103 | fmt.Printf("ClosePathAbsMoveTo(x:%.2f, y:%.2f)\n", x, y)
104 | } else {
105 | fmt.Printf("dst.ClosePathAbsMoveTo(%.2f, %.2f)\n", x, y)
106 | }
107 | if d.Destination != nil {
108 | d.Destination.ClosePathAbsMoveTo(x, y)
109 | }
110 | }
111 |
112 | func (d *DestinationLogger) ClosePathRelMoveTo(x, y float32) {
113 | if !d.Alt {
114 | fmt.Printf("ClosePathRelMoveTo(x:%.2f, y:%.2f)\n", x, y)
115 | } else {
116 | fmt.Printf("dst.ClosePathRelMoveTo(%.2f, %.2f)\n", x, y)
117 | }
118 | if d.Destination != nil {
119 | d.Destination.ClosePathRelMoveTo(x, y)
120 | }
121 | }
122 |
123 | func (d *DestinationLogger) AbsHLineTo(x float32) {
124 | if !d.Alt {
125 | fmt.Printf("AbsHLineTo(x:%.2f)\n", x)
126 | } else {
127 | fmt.Printf("dst.AbsHLineTo(%.2f)\n", x)
128 | }
129 | if d.Destination != nil {
130 | d.Destination.AbsHLineTo(x)
131 | }
132 | }
133 |
134 | func (d *DestinationLogger) RelHLineTo(x float32) {
135 | if !d.Alt {
136 | fmt.Printf("RelHLineTo(x:%.2f)\n", x)
137 | } else {
138 | fmt.Printf("dst.RelHLineTo(%.2f)\n", x)
139 | }
140 | if d.Destination != nil {
141 | d.Destination.RelHLineTo(x)
142 | }
143 | }
144 |
145 | func (d *DestinationLogger) AbsVLineTo(y float32) {
146 | if !d.Alt {
147 | fmt.Printf("AbsVLineTo(y:%.2f)\n", y)
148 | } else {
149 | fmt.Printf("dst.AbsVLineTo(%.2f)\n", y)
150 | }
151 | if d.Destination != nil {
152 | d.Destination.AbsVLineTo(y)
153 | }
154 | }
155 |
156 | func (d *DestinationLogger) RelVLineTo(y float32) {
157 | if !d.Alt {
158 | fmt.Printf("RelVLineTo(y:%.2f)\n", y)
159 | } else {
160 | fmt.Printf("dst.RelVLineTo(%.2f)\n", y)
161 | }
162 | if d.Destination != nil {
163 | d.Destination.RelVLineTo(y)
164 | }
165 | }
166 |
167 | func (d *DestinationLogger) AbsLineTo(x, y float32) {
168 | if !d.Alt {
169 | fmt.Printf("AbsLineTo(x:%.2f, y:%.2f)\n", x, y)
170 | } else {
171 | fmt.Printf("dst.AbsLineTo(%.2f, %.2f)\n", x, y)
172 | }
173 | if d.Destination != nil {
174 | d.Destination.AbsLineTo(x, y)
175 | }
176 | }
177 |
178 | func (d *DestinationLogger) RelLineTo(x, y float32) {
179 | if !d.Alt {
180 | fmt.Printf("RelLineTo(x:%.2f, y:%.2f)\n", x, y)
181 | } else {
182 | fmt.Printf("dst.RelLineTo(%.2f, %.2f)\n", x, y)
183 | }
184 | if d.Destination != nil {
185 | d.Destination.RelLineTo(x, y)
186 | }
187 | }
188 |
189 | func (d *DestinationLogger) AbsSmoothQuadTo(x, y float32) {
190 | if !d.Alt {
191 | fmt.Printf("AbsSmoothQuadTo(x:%.2f, y:%.2f)\n", x, y)
192 | } else {
193 | fmt.Printf("dst.AbsSmoothQuadTo(%.2f, %.2f)\n", x, y)
194 | }
195 | if d.Destination != nil {
196 | d.Destination.AbsSmoothQuadTo(x, y)
197 | }
198 | }
199 |
200 | func (d *DestinationLogger) RelSmoothQuadTo(x, y float32) {
201 | if !d.Alt {
202 | fmt.Printf("RelSmoothQuadTo(x:%.2f, y:%.2f)\n", x, y)
203 | } else {
204 | fmt.Printf("dst.RelSmoothQuadTo(%.2f, %.2f)\n", x, y)
205 | }
206 | if d.Destination != nil {
207 | d.Destination.RelSmoothQuadTo(x, y)
208 | }
209 | }
210 |
211 | func (d *DestinationLogger) AbsQuadTo(x1, y1, x, y float32) {
212 | if !d.Alt {
213 | fmt.Printf("AbsQuadTo(x1:%.2f, y1:%.2f, x:%.2f, y:%.2f)\n", x1, y1, x, y)
214 | } else {
215 | fmt.Printf("dst.AbsQuadTo(%.2f, %.2f, %.2f, %.2f)\n", x1, y1, x, y)
216 | }
217 | if d.Destination != nil {
218 | d.Destination.AbsQuadTo(x1, y1, x, y)
219 | }
220 | }
221 |
222 | func (d *DestinationLogger) RelQuadTo(x1, y1, x, y float32) {
223 | if !d.Alt {
224 | fmt.Printf("RelQuadTo(x1:%.2f, y1:%.2f, x:%.2f, y:%.2f)\n", x1, y1, x, y)
225 | } else {
226 | fmt.Printf("dst.RelQuadTo(%.2f, %.2f, %.2f, %.2f)\n", x1, y1, x, y)
227 | }
228 | if d.Destination != nil {
229 | d.Destination.RelQuadTo(x1, y1, x, y)
230 | }
231 | }
232 |
233 | func (d *DestinationLogger) AbsSmoothCubeTo(x2, y2, x, y float32) {
234 | if !d.Alt {
235 | fmt.Printf("AbsSmoothCubeTo(x2:%.2f, y2:%.2f, x:%.2f, y:%.2f)\n", x2, y2, x, y)
236 | } else {
237 | fmt.Printf("dst.AbsSmoothCubeTo(%.2f, %.2f, %.2f, %.2f)\n", x2, y2, x, y)
238 | }
239 | if d.Destination != nil {
240 | d.Destination.AbsSmoothCubeTo(x2, y2, x, y)
241 | }
242 | }
243 |
244 | func (d *DestinationLogger) RelSmoothCubeTo(x2, y2, x, y float32) {
245 | if !d.Alt {
246 | fmt.Printf("RelSmoothCubeTo(x2:%.2f, y2:%.2f, x:%.2f, y:%.2f)\n", x2, y2, x, y)
247 | } else {
248 | fmt.Printf("dst.RelSmoothCubeTo(%.2f, %.2f, %.2f, %.2f)\n", x2, y2, x, y)
249 | }
250 | if d.Destination != nil {
251 | d.Destination.RelSmoothCubeTo(x2, y2, x, y)
252 | }
253 | }
254 |
255 | func (d *DestinationLogger) AbsCubeTo(x1, y1, x2, y2, x, y float32) {
256 | if !d.Alt {
257 | fmt.Printf("AbsCubeTo(x1:%.2f, y1:%.2f, x2:%.2f, y2:%.2f, x:%.2f, y:%.2f)\n", x1, y1, x2, y2, x, y)
258 | } else {
259 | fmt.Printf("dst.AbsCubeTo(%.2f, %.2f, %.2f, %.2f, %.2f, %.2f)\n", x1, y1, x2, y2, x, y)
260 | }
261 | if d.Destination != nil {
262 | d.Destination.AbsCubeTo(x1, y1, x2, y2, x, y)
263 | }
264 | }
265 |
266 | func (d *DestinationLogger) RelCubeTo(x1, y1, x2, y2, x, y float32) {
267 | if !d.Alt {
268 | fmt.Printf("RelCubeTo(x1:%.2f, y1:%.2f, x2:%.2f, y2:%.2f, x:%.2f, y:%.2f)\n", x1, y1, x2, y2, x, y)
269 | } else {
270 | fmt.Printf("dst.RelCubeTo(%.2f, %.2f, %.2f, %.2f, %.2f, %.2f)\n", x1, y1, x2, y2, x, y)
271 | }
272 | if d.Destination != nil {
273 | d.Destination.RelCubeTo(x1, y1, x2, y2, x, y)
274 | }
275 | }
276 |
277 | func (d *DestinationLogger) AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
278 | if !d.Alt {
279 | fmt.Printf("AbsArcTo(rx:%.2f, ry:%.2f, xAxisRotation:%.2f, largeArc:%t, sweep:%t, x:%.2f, y:%.2f)\n", rx, ry, xAxisRotation, largeArc, sweep, x, y)
280 | } else {
281 | fmt.Printf("dst.AbsArcTo(%.2f, %.2f, %.2f, %t, %t, %.2f, %.2f)\n", rx, ry, xAxisRotation, largeArc, sweep, x, y)
282 | }
283 | if d.Destination != nil {
284 | d.Destination.AbsArcTo(rx, ry, xAxisRotation, largeArc, sweep, x, y)
285 | }
286 | }
287 |
288 | func (d *DestinationLogger) RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
289 | if !d.Alt {
290 | fmt.Printf("RelArcTo(rx:%.2f, ry:%.2f, xAxisRotation:%.2f, largeArc:%t, sweep:%t, x:%.2f, y:%.2f)\n", rx, ry, xAxisRotation, largeArc, sweep, x, y)
291 | } else {
292 | fmt.Printf("dst.RelArcTo(%.2f, %.2f, %.2f, %t, %t, %.2f, %.2f)\n", rx, ry, xAxisRotation, largeArc, sweep, x, y)
293 | }
294 | if d.Destination != nil {
295 | d.Destination.RelArcTo(rx, ry, xAxisRotation, largeArc, sweep, x, y)
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/render/gradient.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package render
6 |
7 | import (
8 | "image"
9 | "image/color"
10 | "math"
11 | )
12 |
13 | // TODO: gamma correction / non-linear color interpolation?
14 |
15 | // TODO: move this out of an internal directory, either under
16 | // golang.org/x/image or under the standard library's image, so that
17 | // golang.org/x/image/{draw,vector} and possibly image/draw can type switch on
18 | // the gradient.Gradient type and provide fast path code.
19 | //
20 | // Doing so requires coming up with a stable API that we'd be happy to support
21 | // in the long term. This would probably include an easier way to create
22 | // linear, circular and elliptical gradients, without having to explicitly
23 | // calculate the Aff3 matrix.
24 |
25 | // Shape is the gradient shape.
26 | type Shape uint8
27 |
28 | const (
29 | ShapeLinear Shape = iota
30 | ShapeRadial
31 | )
32 |
33 | // Spread is the gradient spread, or how to spread a gradient past its nominal
34 | // bounds (from offset being 0.0 to offset being 1.0).
35 | type Spread uint8
36 |
37 | const (
38 | // SpreadNone means that offsets outside of the [0, 1] range map to
39 | // transparent black.
40 | SpreadNone Spread = iota
41 | // SpreadPad means that offsets below 0 and above 1 map to the colors that
42 | // 0 and 1 would map to.
43 | SpreadPad
44 | // SpreadReflect means that the offset mapping is reflected start-to-end,
45 | // end-to-start, start-to-end, etc.
46 | SpreadReflect
47 | // SpreadRepeat means that the offset mapping is repeated start-to-end,
48 | // start-to-end, start-to-end, etc.
49 | SpreadRepeat
50 | )
51 |
52 | // Clamp clamps x to the range [0, 1]. If x is outside that range, it is
53 | // converted to a value in that range according to s's semantics. It returns -1
54 | // if s is SpreadNone and x is outside the range [0, 1].
55 | func (s Spread) Clamp(x float64) float64 {
56 | if x >= 0 {
57 | if x <= 1 {
58 | return x
59 | }
60 | switch s {
61 | case SpreadPad:
62 | return 1
63 | case SpreadReflect:
64 | if int(x)&1 == 0 {
65 | return x - math.Floor(x)
66 | }
67 | return math.Ceil(x) - x
68 | case SpreadRepeat:
69 | return x - math.Floor(x)
70 | }
71 | return -1
72 | }
73 | switch s {
74 | case SpreadPad:
75 | return 0
76 | case SpreadReflect:
77 | x = -x
78 | if int(x)&1 == 0 {
79 | return x - math.Floor(x)
80 | }
81 | return math.Ceil(x) - x
82 | case SpreadRepeat:
83 | return x - math.Floor(x)
84 | }
85 | return -1
86 | }
87 |
88 | // Stop is an offset and color.
89 | type Stop struct {
90 | Offset float64
91 | RGBA64 color.RGBA64
92 | }
93 |
94 | // Range is the range between two stops.
95 | type Range struct {
96 | Offset0 float64
97 | Offset1 float64
98 | Width float64
99 | R0 float64
100 | R1 float64
101 | G0 float64
102 | G1 float64
103 | B0 float64
104 | B1 float64
105 | A0 float64
106 | A1 float64
107 | }
108 |
109 | // MakeRange returns the range between two stops.
110 | func MakeRange(s0, s1 Stop) Range {
111 | return Range{
112 | Offset0: s0.Offset,
113 | Offset1: s1.Offset,
114 | Width: s1.Offset - s0.Offset,
115 | R0: float64(s0.RGBA64.R),
116 | R1: float64(s1.RGBA64.R),
117 | G0: float64(s0.RGBA64.G),
118 | G1: float64(s1.RGBA64.G),
119 | B0: float64(s0.RGBA64.B),
120 | B1: float64(s1.RGBA64.B),
121 | A0: float64(s0.RGBA64.A),
122 | A1: float64(s1.RGBA64.A),
123 | }
124 | }
125 |
126 | // AppendRanges appends to a the ranges defined by a's implicit final stop (if
127 | // any exist) and stops.
128 | func AppendRanges(a []Range, stops []Stop) []Range {
129 | if len(stops) == 0 {
130 | return nil
131 | }
132 | if len(a) != 0 {
133 | z := a[len(a)-1]
134 | a = append(a, MakeRange(Stop{
135 | Offset: z.Offset1,
136 | RGBA64: color.RGBA64{
137 | R: uint16(z.R1),
138 | G: uint16(z.G1),
139 | B: uint16(z.B1),
140 | A: uint16(z.A1),
141 | },
142 | }, stops[0]))
143 | }
144 | for i := 0; i < len(stops)-1; i++ {
145 | a = append(a, MakeRange(stops[i], stops[i+1]))
146 | }
147 | return a
148 | }
149 |
150 | // Aff3 is a 3x3 affine transformation matrix in row major order, where the
151 | // bottom row is implicitly [0 0 1].
152 | //
153 | // m[3*r + c] is the element in the r'th row and c'th column.
154 | type Aff3 [6]float64
155 |
156 | // Gradient is a very large image.Image (the same size as an image.Uniform)
157 | // whose colors form a gradient.
158 | type Gradient struct {
159 | Shape Shape
160 | Spread Spread
161 |
162 | // Pix2Grad transforms coordinates from pixel space (the arguments to the
163 | // Image.At method) to gradient space. Gradient space is where a linear
164 | // gradient ranges from x == 0 to x == 1, and a radial gradient has center
165 | // (0, 0) and radius 1.
166 | //
167 | // This is an affine transform, so it can represent elliptical gradients in
168 | // pixel space, including non-axis-aligned ellipses.
169 | //
170 | // For a linear gradient, the bottom row is ignored.
171 | Pix2Grad Aff3
172 |
173 | Ranges []Range
174 |
175 | // First and Last are the first and last stop's colors.
176 | First, Last color.RGBA64
177 | }
178 |
179 | // Init initializes g to a gradient whose geometry is defined by shape and
180 | // pix2Grad and whose colors are defined by spread and stops.
181 | func (g *Gradient) Init(shape Shape, spread Spread, pix2Grad Aff3, stops []Stop) bool {
182 | g.Shape = shape
183 | g.Spread = spread
184 | g.Pix2Grad = pix2Grad
185 | g.Ranges = AppendRanges(g.Ranges[:0], stops)
186 | if len(stops) == 0 {
187 | g.First = color.RGBA64{}
188 | g.Last = color.RGBA64{}
189 | } else {
190 | g.First = stops[0].RGBA64
191 | g.Last = stops[len(stops)-1].RGBA64
192 | }
193 | return len(g.Ranges) > 0
194 | }
195 |
196 | // ColorModel satisfies the image.Image interface.
197 | func (g *Gradient) ColorModel() color.Model {
198 | return color.RGBA64Model
199 | }
200 |
201 | // Bounds satisfies the image.Image interface.
202 | func (g *Gradient) Bounds() image.Rectangle {
203 | return image.Rectangle{
204 | Min: image.Point{-1e9, -1e9},
205 | Max: image.Point{+1e9, +1e9},
206 | }
207 | }
208 |
209 | // At satisfies the image.Image interface.
210 | func (g *Gradient) At(x, y int) color.Color {
211 | if len(g.Ranges) == 0 {
212 | return color.RGBA64{}
213 | }
214 |
215 | px := float64(x) + 0.5
216 | py := float64(y) + 0.5
217 |
218 | offset := 0.0
219 | if g.Shape == ShapeLinear {
220 | offset = g.Spread.Clamp(g.Pix2Grad[0]*px + g.Pix2Grad[1]*py + g.Pix2Grad[2])
221 | } else {
222 | gx := g.Pix2Grad[0]*px + g.Pix2Grad[1]*py + g.Pix2Grad[2]
223 | gy := g.Pix2Grad[3]*px + g.Pix2Grad[4]*py + g.Pix2Grad[5]
224 | offset = g.Spread.Clamp(math.Sqrt(gx*gx + gy*gy))
225 | }
226 | if !(offset >= 0) {
227 | return color.RGBA64{}
228 | }
229 |
230 | if offset < g.Ranges[0].Offset0 {
231 | return g.First
232 | }
233 | for _, r := range g.Ranges {
234 | if r.Offset0 <= offset && offset <= r.Offset1 {
235 | t := (offset - r.Offset0) / r.Width
236 | s := 1 - t
237 | return color.RGBA64{
238 | uint16(s*r.R0 + t*r.R1),
239 | uint16(s*r.G0 + t*r.G1),
240 | uint16(s*r.B0 + t*r.B1),
241 | uint16(s*r.A0 + t*r.A1),
242 | }
243 | }
244 | }
245 | return g.Last
246 | }
247 |
248 | // GradientShape returns 0 for a linear gradient and 1 for a radial gradient.
249 | func (g *Gradient) GradientShape() int {
250 | return int(g.Shape)
251 | }
252 |
253 | // SpreadMethod returns 0 for 'none', 1 for 'pad', 2 for 'reflect', 3 for
254 | // 'repeat'.
255 | func (g *Gradient) SpreadMethod() int {
256 | return int(g.Spread)
257 | }
258 |
259 | // StopOffsets returns as a slice the offsets of the gradient stops.
260 | func (g *Gradient) StopOffsets() []float64 {
261 | if len(g.Ranges) == 0 {
262 | return nil
263 | }
264 | offsets := make([]float64, len(g.Ranges)+1)
265 | for i, r := range g.Ranges {
266 | offsets[i] = r.Offset0
267 | offsets[i+1] = r.Offset1
268 | }
269 | return offsets
270 | }
271 |
272 | // StopColors returns as a slice the colors of the gradient stops.
273 | func (g *Gradient) StopColors() []color.RGBA {
274 | if len(g.Ranges) == 0 {
275 | return nil
276 | }
277 | colors := make([]color.RGBA, len(g.Ranges)+1)
278 | for i, r := range g.Ranges {
279 | colors[i] = color.RGBA{
280 | uint8(uint16(r.R0) >> 8),
281 | uint8(uint16(r.G0) >> 8),
282 | uint8(uint16(r.B0) >> 8),
283 | uint8(uint16(r.A0) >> 8),
284 | }
285 | }
286 | colors[len(colors)-1] = color.RGBA{
287 | uint8(g.Last.R >> 8),
288 | uint8(g.Last.G >> 8),
289 | uint8(g.Last.B >> 8),
290 | uint8(g.Last.A >> 8),
291 | }
292 | return colors
293 | }
294 |
295 | // Transform is the pixel space to gradient space affine transformation
296 | // matrix.
297 | // | a b c |
298 | // | d e f |
299 | func (g *Gradient) Transform() (a, b, c, d, e, f float64) {
300 | a = g.Pix2Grad[0]
301 | b = g.Pix2Grad[1]
302 | c = g.Pix2Grad[2]
303 | d = g.Pix2Grad[3]
304 | e = g.Pix2Grad[4]
305 | f = g.Pix2Grad[5]
306 | return
307 | }
308 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ivg
2 |
3 | import "github.com/reactivego/ivg"
4 |
5 | [](https://pkg.go.dev/github.com/reactivego/ivg#section-documentation)
6 |
7 | Package `ivg` provides a powerful implementation for rendering [IconVG](https://github.com/google/iconvg) icons through a flexible Rasterizer interface. IconVG is an efficient binary format designed specifically for vector graphic icons.
8 |
9 | This package enhances the original [IconVG](https://golang.org/x/exp/shiny/iconvg) implementation by introducing a modular vector graphics Rasterizer interface, replacing the original bitmap-only rendering system. This architectural change enables diverse rendering capabilities through different rasterizer implementations. Users can now choose between various output targets, such as bitmap images or gioui.org contexts, by implementing the appropriate rasterizer for their needs.
10 |
11 | To maintain clarity and avoid namespace confusion with the original implementation, this package has been renamed from `iconvg` to `ivg`.
12 |
13 | ## File Format Versions
14 |
15 | In order for the IconVG format to support animation in future incarnations. The format was simplified and updated to version 1 (FFV1), renaming the original format to FFV0 retroactively.
16 |
17 | FFV1 targets representing static vector graphics icons, while the future FFV2 will target representing animated vector graphics icons.
18 |
19 | The rationale for this was dicussed in a github proposal: [File Format Versions 1, 2 and Beyond](https://github.com/google/iconvg/issues/4#issue-905297018)
20 |
21 | Below are links to the different File Format Versions of the spec:
22 | - [IconVG FFV0](spec/iconvg-spec-v0.md)
23 | - [IconVG FFV1](https://github.com/google/iconvg/blob/97b0c08e6e298f5f3606f79f3fb38cc0d64d3198/spec/iconvg-spec.md)
24 |
25 | > NOTE: This package implements the [FFV0](spec/iconvg-spec-v0.md) version of the IconVG format.
26 |
27 | ## Code Organization
28 |
29 | The original purpose of IconVG was to convert a material design icon in SVG format to a binary data blob that could be embedded in a Go program.
30 |
31 | The code is organized in several packages that can be combined in different ways to create different IconVG render pipelines. The `Destination` interface is implemented both by the `Encoder` in package `encode` and by the `Renderer` in package `render`. The `Generator` type in the `generator` package just uses a `Destination` and doesn't care whether calls are generating a data blob or render directly to a `Rasterizer` via the `Renderer`.
32 |
33 | ```go
34 | // Destination handles the actions decoded from an IconVG graphic's opcodes.
35 | //
36 | // When passed to Decode, the first method called (if any) will be Reset. No
37 | // methods will be called at all if an error is encountered in the encoded form
38 | // before the metadata is fully decoded.
39 | type Destination interface {
40 | Reset(viewbox ViewBox, palette [64]color.RGBA)
41 | CSel() uint8
42 | SetCSel(cSel uint8)
43 | NSel() uint8
44 | SetNSel(nSel uint8)
45 | SetCReg(adj uint8, incr bool, c Color)
46 | SetNReg(adj uint8, incr bool, f float32)
47 | SetLOD(lod0, lod1 float32)
48 |
49 | StartPath(adj uint8, x, y float32)
50 | ClosePathEndPath()
51 | ClosePathAbsMoveTo(x, y float32)
52 | ClosePathRelMoveTo(x, y float32)
53 |
54 | AbsHLineTo(x float32)
55 | RelHLineTo(x float32)
56 | AbsVLineTo(y float32)
57 | RelVLineTo(y float32)
58 | AbsLineTo(x, y float32)
59 | RelLineTo(x, y float32)
60 | AbsSmoothQuadTo(x, y float32)
61 | RelSmoothQuadTo(x, y float32)
62 | AbsQuadTo(x1, y1, x, y float32)
63 | RelQuadTo(x1, y1, x, y float32)
64 | AbsSmoothCubeTo(x2, y2, x, y float32)
65 | RelSmoothCubeTo(x2, y2, x, y float32)
66 | AbsCubeTo(x1, y1, x2, y2, x, y float32)
67 | RelCubeTo(x1, y1, x2, y2, x, y float32)
68 | AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)
69 | RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)
70 | }
71 | ```
72 |
73 | A parser of SVG files reads the SVG and then calls methods on a `Destination` to produce a binary data blob.
74 |
75 | For Material Design icons:
76 |
77 | ```
78 | mdicons/Parse -> [Destination]encode/Encoder -> []byte
79 | ```
80 |
81 | For more complex SVGs a Generator supports handling of e.g. gradients and transforms. The Generator is hooked up to a `Destination` to produce the binary data blob.
82 |
83 | ```
84 | svgicon/Parse -> generate/Generator -> [Destination]encode/Encoder -> []byte
85 | ```
86 |
87 | To actually render the icon, the binary data blob would be passed to a `Decoder` that would call methods on a `Renderer` hooked up to a `Rasterizer` to render the icon.
88 |
89 | ```go
90 | // Rasterizer is a 2-D vector graphics rasterizer.
91 | type Rasterizer interface {
92 | // Reset resets a Rasterizer as if it was just returned by NewRasterizer.
93 | // This includes setting z.DrawOp to draw.Over.
94 | Reset(w, h int)
95 | // Size returns the width and height passed to NewRasterizer or Reset.
96 | Size() image.Point
97 | // Bounds returns the rectangle from (0, 0) to the width and height passed to
98 | // Reset.
99 | Bounds() image.Rectangle
100 | // Pen returns the location of the path-drawing pen: the last argument to the
101 | // most recent XxxTo call.
102 | Pen() (x, y float32)
103 | // MoveTo starts a new path and moves the pen to (ax, ay). The coordinates
104 | // are allowed to be out of the Rasterizer's bounds.
105 | MoveTo(ax, ay float32)
106 | // LineTo adds a line segment, from the pen to (bx, by), and moves the pen to
107 | // (bx, by). The coordinates are allowed to be out of the Rasterizer's
108 | // bounds.
109 | LineTo(bx, by float32)
110 | // QuadTo adds a quadratic Bézier segment, from the pen via (bx, by) to (cx,
111 | // cy), and moves the pen to (cx, cy). The coordinates are allowed to be out
112 | // of the Rasterizer's bounds.
113 | QuadTo(bx, by, cx, cy float32)
114 | // CubeTo adds a cubic Bézier segment, from the pen via (bx, by) and (cx, cy)
115 | // to (dx, dy), and moves the pen to (dx, dy). The coordinates are allowed to
116 | // be out of the Rasterizer's bounds.
117 | CubeTo(bx, by, cx, cy, dx, dy float32)
118 | // ClosePath closes the current path.
119 | ClosePath()
120 | // Draw aligns r.Min in z with sp in src and then replaces the rectangle r in
121 | // z with the result of a Porter-Duff composition. The vector paths
122 | // previously added via the XxxTo calls become the mask for drawing src onto
123 | // z.
124 | Draw(r image.Rectangle, src image.Image, sp image.Point)
125 | }
126 | ```
127 |
128 | Decoding a blob and rendering it to a `Rasterizer`:
129 |
130 | ```
131 | []byte -> decode/Decoder -> [Destination]render/Renderer -> [Rasterizer]raster/img/Rasterizer
132 | ```
133 |
134 | To render and icon from SVG, the Generator can also be hooked up to the `Renderer` directly and the `Encoder`/`Decoder` phase would be skipped.
135 |
136 | ```
137 | svgicon/Parse -> generate/Generator -> [Destination]render/Renderer -> [Rasterizer]raster/img/Rasterizer
138 | ```
139 |
140 | ## Changes
141 |
142 | This package changes the original IconVG code in several ways.
143 | The most important changes w.r.t. the original IconVG code are:
144 |
145 | 1. Separate code into packages with a clear purpose and responsibility for better cohesion and less coupling.
146 | 2. Split icon encoding into `encode` and `generate` package.
147 | 3. SVG gradient and path support is now part of `generate` package.
148 | 4. Rename `Rasterizer` to `Renderer` and place it in the `render` package.
149 | 5. Move `Destination` interface into root `ivg` package.
150 | 6. Make both `Encoder` and `Renderer` implement `Destination`.
151 | 7. Make both `Decoder` and `Generator` use only `Destination` interface.
152 | 8. `Generator` can now directly render by plugging in a `Renderer` (very useful).
153 | 9. `Encoder` can be plugged directly into a `Decoder` (useful for testing).
154 | 10. Abstract away rasterizing into a seperate package `raster`
155 | - Declare interface `Rasterizer`.
156 | - Declare interface `GradientConfig` implemented by `Renderer`.
157 | 11. Create a rasterizer using "golang.org/x/image/vector" in directory `raster/vec`
158 | 12. Create examples in directory `raster/gio/example`.
159 | - `playarrow` simplest example of rendering an icon.
160 | - `actioninfo` generate an icon on the fly, render it and cache the result.
161 | - The following examples allow you to see rendering and speed differences between rasterizers by clicking on the image to switch rasterizer.
162 | - `icons` renders golang.org/x/exp/shiny/materialdesign/icons.
163 | - `favicon` vector image with several blended layers.
164 | - `cowbell` vector image with several blended layers including gradients.
165 | - `gradients` vector image with lots of gradients.
166 |
167 | ## Acknowledgement
168 |
169 | The code in this package is based on [golang.org/x/exp/shiny/iconvg](https://github.com/golang/exp/tree/master/shiny/iconvg).
170 |
171 | The specification of the IconVG format has recently been moved to a separate repository [github.com/google/iconvg](https://github.com/google/iconvg).
172 |
173 |
174 | ## License
175 |
176 | Everything under the raster folder is Unlicense OR MIT (whichever you prefer). See file [raster/LICENSE](raster/LICENSE).
177 |
178 | All the other code is is governed by a BSD-style license that can be found in the [LICENSE](LICENSE) file.
179 |
--------------------------------------------------------------------------------
/color.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package ivg
6 |
7 | import (
8 | "fmt"
9 | "image/color"
10 | )
11 |
12 | // ColorType distinguishes types of Colors.
13 | type ColorType uint8
14 |
15 | const (
16 | // ColorTypeRGBA is a direct RGBA color.
17 | ColorTypeRGBA ColorType = iota
18 |
19 | // ColorTypePaletteIndex is an indirect color, indexing the custom palette.
20 | ColorTypePaletteIndex
21 |
22 | // ColorTypeCReg is an indirect color, indexing the CREG color registers.
23 | ColorTypeCReg
24 |
25 | // ColorTypeBlend is an indirect color, blending two other colors.
26 | ColorTypeBlend
27 | )
28 |
29 | // Color is an IconVG color, whose RGBA values can depend on context. Some
30 | // Colors are direct RGBA values. Other Colors are indirect, referring to an
31 | // index of the custom palette, a color register of the decoder virtual
32 | // machine, or a blend of two other Colors.
33 | //
34 | // See the "Colors" section in the package documentation for details.
35 | type Color struct {
36 | typ ColorType
37 | data color.RGBA
38 | }
39 |
40 | // RGBAColor returns a direct Color.
41 | func RGBAColor(c color.RGBA) Color { return Color{ColorTypeRGBA, c} }
42 |
43 | // PaletteIndexColor returns an indirect Color referring to an index of the
44 | // custom palette.
45 | func PaletteIndexColor(i uint8) Color { return Color{ColorTypePaletteIndex, color.RGBA{R: i & 0x3f}} }
46 |
47 | // CRegColor returns an indirect Color referring to a color register of the
48 | // decoder virtual machine.
49 | func CRegColor(i uint8) Color { return Color{ColorTypeCReg, color.RGBA{R: i & 0x3f}} }
50 |
51 | // BlendColor returns an indirect Color that blends two other Colors. Those two
52 | // other Colors must both be encodable as a 1 byte color.
53 | //
54 | // To blend a Color that is not encodable as a 1 byte color, first load that
55 | // Color into a CREG color register, then call CRegColor to produce a Color
56 | // that is encodable as a 1 byte color. See testdata/favicon.ivg for an
57 | // example.
58 | //
59 | // See the "Colors" section in the package documentation for details.
60 | func BlendColor(t, c0, c1 uint8) Color { return Color{ColorTypeBlend, color.RGBA{R: t, G: c0, B: c1}} }
61 |
62 | func (c Color) rgba() color.RGBA { return c.data }
63 | func (c Color) paletteIndex() uint8 { return c.data.R }
64 | func (c Color) cReg() uint8 { return c.data.R }
65 | func (c Color) blend() (t, c0, c1 uint8) { return c.data.R, c.data.G, c.data.B }
66 |
67 | // RGBA returns the color as a color.RGBA when that is its color type and the
68 | // color is a valid premultiplied color. If the color is of a different color
69 | // type or invalid, it will return a opaque black and false.
70 | func (c Color) RGBA() (color.RGBA, bool) {
71 | if c.typ != ColorTypeRGBA || !ValidAlphaPremulColor(c.data) {
72 | return color.RGBA{0x00, 0x00, 0x00, 0xff}, false
73 | }
74 | return c.data, true
75 | }
76 |
77 | // Resolve resolves the Color's RGBA value, given its context: the custom
78 | // palette and the color registers of the decoder virtual machine.
79 | func (c Color) Resolve(palette *[64]color.RGBA, cReg *[64]color.RGBA) color.RGBA {
80 | switch c.typ {
81 | case ColorTypeRGBA:
82 | return c.rgba()
83 | case ColorTypePaletteIndex:
84 | return palette[c.paletteIndex()&0x3f]
85 | case ColorTypeCReg:
86 | return cReg[c.cReg()&0x3f]
87 | case ColorTypeBlend:
88 | t, c0, c1 := c.blend()
89 | p, q := uint32(255-t), uint32(t)
90 | rgba0 := DecodeColor1(c0).Resolve(palette, cReg)
91 | rgba1 := DecodeColor1(c1).Resolve(palette, cReg)
92 | return color.RGBA{
93 | uint8(((p * uint32(rgba0.R)) + q*uint32(rgba1.R) + 128) / 255),
94 | uint8(((p * uint32(rgba0.G)) + q*uint32(rgba1.G) + 128) / 255),
95 | uint8(((p * uint32(rgba0.B)) + q*uint32(rgba1.B) + 128) / 255),
96 | uint8(((p * uint32(rgba0.A)) + q*uint32(rgba1.A) + 128) / 255),
97 | }
98 | }
99 | return color.RGBA{}
100 | }
101 |
102 | func DecodeColor1(x byte) Color {
103 | if x >= 0x80 {
104 | if x >= 0xc0 {
105 | return CRegColor(x)
106 | } else {
107 | return PaletteIndexColor(x)
108 | }
109 | }
110 | if x >= 125 {
111 | switch x - 125 {
112 | case 0:
113 | return RGBAColor(color.RGBA{0xc0, 0xc0, 0xc0, 0xc0})
114 | case 1:
115 | return RGBAColor(color.RGBA{0x80, 0x80, 0x80, 0x80})
116 | case 2:
117 | return RGBAColor(color.RGBA{0x00, 0x00, 0x00, 0x00})
118 | }
119 | }
120 | blue := dc1Table[x%5]
121 | x = x / 5
122 | green := dc1Table[x%5]
123 | x = x / 5
124 | red := dc1Table[x]
125 | return RGBAColor(color.RGBA{red, green, blue, 0xff})
126 | }
127 |
128 | var dc1Table = [5]byte{0x00, 0x40, 0x80, 0xc0, 0xff}
129 |
130 | func Is1(c color.RGBA) bool {
131 | is1 := func(u uint8) bool { return u&0x3f == 0 || u == 0xff }
132 | return is1(c.R) && is1(c.G) && is1(c.B) && is1(c.A)
133 | }
134 |
135 | func Is2(c color.RGBA) bool {
136 | is2 := func(u uint8) bool { return u%0x11 == 0 }
137 | return is2(c.R) && is2(c.G) && is2(c.B) && is2(c.A)
138 | }
139 |
140 | func Is3(c color.RGBA) bool {
141 | return c.A == 0xff
142 | }
143 |
144 | func ValidAlphaPremulColor(c color.RGBA) bool {
145 | return c.R <= c.A && c.G <= c.A && c.B <= c.A
146 | }
147 |
148 | // ValidGradient returns true if the RGBA color is non-sensical
149 | func ValidGradient(c color.RGBA) bool {
150 | return c.A == 0 && c.B&0x80 != 0
151 | }
152 |
153 | // EncodeGradient returns a non-sensical RGBA color encoding gradient
154 | // parameters.
155 | func EncodeGradient(cBase, nBase, shape, spread, nStops uint8) color.RGBA {
156 | cBase &= 0x3f
157 | nBase &= 0x3f
158 | shape = 0x02 | shape&0x01
159 | spread &= 0x03
160 | nStops &= 0x3f
161 | return color.RGBA{
162 | R: nStops,
163 | G: cBase | spread<<6,
164 | B: nBase | shape<<6,
165 | A: 0x00,
166 | }
167 | }
168 |
169 | // DecodeGradient returns the gradient parameters from a non-sensical RGBA
170 | // color encoding a gradient.
171 | func DecodeGradient(c color.RGBA) (cBase, nBase, shape, spread, nStops uint8) {
172 | cBase = c.G & 0x3f
173 | nBase = c.B & 0x3f
174 | shape = (c.B >> 6) & 0x01
175 | spread = (c.G >> 6) & 0x03
176 | nStops = c.R & 0x3f
177 | return
178 | }
179 |
180 | func (c Color) Is1() bool {
181 | return c.typ == ColorTypeRGBA && Is1(c.data)
182 | }
183 |
184 | func (c Color) Encode1() (x byte, ok bool) {
185 | switch c.typ {
186 | case ColorTypeRGBA:
187 | if c.data.A != 0xff {
188 | switch c.data {
189 | case color.RGBA{0x00, 0x00, 0x00, 0x00}:
190 | return 127, true
191 | case color.RGBA{0x80, 0x80, 0x80, 0x80}:
192 | return 126, true
193 | case color.RGBA{0xc0, 0xc0, 0xc0, 0xc0}:
194 | return 125, true
195 | }
196 | } else if Is1(c.data) {
197 | r := c.data.R / 0x3f
198 | g := c.data.G / 0x3f
199 | b := c.data.B / 0x3f
200 | return 25*r + 5*g + b, true
201 | }
202 | case ColorTypePaletteIndex:
203 | return c.data.R | 0x80, true
204 | case ColorTypeCReg:
205 | return c.data.R | 0xc0, true
206 | }
207 | return 0, false
208 | }
209 |
210 | func (c Color) Is2() bool {
211 | return c.typ == ColorTypeRGBA && Is2(c.data)
212 | }
213 |
214 | func (c Color) Encode2() (x [2]byte, ok bool) {
215 | if c.Is2() {
216 | return [2]byte{
217 | (c.data.R/0x11)<<4 | (c.data.G / 0x11),
218 | (c.data.B/0x11)<<4 | (c.data.A / 0x11),
219 | }, true
220 | }
221 | return [2]byte{}, false
222 | }
223 |
224 | func (c Color) Is3() bool {
225 | return c.typ == ColorTypeRGBA && Is3(c.data)
226 | }
227 |
228 | func (c Color) Encode3Direct() (x [3]byte, ok bool) {
229 | if c.Is3() {
230 | return [3]byte{c.data.R, c.data.G, c.data.B}, true
231 | }
232 | return [3]byte{}, false
233 | }
234 |
235 | func (c Color) Encode4() (x [4]byte, ok bool) {
236 | if c.typ == ColorTypeRGBA {
237 | return [4]byte{c.data.R, c.data.G, c.data.B, c.data.A}, true
238 | }
239 | return [4]byte{}, false
240 | }
241 |
242 | func (c Color) Encode3Indirect() (x [3]byte, ok bool) {
243 | if c.typ == ColorTypeBlend {
244 | return [3]byte{c.data.R, c.data.G, c.data.B}, true
245 | }
246 | return [3]byte{}, false
247 | }
248 |
249 | func (c Color) String() string {
250 | switch c.typ {
251 | case ColorTypeRGBA:
252 | rgba := c.rgba()
253 | switch {
254 | case ValidAlphaPremulColor(rgba):
255 | return fmt.Sprintf("RGBA %02x%02x%02x%02x", rgba.R, rgba.G, rgba.B, rgba.A)
256 | case ValidGradient(rgba):
257 | gradientShapeNames := [2]string{"linear", "radial"}
258 | gradientSpreadNames := [4]string{"none", "pad", "reflect", "repeat"}
259 | return fmt.Sprintf("gradient (NSTOPS=%d, CBASE=%d, NBASE=%d, %s, %s)",
260 | rgba.R&0x3f,
261 | rgba.G&0x3f,
262 | rgba.B&0x3f,
263 | gradientShapeNames[(rgba.B>>6)&0x01],
264 | gradientSpreadNames[rgba.G>>6],
265 | )
266 | }
267 | case ColorTypePaletteIndex:
268 | return fmt.Sprintf("customPalette[%d]", c.paletteIndex())
269 | case ColorTypeCReg:
270 | return fmt.Sprintf("CREG[%d]", c.cReg())
271 | case ColorTypeBlend:
272 | // old
273 | // 40 blend 191:64 c0:c1
274 | // ff c0: CREG[63]
275 | // 80 c1: customPalette[0]
276 |
277 | // new
278 | // 40 ff 80 blend (191:64) (CREG[63]:customPalette[0])
279 | t, c0, c1 := c.blend()
280 | return fmt.Sprintf("blend (%d:%d) (%v:%v)", 0xff-t, t, DecodeColor1(c0), DecodeColor1(c1))
281 | }
282 | return "nonsensical color"
283 | }
284 |
--------------------------------------------------------------------------------
/raster/gio/example/favicon/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense OR MIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "image"
8 | "image/color"
9 | "log"
10 | "os"
11 | "time"
12 |
13 | "golang.org/x/exp/shiny/materialdesign/colornames"
14 |
15 | "gioui.org/app"
16 | "gioui.org/io/pointer"
17 | "gioui.org/io/system"
18 | "gioui.org/layout"
19 | "gioui.org/op"
20 | "gioui.org/op/clip"
21 | "gioui.org/op/paint"
22 | "gioui.org/text"
23 |
24 | "github.com/reactivego/gio"
25 | "github.com/reactivego/gio/style"
26 |
27 | "github.com/reactivego/ivg"
28 | "github.com/reactivego/ivg/encode"
29 | "github.com/reactivego/ivg/generate"
30 | raster "github.com/reactivego/ivg/raster/gio"
31 | )
32 |
33 | func main() {
34 | go FavIcon()
35 | app.Main()
36 | }
37 |
38 | func FavIcon() {
39 | window := app.NewWindow(
40 | app.Title("IVG - Favicon"),
41 | app.Size(768, 768),
42 | )
43 |
44 | grey300 := color.NRGBAModel.Convert(colornames.Grey300).(color.NRGBA)
45 | grey800 := color.NRGBAModel.Convert(colornames.Grey800).(color.NRGBA)
46 | black := color.NRGBA{A: 255}
47 |
48 | data, err := FaviconIVG()
49 | if err != nil {
50 | log.Fatal(err)
51 | }
52 |
53 | ops := new(op.Ops)
54 | shaper := text.NewShaper(style.FontFaces())
55 | backend := "Gio"
56 | for next := range window.Events() {
57 | if event, ok := next.(system.FrameEvent); ok {
58 | gtx := layout.NewContext(ops, event)
59 |
60 | // backdrop
61 | pointer.InputOp{Tag: backend, Types: pointer.Release}.Add(gtx.Ops)
62 | for _, next := range event.Queue.Events(backend) {
63 | if event, ok := next.(pointer.Event); ok {
64 | if event.Type == pointer.Release {
65 | backend = map[string]string{"Gio": "Img", "Img": "Gio"}[backend]
66 | }
67 | }
68 | }
69 | paint.Fill(gtx.Ops, grey800)
70 |
71 | layout.UniformInset(12).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
72 | size := gtx.Constraints.Max
73 | paint.FillShape(ops, grey300, clip.Rect(image.Rectangle{Max: size}).Op())
74 | start := time.Now()
75 | var widget layout.Widget
76 | switch backend {
77 | case "Gio":
78 | widget, _ = raster.Widget(data, 48, 48)
79 | case "Img":
80 | widget, _ = raster.Widget(data, 48, 48, raster.WithImageBackend())
81 | }
82 | widget(gtx)
83 | msg := fmt.Sprintf("%s (%v)", backend, time.Since(start).Round(time.Microsecond))
84 | text := gio.Text(shaper, style.H5, 0.0, 0.0, black, msg)
85 | text(gtx)
86 |
87 | return layout.Dimensions{Size: size}
88 | })
89 |
90 | event.Frame(ops)
91 | }
92 | }
93 | os.Exit(0)
94 | }
95 |
96 | func FaviconIVG() ([]byte, error) {
97 | enc := &encode.Encoder{}
98 | gen := generate.Generator{}
99 | gen.SetDestination(enc)
100 |
101 | viewbox := ivg.ViewBox{
102 | MinX: 0, MinY: 0,
103 | MaxX: +48, MaxY: +48,
104 | }
105 | gen.Reset(viewbox, ivg.DefaultPalette)
106 |
107 | colors := []color.RGBA{
108 | {0x76, 0xe1, 0xfe, 0xff}, // 0
109 | {0x38, 0x4e, 0x54, 0xff}, // 1
110 | {0xff, 0xff, 0xff, 0xff}, // 2
111 | {0x17, 0x13, 0x11, 0xff}, // 3
112 | {0x00, 0x00, 0x00, 0x54}, // 4
113 | {0xff, 0xfc, 0xfb, 0xff}, // 5
114 | {0xc3, 0x8c, 0x74, 0xff}, // 6
115 | {0x23, 0x20, 0x1f, 0xff}, // 7
116 | }
117 |
118 | type Path struct {
119 | i int
120 | d string
121 | }
122 |
123 | paths := []Path{{
124 | i: 1,
125 | d: "m16.092 1.002c-1.1057.01-2.2107.048844-3.3164.089844-2.3441.086758-4.511.88464-6.2832 2.1758a3.8208 3.5794 29.452 0 0 -.8947 -.6856 3.8208 3.5794 29.452 0 0 -5.0879 1.2383 3.8208 3.5794 29.452 0 0 1.5664 4.9961 3.8208 3.5794 29.452 0 0 .3593 .1758c-.2784.9536-.4355 1.9598-.4355 3.0078v20h28v-20c0-1.042-.152-2.0368-.418-2.9766a3.5794 3.8208 60.548 0 0 .43359 -.20703 3.5794 3.8208 60.548 0 0 1.5684 -4.9961 3.5794 3.8208 60.548 0 0 -5.0879 -1.2383 3.5794 3.8208 60.548 0 0 -.92969 .72461c-1.727-1.257-3.843-2.0521-6.1562-2.2148-1.1058-.078-2.2126-.098844-3.3184-.089844z",
126 | }, {
127 | i: 0,
128 | d: "m16 3c-4.835 0-7.9248 1.0791-9.7617 2.8906-.4777-.4599-1.2937-1.0166-1.6309-1.207-.9775-.5520-2.1879-.2576-2.7051.6582-.5171.9158-.1455 2.1063.8321 2.6582.2658.1501 1.2241.5845 1.7519.7441-.3281.9946-.4863 2.0829-.4863 3.2559v20h24c-.049-7.356 0-18 0-20 0-1.209-.166-2.3308-.516-3.3496.539-.2011 1.243-.5260 1.463-.6504.978-.5519 1.351-1.7424.834-2.6582s-1.729-1.2102-2.707-.6582c-.303.1711-.978.6356-1.463 1.0625-1.854-1.724-4.906-2.7461-9.611-2.7461z",
129 | }, {
130 | i: 1,
131 | d: "m3.0918 5.9219c-.060217.00947-.10772.020635-.14648.033203-.019384.00628-.035462.013581-.052734.021484-.00864.00395-.019118.00825-.03125.015625-.00607.00369-.011621.00781-.021484.015625-.00493.00391-.017342.015389-.017578.015625-.0002366.0002356-.025256.031048-.025391.03125a.19867 .19867 0 0 0 .26367 .28320c.0005595-.0002168.00207-.00128.00391-.00195a.19867 .19867 0 0 0 .00391 -.00195c.015939-.00517.045148-.013113.085937-.019531.081581-.012836.20657-.020179.36719.00391.1020.0152.2237.0503.3535.0976-.3277.0694-.5656.1862-.7227.3145-.1143.0933-.1881.1903-.2343.2695-.023099.0396-.039499.074216-.050781.10547-.00564.015626-.00989.029721-.013672.046875-.00189.00858-.00458.017085-.00586.03125-.0006392.00708-.0005029.014724 0 .027344.0002516.00631.00192.023197.00195.023437.0000373.0002412.0097.036937.00977.037109a.19867 .19867 0 0 0 .38477 -.039063 .19867 .19867 0 0 0 0 -.00195c.00312-.00751.00865-.015947.017578-.03125.0230-.0395.0660-.0977.1425-.1601.1530-.1250.4406-.2702.9863-.2871a.19930 .19930 0 0 0 .082031 -.019531c.12649.089206.25979.19587.39844.32422a.19867 .19867 0 1 0 .2696 -.2911c-.6099-.5646-1.1566-.7793-1.5605-.8398-.2020-.0303-.3679-.0229-.4883-.0039z",
132 | }, {
133 | i: 1,
134 | d: "m28.543 5.8203c-.12043-.018949-.28631-.026379-.48828.00391-.40394.060562-.94869.27524-1.5586.83984a.19867 .19867 0 1 0 .26953 .29102c.21354-.19768.40814-.33222.59180-.44141.51624.023399.79659.16181.94531.28320.07652.062461.11952.12063.14258.16016.0094.016037.01458.025855.01758.033203a.19867 .19867 0 0 0 .38476 .039063c.000062-.0001719.0097-.036868.0098-.037109.000037-.0002412.0017-.017125.002-.023437.000505-.012624.000639-.020258 0-.027344-.0013-.01417-.004-.022671-.0059-.03125-.0038-.017158-.008-.031248-.01367-.046875-.01128-.031254-.02768-.067825-.05078-.10742-.04624-.079195-.12003-.17424-.23437-.26758-.11891-.097066-.28260-.18832-.49609-.25781.01785-.00328.03961-.011119.05664-.013672.16062-.024082.28561-.016738.36719-.00391.03883.00611.06556.012409.08203.017578.000833.0002613.0031.0017.0039.00195a.19867 .19867 0 0 0 .271 -.2793c-.000135-.0002016-.02515-.031014-.02539-.03125-.000236-.0002356-.01265-.011717-.01758-.015625-.0099-.00782-.01737-.01194-.02344-.015625-.01213-.00737-.02066-.011673-.0293-.015625-.01727-.0079-.03336-.013247-.05273-.019531-.03877-.012568-.08822-.025682-.14844-.035156z",
135 | }, {
136 | i: 2,
137 | d: "m15.171 9.992a4.8316 4.8316 0 0 1 -4.832 4.832 4.8316 4.8316 0 0 1 -4.8311 -4.832 4.8316 4.8316 0 0 1 4.8311 -4.8316 4.8316 4.8316 0 0 1 4.832 4.8316z",
138 | }, {
139 | i: 2,
140 | d: "m25.829 9.992a4.6538 4.6538 0 0 1 -4.653 4.654 4.6538 4.6538 0 0 1 -4.654 -4.654 4.6538 4.6538 0 0 1 4.654 -4.6537 4.6538 4.6538 0 0 1 4.653 4.6537z",
141 | }, {
142 | i: 3,
143 | d: "m14.377 9.992a1.9631 1.9631 0 0 1 -1.963 1.963 1.9631 1.9631 0 0 1 -1.963 -1.963 1.9631 1.9631 0 0 1 1.963 -1.963 1.9631 1.9631 0 0 1 1.963 1.963z",
144 | }, {
145 | i: 3,
146 | d: "m25.073 9.992a1.9631 1.9631 0 0 1 -1.963 1.963 1.9631 1.9631 0 0 1 -1.963 -1.963 1.9631 1.9631 0 0 1 1.963 -1.963 1.9631 1.9631 0 0 1 1.963 1.963z",
147 | }, {
148 | i: 4,
149 | d: "m14.842 15.555h2.2156c.40215 0 .72590.3237.72590.7259v2.6545c0 .4021-.32375.7259-.72590.7259h-2.2156c-.40215 0-.72590-.3238-.72590-.7259v-2.6545c0-.4022.32375-.7259.72590-.7259z",
150 | }, {
151 | i: 5,
152 | d: "m14.842 14.863h2.2156c.40215 0 .72590.3238.72590.7259v2.6546c0 .4021-.32375.7259-.72590.7259h-2.2156c-.40215 0-.72590-.3238-.72590-.7259v-2.6546c0-.4021.32375-.7259.72590-.7259z",
153 | }, {
154 | i: 4,
155 | d: "m20 16.167c0 .838-.87123 1.2682-2.1448 1.1659-.02366 0-.04795-.6004-.25415-.5832-.50367.042-1.0959-.02-1.686-.02-.61294 0-1.2063.1826-1.6855.017-.11023-.038-.17830.5838-.26153.5816-1.2437-.033-2.0788-.3383-2.0788-1.1618 0-1.2118 1.8156-2.1941 4.0554-2.1941 2.2397 0 4.0554.9823 4.0554 2.1941z",
156 | }, {
157 | i: 6,
158 | d: "m19.977 15.338c0 .5685-.43366.8554-1.1381 1.0001-.29193.06-.63037.096-1.0037.1166-.56405.032-1.2078.031-1.8912.031-.67283 0-1.3072 0-1.8649-.029-.30627-.017-.58943-.043-.84316-.084-.81383-.1318-1.325-.417-1.325-1.0344 0-1.1601 1.8056-2.1006 4.033-2.1006s4.033.9405 4.033 2.1006z",
159 | }, {
160 | i: 7,
161 | d: "m18.025 13.488a2.0802 1.3437 0 0 1 -2.0802 1.3437 2.0802 1.3437 0 0 1 -2.0802 -1.3437 2.0802 1.3437 0 0 1 2.0802 -1.3437 2.0802 1.3437 0 0 1 2.0802 1.3437z",
162 | }}
163 |
164 | // Set up a base color for theming the favicon, gopher blue by default.
165 | pal := ivg.DefaultPalette
166 | pal[0] = colors[0] // color.RGBA{0x76, 0xe1, 0xfe, 0xff}
167 |
168 | gen.Reset(ivg.DefaultViewBox, pal)
169 |
170 | // The favicon graphic also uses a dark version of that base color.
171 | dark := color.RGBA{0x23, 0x1d, 0x1b, 0xff}
172 |
173 | // Blend is 75% dark (CReg[63]) and 25% the base color (pal[0]).
174 | // t = 64 / 256 = 0.25
175 | // c0 = 255 -> CREG color at index (255 - 192) = 63
176 | // c1 = 128 -> Custom Palette color at index (128 - 128) = 0
177 | blend := ivg.BlendColor(0x40, 0xff, 0x80)
178 |
179 | // First, set CReg[63] to dark, then set CReg[63] to the blend of that dark
180 | // color with pal[0].
181 | gen.SetCReg(1, false, ivg.RGBAColor(dark))
182 | gen.SetCReg(1, false, blend)
183 |
184 | // Set aside the remaining, non-themable colors.
185 | remainingColors := colors[2:]
186 |
187 | seenFCI2 := false
188 | for _, path := range paths {
189 | adj := uint8(path.i)
190 | if adj >= 2 {
191 | if !seenFCI2 {
192 | seenFCI2 = true
193 | for i, c := range remainingColors {
194 | gen.SetCReg(uint8(i), false, ivg.RGBAColor(c))
195 | }
196 | }
197 | adj -= 2
198 | }
199 | fromVB := generate.Concat(generate.Translate(0, 0), generate.Scale(1.0/32, 1.0/32))
200 | toVB := generate.Concat(generate.Scale(64, 64), generate.Translate(-32, -32))
201 | gen.SetTransform(fromVB, toVB)
202 | gen.SetPathData(path.d, adj)
203 | }
204 |
205 | return enc.Bytes()
206 | }
207 |
--------------------------------------------------------------------------------
/generate/generate.go:
--------------------------------------------------------------------------------
1 | package generate
2 |
3 | import (
4 | "fmt"
5 | "image/color"
6 | "math"
7 | "strconv"
8 |
9 | "github.com/reactivego/ivg"
10 | )
11 |
12 | type Error string
13 |
14 | func (e Error) Error() string { return string(e) }
15 |
16 | const (
17 | CSELUsedAsBothGradientAndStop = Error("ivg: CSEL used as both gradient and stop")
18 | TooManyGradientStops = Error("ivg: too many gradient stops")
19 | )
20 |
21 | func UnrecognizedPathDataVerb(verb byte) Error {
22 | return Error(fmt.Sprintf("ivg: unrecognized path data verb (%c)", verb))
23 | }
24 |
25 | // Aff3 is a 3x3 affine transformation matrix in row major order, where the
26 | // bottom row is implicitly [0 0 1].
27 | //
28 | // m[3*r + c] is the element in the r'th row and c'th column.
29 | type Aff3 [6]float32
30 |
31 | func Translate(x, y float32) Aff3 {
32 | return Aff3{
33 | 1, 0, x,
34 | 0, 1, y,
35 | }
36 | }
37 |
38 | func Scale(v ...float32) Aff3 {
39 | switch len(v) {
40 | case 0:
41 | return Aff3{1, 0, 0, 0, 1, 0}
42 | case 1:
43 | return Aff3{v[0], 0, 0, 0, v[0], 0}
44 | default:
45 | return Aff3{v[0], 0, 0, 0, v[1], 0}
46 | }
47 | }
48 |
49 | func Concat(affs ...Aff3) Aff3 {
50 | switch len(affs) {
51 | case 0:
52 | return Aff3{1, 0, 0, 0, 1, 0}
53 | case 1:
54 | return affs[0]
55 | default:
56 | a := Aff3{1, 0, 0, 0, 1, 0}
57 | for _, b := range affs {
58 | a = Aff3{
59 | a[0]*b[0] + a[3]*b[1], a[1]*b[0] + a[4]*b[1], a[2]*b[0] + a[5]*b[1] + b[2],
60 | a[0]*b[3] + a[3]*b[4], a[1]*b[3] + a[4]*b[4], a[2]*b[3] + a[5]*b[4] + b[5],
61 | }
62 | }
63 | return a
64 | }
65 | }
66 |
67 | func MulAff3(x, y float32, a Aff3) (X, Y float32) {
68 | return x*a[0] + y*a[1] + a[2], x*a[3] + y*a[4] + a[5]
69 | }
70 |
71 | // GradientShape is the gradient shape.
72 | type GradientShape uint8
73 |
74 | const (
75 | GradientShapeLinear GradientShape = iota
76 | GradientShapeRadial
77 | )
78 |
79 | // GradientSpread is how to spread a gradient past its nominal bounds (from
80 | // offset being 0.0 to offset being 1.0).
81 | type GradientSpread uint8
82 |
83 | const (
84 | GradientSpreadNone GradientSpread = iota
85 | GradientSpreadPad
86 | GradientSpreadReflect
87 | GradientSpreadRepeat
88 | )
89 |
90 | // GradientStop is a color/offset gradient stop.
91 | type GradientStop struct {
92 | Offset float32
93 | Color color.Color
94 | }
95 |
96 | type Generator struct {
97 | ivg.Destination
98 | transforms []Aff3
99 | }
100 |
101 | func (g *Generator) SetDestination(d ivg.Destination) {
102 | g.Destination = d
103 | }
104 |
105 | // SetLinearGradient is like SetGradient with shape=ShapeLinear except that the
106 | // transformation matrix is implicitly defined by two boundary points (x1, y1)
107 | // and (x2, y2).
108 | func (g *Generator) SetLinearGradient(x1, y1, x2, y2 float32, spread GradientSpread, stops []GradientStop) error {
109 | // See the package documentation's appendix for a derivation of the
110 | // transformation matrix.
111 | dx, dy := x2-x1, y2-y1
112 | d := dx*dx + dy*dy
113 | ma := dx / d
114 | mb := dy / d
115 | vbx2grad := Aff3{
116 | ma, mb, -ma*x1 - mb*y1,
117 | 0, 0, 0,
118 | }
119 | return g.SetGradient(GradientShapeLinear, spread, stops, vbx2grad)
120 | }
121 |
122 | // SetCircularGradient is like SetGradient with radial=true except that the
123 | // transformation matrix is implicitly defined by a center (cx, cy) and a
124 | // radius vector (rx, ry) such that (cx+rx, cy+ry) is on the circle.
125 | func (g *Generator) SetCircularGradient(cx, cy, rx, ry float32, spread GradientSpread, stops []GradientStop) error {
126 | // See the package documentation's appendix for a derivation of the
127 | // transformation matrix.
128 | invR := float32(1 / math.Sqrt(float64(rx*rx+ry*ry)))
129 | vbx2grad := Aff3{
130 | invR, 0, -cx * invR,
131 | 0, invR, -cy * invR,
132 | }
133 | return g.SetGradient(GradientShapeRadial, spread, stops, vbx2grad)
134 | }
135 |
136 | // SetEllipticalGradient is like SetGradient with radial=true except that the
137 | // transformation matrix is implicitly defined by a center (cx, cy) and two
138 | // axis vectors (rx, ry) and (sx, sy) such that (cx+rx, cy+ry) and (cx+sx,
139 | // cy+sy) are on the ellipse.
140 | func (d *Generator) SetEllipticalGradient(cx, cy, rx, ry, sx, sy float32, spread GradientSpread, stops []GradientStop) error {
141 | // Explicitly disable FMA in the floating-point calculations below
142 | // to get consistent results on all platforms, and in turn produce
143 | // a byte-identical encoding.
144 | // See https://golang.org/ref/spec#Floating_point_operators and issue 43219.
145 |
146 | // See the package documentation's appendix for a derivation of the
147 | // transformation matrix.
148 | invRSSR := 1 / (float32(rx*sy) - float32(sx*ry))
149 |
150 | ma := +sy * invRSSR
151 | mb := -sx * invRSSR
152 | mc := -float32(ma*cx) - float32(mb*cy)
153 | md := -ry * invRSSR
154 | me := +rx * invRSSR
155 | mf := -float32(md*cx) - float32(me*cy)
156 |
157 | vbx2grad := Aff3{
158 | ma, mb, mc,
159 | md, me, mf,
160 | }
161 | return d.SetGradient(GradientShapeRadial, spread, stops, vbx2grad)
162 | }
163 |
164 | // SetGradient sets CREG[CSEL] to encode the gradient whose colors defined by
165 | // spread and stops. Its geometry is either linear or radial, depending on the
166 | // radial argument, and the given affine transformation matrix maps from
167 | // graphic coordinate space defined by the metadata's viewBox (e.g. from (-32,
168 | // -32) to (+32, +32)) to gradient coordinate space. Gradient coordinate space
169 | // is where a linear gradient ranges from x=0 to x=1, and a radial gradient has
170 | // center (0, 0) and radius 1.
171 | //
172 | // The colors of the n stops are encoded at CREG[cBase+0], CREG[cBase+1], ...,
173 | // CREG[cBase+n-1]. Similarly, the offsets of the n stops are encoded at
174 | // NREG[nBase+0], NREG[nBase+1], ..., NREG[nBase+n-1]. Additional parameters
175 | // are stored at NREG[nBase-4], NREG[nBase-3], NREG[nBase-2] and NREG[nBase-1].
176 | //
177 | // The CSEL and NSEL selector registers maintain the same values after the
178 | // method returns as they had when the method was called.
179 | //
180 | // See the package documentation for more details on the gradient encoding
181 | // format and the derivation of common transformation matrices.
182 | func (d *Generator) SetGradient(shape GradientShape, spread GradientSpread, stops []GradientStop, transform Aff3) error {
183 | cBase, nBase := uint8(10), uint8(10)
184 |
185 | nStops := uint8(len(stops))
186 | if nStops > uint8(64-len(transform)) {
187 | return TooManyGradientStops
188 | }
189 | if x, y := d.CSel(), d.CSel()+64; (cBase <= x && x < cBase+nStops) || (cBase <= y && y < cBase+nStops) {
190 | return CSELUsedAsBothGradientAndStop
191 | }
192 |
193 | oldCSel := d.CSel()
194 | oldNSel := d.NSel()
195 | d.SetCReg(0, false, ivg.RGBAColor(ivg.EncodeGradient(cBase, nBase, uint8(shape), uint8(spread), nStops)))
196 | d.SetCSel(cBase)
197 | d.SetNSel(nBase)
198 | for i, v := range transform {
199 | d.SetNReg(uint8(len(transform)-i), false, v)
200 | }
201 | for _, s := range stops {
202 | r, g, b, a := s.Color.RGBA()
203 | d.SetCReg(0, true, ivg.RGBAColor(color.RGBA{
204 | R: uint8(r >> 8),
205 | G: uint8(g >> 8),
206 | B: uint8(b >> 8),
207 | A: uint8(a >> 8),
208 | }))
209 | d.SetNReg(0, true, s.Offset)
210 | }
211 | d.SetCSel(oldCSel)
212 | d.SetNSel(oldNSel)
213 | return nil
214 | }
215 |
216 | func (e *Generator) SetTransform(transforms ...Aff3) {
217 | e.transforms = []Aff3{Concat(transforms...)}
218 | }
219 |
220 | func (e *Generator) SetPathData(d string, adj uint8) error {
221 | var args [7]float32
222 | prevN, prevVerb := 0, byte(0)
223 | for start := true; d != "z"; start = false {
224 | // The verb at the start of the path data in d must be either 'M' or 'm'
225 | // A terminating 'Z' or 'z' is optional and only makes a difference for stroking.
226 | n, verb, implicit := 0, d[0], false
227 | switch verb {
228 | case 'H', 'h', 'V', 'v':
229 | n = 1
230 | case 'L', 'l', 'M', 'm', 'T', 't':
231 | n = 2
232 | case 'Q', 'q', 'S', 's':
233 | n = 4
234 | case 'C', 'c':
235 | n = 6
236 | case 'A', 'a':
237 | n = 7
238 | case 'Z', 'z':
239 | n = 0
240 | default:
241 | if prevVerb == '\x00' {
242 | return UnrecognizedPathDataVerb(verb)
243 | }
244 | n, verb, implicit = prevN, prevVerb, true
245 | }
246 | prevN, prevVerb = n, verb
247 | if prevVerb == 'M' {
248 | prevVerb = 'L'
249 | } else if prevVerb == 'm' {
250 | prevVerb = 'l'
251 | }
252 | if start {
253 | verb = '@'
254 | }
255 | if !implicit {
256 | d = d[1:]
257 | }
258 |
259 | if dnext, err := scan(&args, d, n); err != nil {
260 | return err
261 | } else {
262 | d = dnext
263 | }
264 | normalize(&args, n, verb, e.transforms...)
265 |
266 | switch verb {
267 | case 'H':
268 | e.AbsHLineTo(args[0])
269 | case 'h':
270 | e.RelHLineTo(args[0])
271 | case 'V':
272 | e.AbsVLineTo(args[0])
273 | case 'v':
274 | e.RelVLineTo(args[0])
275 | case 'L':
276 | e.AbsLineTo(args[0], args[1])
277 | case 'l':
278 | e.RelLineTo(args[0], args[1])
279 | case '@':
280 | e.StartPath(adj, args[0], args[1])
281 | case 'M':
282 | e.ClosePathAbsMoveTo(args[0], args[1])
283 | case 'm':
284 | e.ClosePathRelMoveTo(args[0], args[1])
285 | case 'T':
286 | e.AbsSmoothQuadTo(args[0], args[1])
287 | case 't':
288 | e.RelSmoothQuadTo(args[0], args[1])
289 | case 'Q':
290 | e.AbsQuadTo(args[0], args[1], args[2], args[3])
291 | case 'q':
292 | e.RelQuadTo(args[0], args[1], args[2], args[3])
293 | case 'S':
294 | e.AbsSmoothCubeTo(args[0], args[1], args[2], args[3])
295 | case 's':
296 | e.RelSmoothCubeTo(args[0], args[1], args[2], args[3])
297 | case 'C':
298 | e.AbsCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
299 | case 'c':
300 | e.RelCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
301 | case 'A':
302 | e.AbsArcTo(args[0], args[1], args[2]/360, args[3] != 0, args[4] != 0, args[5], args[6])
303 | case 'a':
304 | e.RelArcTo(args[0], args[1], args[2]/360, args[3] != 0, args[4] != 0, args[5], args[6])
305 | case 'Z', 'z':
306 | // No-op.
307 | default:
308 | return UnrecognizedPathDataVerb(verb)
309 | }
310 | }
311 | e.ClosePathEndPath()
312 | return nil
313 | }
314 |
315 | func scan(args *[7]float32, d string, n int) (string, error) {
316 | for i := 0; i < n; i++ {
317 | nDots := 0
318 | if d[0] == '.' {
319 | nDots = 1
320 | }
321 | j := 1 // skip over a '+' or '-' or any other character for that matter
322 | for ; ; j++ {
323 | switch d[j] {
324 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
325 | continue
326 | case '.':
327 | nDots++
328 | if nDots == 1 {
329 | continue
330 | }
331 | }
332 | break
333 | }
334 | f, err := strconv.ParseFloat(d[:j], 64)
335 | if err != nil {
336 | return d, err
337 | }
338 | args[i] = float32(f)
339 | for ; d[j] == ' ' || d[j] == ','; j++ {
340 | }
341 | d = d[j:]
342 | }
343 | return d, nil
344 | }
345 |
346 | func normalize(args *[7]float32, n int, verb byte, transforms ...Aff3) {
347 | if len(transforms) > 0 {
348 | // The original SVG is 32x32 units, with the top left being (0, 0).
349 | // Normalize to 64x64 units, with the center being (0, 0).
350 | if true {
351 | transform := Concat(transforms...)
352 | scale := Aff3{transform[0], 0, 0, 0, transform[4], 0}
353 | if 'a' <= verb && verb <= 'z' {
354 | transform = scale
355 | }
356 | switch n {
357 | case 7:
358 | args[0], args[1] = MulAff3(args[0], args[1], scale)
359 | args[5], args[6] = MulAff3(args[5], args[6], transform)
360 | case 6:
361 | args[4], args[5] = MulAff3(args[4], args[5], transform)
362 | fallthrough
363 | case 4:
364 | args[2], args[3] = MulAff3(args[2], args[3], transform)
365 | fallthrough
366 | case 2:
367 | args[0], args[1] = MulAff3(args[0], args[1], transform)
368 | case 1:
369 | if verb == 'H' || verb == 'h' {
370 | args[0], _ = MulAff3(args[0], 0, transform)
371 | } else if verb == 'V' || verb == 'v' {
372 | _, args[0] = MulAff3(0, args[0], transform)
373 | }
374 | }
375 | } else {
376 | if verb == 'A' {
377 | args[0] = 2 * args[0]
378 | args[1] = 2 * args[1]
379 | args[5] = 2*args[5] - 32
380 | args[6] = 2*args[6] - 32
381 | } else if verb == 'a' {
382 | args[0] = 2 * args[0]
383 | args[1] = 2 * args[1]
384 | args[5] = 2 * args[5]
385 | args[6] = 2 * args[6]
386 | } else if '@' <= verb && verb <= 'Z' {
387 | for i := range args {
388 | args[i] = 2*args[i] - 32
389 | }
390 | } else {
391 | for i := range args {
392 | args[i] = 2 * args[i]
393 | }
394 | }
395 | }
396 | // fmt.Println(args[:n])
397 | }
398 | }
399 |
--------------------------------------------------------------------------------