├── .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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 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 | [![Go Reference](https://pkg.go.dev/badge/github.com/reactivego/ivg.svg)](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 | --------------------------------------------------------------------------------