import "github.com/reactivego/ivg"
Gio (immediate mode GUI in Go) uses IconVG (binary format for simple vector graphic icons).
This code is a refactoring of the IconVG code. It removes the need for rendering to an intermediate RGBA image. Instead it uses Gio clip.Path functionality.
The name of the IconVG package has been changed to ivg so we don't confuse people about what's what.
The most important changes w.r.t. the original IconVG code are:
- Separate code into packages with a clear purpose and responsibility for better cohesion and less coupling.
- Split icon encoding into
encodeandgeneratepackage. - SVG gradient and path support is now part of
generatepackage. - Rename
RasterizertoRendererand place it in therenderpackage. - Move
Destinationinterface into rootivgpackage. - Make both
EncoderandRendererimplementDestination. - Make both
DecoderandGeneratoruse onlyDestinationinterface. Generatorcan now directly render by plugging in aRenderer(very useful).Encodercan be plugged directly into aDecoder(useful for testing).- Abstract away rasterizing into a seperate package
raster- Declare interface
Rasterizer. - Declare interface
GradientConfigimplemented byRenderer.
- Declare interface
- Create a rasterizer using "golang.org/x/image/vector" in directory
raster/vec - Create a rasterizer using "gioui.org/op/clip" in directory
raster/gio- Special case for
GradientConfig, selectively sample gradient only inside path bounds.
- Special case for
- Create examples in the
examplefolder.
Simplest example of rendering an icon from an .ivg file stored in a slice of bytes.
package main
import (
"image/color"
"log"
"os"
"gioui.org/app"
"gioui.org/f32"
"gioui.org/io/system"
"gioui.org/op"
"gioui.org/unit"
"github.com/reactivego/ivg/icon"
)
func main() {
go PlayArrow()
app.Main()
}
func PlayArrow() {
window := app.NewWindow(
app.Title("IVG - PlayArrow"),
app.Size(unit.Dp(768), unit.Dp(768)),
)
playArrow, err := icon.New([]byte{
// AVPlayArrow data taken from "golang.org/x/exp/shiny/materialdesign/icons"
0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0,
0xb0, 0xc0, 0x70, 0x64, 0xe9, 0xb8, 0x20, 0xac, 0x64, 0xe1,
})
if err != nil {
log.Fatal(err)
}
ops := new(op.Ops)
for event := range window.Events() {
if frame, ok := event.(system.FrameEvent); ok {
ops.Reset()
contentRect := f32.Rect(0, 0, float32(frame.Size.X), float32(frame.Size.Y))
viewRect := playArrow.AspectMeet(contentRect, 0.5, 0.5)
blue := color.RGBA{0x21, 0x96, 0xf3, 0xff}
callOp, err := playArrow.Rasterize(viewRect, blue)
if err != nil {
log.Fatal(err)
}
callOp.Add(ops)
frame.Frame(ops)
}
}
os.Exit(0)
}Generating the .ivg bytes for an icon on the fly and then rendering it. Rendering operations are cached in an icon cache. The function ActionInfoData() is called once to programatically generate an .ivg byte slice using the following pipeline:
Generator -> Encoder
The resulting bytes are stored for later rendering during a system.FrameEvent. When the icon needs to be rendered, call the icon.Cache Render method with the .ivg data bytes and additional arguments. The icon cache uses the following pipeline to render the icon.
Decoder -> Renderer -> Rasterizer
The cache stores the resulting op.CallOp keyed on the icon data and parameters used for rendering.
package main
import (
"image/color"
"log"
"os"
"gioui.org/app"
"gioui.org/f32"
"gioui.org/io/system"
"gioui.org/op"
"gioui.org/unit"
"github.com/reactivego/ivg"
"github.com/reactivego/ivg/encode"
"github.com/reactivego/ivg/generate"
"github.com/reactivego/ivg/icon"
)
func main() {
go ActionInfo()
app.Main()
}
func ActionInfo() {
window := app.NewWindow(
app.Title("IVG - ActionInfo"),
app.Size(unit.Dp(768), unit.Dp(768)),
)
// generate ivg data bytes on the fly for the ActionInfo icon.
enc := &encode.Encoder{}
gen := &generate.Generator{Destination: enc}
gen.Reset(ivg.ViewBox{0, 0, 48, 48}, &ivg.DefaultPalette)
gen.SetPathData("M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 "+
"20-20S35.05 4 24 4zm2 30h-4V22h4v12zm0-16h-4v-4h4v4z", 0, false)
actionInfoData, err := enc.Bytes()
if err != nil {
log.Fatal(err)
}
actionInfo, err := icon.New(actionInfoData)
if err != nil {
log.Fatal(err)
}
cache := icon.NewCache()
ops := new(op.Ops)
for next := range window.Events() {
if frame, ok := next.(system.FrameEvent); ok {
ops.Reset()
contentRect := f32.Rect(0, 0, float32(frame.Size.X), float32(frame.Size.Y))
viewRect := actionInfo.AspectMeet(contentRect, ivg.Mid, ivg.Mid)
blue := color.RGBA{0x21, 0x96, 0xf3, 0xff}
if callOp, err := cache.Rasterize(actionInfo, viewRect, blue); err == nil {
callOp.Add(ops)
} else {
log.Fatal(err)
}
frame.Frame(ops)
}
}
os.Exit(0)
}| Gio | Vec |
The Icons example program takes icons from the package "golang.org/x/exp/shiny/materialdesign/icons" and renders them. These icons are just a few layers filled with a single color. The example uses function Render from package "github.com/reactivego/ivg/icon" for rendering. Render uses the following pipeline:
Decoder -> Renderer -> Rasterizer
By clicking the window you can switch between rendering using the Gio or Vec ("golang.org/x/image/vector") rasterizer.
The rendering using Gio is extremely quick as it only generates a few clipping & blending operations that are put in an operator queue. The actual rendering by Gio takes place on the GPU. For the Vec rasterizer all the pixels of the image need to be pre-generated, which takes relatively a long time.
package main
import (
"fmt"
"image"
"log"
"os"
"time"
"golang.org/x/exp/shiny/materialdesign/colornames"
"gioui.org/app"
"gioui.org/f32"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"github.com/reactivego/ivg/icon"
)
func main() {
go Icons()
app.Main()
}
func Icons() {
window := app.NewWindow(
app.Title("IVG - Icons"),
app.Size(unit.Dp(768), unit.Dp(768)),
)
var rasterizer icon.Rasterizer = icon.GioRasterizer
ops := new(op.Ops)
backdrop := new(int)
index := 0
for next := range window.Events() {
if frame, ok := next.(system.FrameEvent); ok {
ops.Reset()
// clicking on backdrop will switch active renderer
pointer.InputOp{Tag: backdrop, Types: pointer.Release}.Add(ops)
for _, next := range frame.Queue.Events(backdrop) {
if event, ok := next.(pointer.Event); ok {
if event.Type == pointer.Release {
switch rasterizer {
case icon.GioRasterizer:
rasterizer = icon.VecRasterizer
case icon.VecRasterizer:
rasterizer = icon.GioRasterizer
}
}
}
}
// fill the whole backdrop rectangle
paint.ColorOp{Color: colornames.Grey800}.Add(ops)
paint.PaintOp{}.Add(ops)
// device independent content rect calculation
margin := unit.Dp(12)
minX := unit.Add(frame.Metric, margin, frame.Insets.Left)
minY := unit.Add(frame.Metric, margin, frame.Insets.Top)
maxX := unit.Add(frame.Metric, unit.Px(float32(frame.Size.X)), frame.Insets.Right.Scale(-1), margin.Scale(-1))
maxY := unit.Add(frame.Metric, unit.Px(float32(frame.Size.Y)), frame.Insets.Bottom.Scale(-1), margin.Scale(-1))
contentRect := f32.Rect(
float32(frame.Metric.Px(minX)), float32(frame.Metric.Px(minY)),
float32(frame.Metric.Px(maxX)), float32(frame.Metric.Px(maxY)))
// fill content rect
paint.ColorOp{Color: colornames.Grey300}.Add(ops)
state := op.Save(ops)
op.Offset(contentRect.Min).Add(ops)
clip.Rect(image.Rect(0, 0, int(contentRect.Dx()), int(contentRect.Dy()))).Add(ops)
paint.PaintOp{}.Add(ops)
state.Load()
// select next icon and paint
n := uint(len(IconCollection))
ico := IconCollection[(uint(index)+n)%n]
index++
start := time.Now()
icon, err := icon.New(ico.data)
if err != nil {
log.Fatal(err)
}
viewRect := icon.AspectMeet(contentRect, 0.5, 0.5)
if callOp, err := rasterizer.Rasterize(icon, viewRect, colornames.LightBlue600); err == nil {
callOp.Add(ops)
} else {
log.Fatal(err)
}
msg := fmt.Sprintf("%s (%v)", rasterizer.Name(), time.Since(start).Round(time.Microsecond))
PrintText(msg, contentRect.Min, 0.0, 0.0, contentRect.Dx(), H5, ops)
at := time.Now().Add(500 * time.Millisecond)
op.InvalidateOp{At: at}.Add(ops)
frame.Frame(ops)
}
}
os.Exit(0)
}| Gio | Vec |
Favicon programatically renders a vector image of a Gopher using multiple layers with translucency. It uses the following pipeline:
Generator -> Renderer -> Rasterizer
This example hooks up the generator directly to the renderer and forgoes the Encoder -> Decoder stages. The rendering using Gio is extremely quick as it only generates a few clipping & blending operations that are put in an operator queue. The actual rendering by Gio takes place on the GPU. For the Vec ("golang.org/x/image/vector") rasterizer all the pixels of the image need to be pre-generated, which takes relatively a long time.
The resulting images are a little bit different. Look under the nose of the Gopher. Gio produces lighter results than Vec. The reason for this is that Vec performs blending in sRGB space whereas Gio performs the blending in Linear space.
| Gio | Vec |
Cowbell programatically renders a vector image of a Cowbell using multiple layers with gradients and translucency. It uses the following pipeline:
Generator -> Renderer -> Rasterizer
The rendering takes relatively long because the gradients need to be pre-generated even for the Gio renderer.
| Gio | Vec |
Gradients uses the following pipeline to programatically render a vector image consisting of multiple different gradients:
Generator -> Renderer -> Rasterizer
The rendering takes relatively long because the gradients need to be pre-generated even for the Gio renderer. But even considering that, Gio is approximately 8 times faster than Vec.
This code is base on golang.org/x/exp/shiny/iconvg.
Everything under the raster folder is Unlicense OR MIT (whichever you prefer). See file raster/LICENSE.
All the other code is is governed by a BSD-style license that can be found in the LICENSE file.