~eliasnaur/gio

3bb141c499f829afc892763eeaecc70ed1e4ed0d — Elias Naur 2 years ago 69f982e raster
wip

Signed-off-by: Elias Naur <mail@eliasnaur.com>
1 files changed, 219 insertions(+), 0 deletions(-)

A raster/raster.go
A raster/raster.go => raster/raster.go +219 -0
@@ 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],
	}
}