Compare commits

...

1 Commits

Author SHA1 Message Date
Elias Naur 3bb141c499 wip
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-04-06 13:22:45 +02:00
+219
View File
@@ -0,0 +1,219 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package raster implements a rasterizer for Gio optimized for embedded
platforms.
Note: the implementation is incomplete.
*/
package raster
import (
"image"
"image/color"
"image/draw"
"gioui.org/f32"
"gioui.org/internal/ops"
"gioui.org/internal/scene"
"gioui.org/layout"
"gioui.org/op"
"golang.org/x/image/vector"
)
type Rasterizer struct {
reader ops.Reader
scratch struct {
transforms []f32.Affine2D
states []f32.Affine2D
clips []clipState
}
}
type clipState struct {
path []byte
trans f32.Affine2D
bounds image.Rectangle
paths int
}
func (r *Rasterizer) Frame(frame *op.Ops, frameBuf *image.RGBA) {
if frame == nil {
return
}
d := &r.reader
d.Reset(&frame.Internal)
stack := r.scratch.transforms[:0]
states := r.scratch.states[:0]
clips := r.scratch.clips
defer func() {
r.scratch.transforms = stack
r.scratch.states = states
r.scratch.clips = clips
}()
type decodeState struct {
t f32.Affine2D
material image.Image
}
var pathData struct {
data []byte
}
zeroState := decodeState{
material: image.NewUniform(&color.NRGBA{}),
}
state := zeroState
for encOp, ok := d.Decode(); ok; encOp, ok = d.Decode() {
switch ops.OpType(encOp.Data[0]) {
case ops.TypeTransform:
dop, push := ops.DecodeTransform(encOp.Data)
if push {
stack = append(stack, state.t)
}
state.t = state.t.Mul(dop)
case ops.TypePopTransform:
state.t = stack[len(stack)-1]
stack = stack[:len(stack)-1]
case ops.TypeStroke:
// TODO.
case ops.TypePath:
op, ok := d.Decode()
if !ok {
panic("unexpected end of path operation")
}
pathData.data = op.Data[ops.TypeAuxLen:]
case ops.TypeClip:
var op ops.ClipOp
op.Decode(encOp.Data)
bounds := transformBounds(state.t, op.Bounds)
paths := 0
if pathData.data != nil {
paths = 1
}
if len(clips) > 0 {
parent := clips[len(clips)-1]
bounds = bounds.Intersect(parent.bounds)
paths += parent.paths
}
clips = append(clips, clipState{
path: pathData.data,
trans: state.t,
bounds: bounds,
paths: paths,
})
pathData.data = nil
case ops.TypePopClip:
clips = clips[:len(clips)-1]
case ops.TypeColor:
col := decodeColorOp(encOp.Data)
state.material = image.NewUniform(col)
case ops.TypeLinearGradient:
// TODO.
case ops.TypeImage:
state.material = encOp.Refs[0].(*image.RGBA)
case ops.TypePaint:
bounds := frameBuf.Bounds()
paths := 0
if len(clips) > 0 {
parent := clips[len(clips)-1]
bounds = bounds.Intersect(parent.bounds)
paths = parent.paths
}
if bounds.Empty() {
break
}
switch paths {
case 0:
draw.Draw(frameBuf, bounds, state.material, state.material.Bounds().Min, draw.Over)
case 1:
vr := vector.NewRasterizer(bounds.Dx(), bounds.Dy())
vr.DrawOp = draw.Over
for i := len(clips) - 1; i >= 0; i-- {
c := clips[i]
if c.path == nil {
continue
}
off := layout.FPt(bounds.Min.Mul(-1))
decodePath(vr, c.path, c.trans.Offset(off))
}
vr.Draw(frameBuf, bounds, state.material, state.material.Bounds().Min)
}
case ops.TypeSave:
id := ops.DecodeSave(encOp.Data)
if extra := id - len(states) + 1; extra > 0 {
states = append(states, make([]f32.Affine2D, extra)...)
}
states[id] = state.t
case ops.TypeLoad:
id := ops.DecodeLoad(encOp.Data)
state = zeroState
state.t = states[id]
}
}
}
func decodePath(r *vector.Rasterizer, pathData []byte, t f32.Affine2D) {
for len(pathData) >= scene.CommandSize+4 {
cmd := ops.DecodeCommand(pathData[4:])
var pen f32.Point
switch cmd.Op() {
case scene.OpLine:
from, to := scene.DecodeLine(cmd)
from, to = t.Transform(from), t.Transform(to)
if from != pen {
r.MoveTo(from.X, from.Y)
}
r.LineTo(to.X, to.Y)
pen = to
case scene.OpGap:
from, to := scene.DecodeGap(cmd)
from, to = t.Transform(from), t.Transform(to)
if from != pen {
r.MoveTo(from.X, from.Y)
}
r.LineTo(to.X, to.Y)
pen = to
case scene.OpQuad:
from, ctrl, to := scene.DecodeQuad(cmd)
from, ctrl, to = t.Transform(from), t.Transform(ctrl), t.Transform(to)
if from != pen {
r.MoveTo(from.X, from.Y)
}
r.QuadTo(ctrl.X, ctrl.Y, to.X, to.Y)
pen = to
case scene.OpCubic:
from, ctrl0, ctrl1, to := scene.DecodeCubic(cmd)
from, ctrl0, ctrl1, to = t.Transform(from), t.Transform(ctrl0), t.Transform(ctrl1), t.Transform(to)
if from != pen {
r.MoveTo(from.X, from.Y)
}
r.CubeTo(ctrl0.X, ctrl0.Y, ctrl1.X, ctrl1.Y, to.X, to.Y)
pen = to
default:
panic("unsupported scene command")
}
pathData = pathData[scene.CommandSize+4:]
}
}
func transformBounds(t f32.Affine2D, bounds image.Rectangle) image.Rectangle {
b0 := f32.Rectangle{
Min: t.Transform(layout.FPt(bounds.Min)),
Max: t.Transform(layout.FPt(bounds.Max)),
}.Canon()
b1 := f32.Rectangle{
Min: t.Transform(layout.FPt(image.Pt(bounds.Max.X, bounds.Min.Y))),
Max: t.Transform(layout.FPt(image.Pt(bounds.Min.X, bounds.Max.Y))),
}.Canon()
return b0.Union(b1).Round()
}
func decodeColorOp(data []byte) color.NRGBA {
return color.NRGBA{
R: data[1],
G: data[2],
B: data[3],
A: data[4],
}
}