shiny/iconvg: implement elliptical gradients.
Change-Id: I8db1977187c1137823b88d4616f6dc3d6ee70968
Reviewed-on: https://go-review.googlesource.com/31542
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/shiny/iconvg/decode_test.go b/shiny/iconvg/decode_test.go
index e23ccf1..520abb7 100644
--- a/shiny/iconvg/decode_test.go
+++ b/shiny/iconvg/decode_test.go
@@ -134,6 +134,8 @@
{"testdata/action-info.hires", ""},
{"testdata/arcs", ""},
{"testdata/blank", ""},
+ {"testdata/cowbell", ""},
+ {"testdata/elliptical", ""},
{"testdata/favicon", ""},
{"testdata/gradient", ""},
{"testdata/lod-polygon", ";64"},
@@ -165,7 +167,7 @@
continue
}
if !bytes.Equal(got, want) {
- t.Errorf("got:\n%s\nwant:\n%s", got, want)
+ t.Errorf("%s: got:\n%s\nwant:\n%s", tc.filename, got, want)
diffLines(t, string(got), string(want))
}
}
diff --git a/shiny/iconvg/doc.go b/shiny/iconvg/doc.go
index 10bcb84..bfa7310 100644
--- a/shiny/iconvg/doc.go
+++ b/shiny/iconvg/doc.go
@@ -98,8 +98,11 @@
The high 2 bits of the green value is how to spread the gradient past its
nominal bounds (from offset being 0.0 to offset being 1.0). The high two bits
being 0, 1, 2 or 3 mean none, pad, reflect and repeat respectively. None means
-extend with transparent black, the others are per SVG's
-https://www.w3.org/TR/SVG/pservers.html#LinearGradientElementSpreadMethodAttribute
+that offsets outside of the [0.0, 1.0] range map to transparent black. Pad
+means that offsets below 0.0 and above 1.0 map to the colors that 0.0 and 1.0
+would map to. Reflect means that the offset mapping is reflected start-to-end,
+end-to-start, start-to-end, etc. Repeat means that the offset mapping is
+repeated start-to-end, start-to-end, start-to-end, etc.
The low 6 bits of the blue value is the number register base, NBASE.
@@ -108,15 +111,23 @@
The gradient has NSTOPS color/offset stops. The first stop has color
CREG[CBASE+0] and offset NREG[NBASE+0], the second stop has color CREG[CBASE+1]
-and offset NREG[NBASE+1], and so on. The gradient also uses the four numbers
-NREG[NBASE-4], NREG[NBASE-3], NREG[NBASE-2], NREG[NBASE-1].
+and offset NREG[NBASE+1], and so on.
-For a linear gradient, these are the x1, y1, x2 and y2 (in order) that define
-the gradient start and end, corresponding to stop offsets 0 and 1.
+The gradient also uses the six numbers from NREG[NBASE-6] to NREG[NBASE-1],
+which form an affine transformation matrix [a, b, c; d, e, f] such that
+a=NREG[NBASE-6], b=NREG[NBASE-5], c=NREG[NBASE-4], etc. This matrix maps from
+graphic coordinate space (defined by the metadata's viewBox) to gradient
+coordinate space. Gradient coordinate space is where a linear gradient ranges
+from x=0 to x=1, and a radial gradient has center (0, 0) and radius 1.
-For a radial gradient, these are the cx, cy, r1 and r2 (in order) that define
-the gradient center and a start and end radius, corresponding to stop offsets 0
-and 1.
+The graphic coordinate (px, py) maps to the gradient coordinate (dx, dy) by:
+
+ dx = a*px + b*py + c
+ dy = d*px + e*py + f
+
+The appendix below gives explicit formulae for the [a, b, c; d, e, f] affine
+transformation matrix for common gradient geometry, such as a linear gradient
+defined by two points.
At the time a gradient is used to fill a path, it is invalid for any of the
stop colors to itself be a gradient, or for any stop offset to be less than or
@@ -546,11 +557,104 @@
e1 z (closePath); end path
There are more examples in the ./testdata directory.
+
+
+Appendix - Gradient Transformation Matrices
+
+This appendix derives the affine transformation matrices [a, b, c; d, e, f] for
+linear, circular and elliptical gradients.
+
+
+Linear Gradients
+
+For a linear gradient from (x1, y1) to (x2, y2), let dx, dy = x2-x1, y2-y1. In
+gradient coordinate space, the y-coordinate is ignored, so the transformation
+matrix simplifies to [a, b, c; 0, 0, 0]. It satisfies the three simultaneous
+equations:
+
+ a*(x1 ) + b*(y1 ) + c = 0 (eq L.0)
+ a*(x1+dy) + b*(y1-dx) + c = 0 (eq L.1)
+ a*(x1+dx) + b*(y1+dy) + c = 1 (eq L.2)
+
+Subtracting equation L.0 from equations L.1 and L.2 yields:
+
+ a*dy - b*dx = 0
+ a*dx + b*dy = 1
+
+So that
+
+ a*dy*dy - b*dx*dy = 0
+ a*dx*dx + b*dx*dy = dx
+
+Overall:
+
+ a = dx / (dx*dx + dy*dy)
+ b = dy / (dx*dx + dy*dy)
+ c = -a*x1 - b*y1
+ d = 0
+ e = 0
+ f = 0
+
+
+Circular Gradients
+
+For a circular gradient with center (cx, cy) and radius vector (rx, ry), such
+that (cx+rx, cy+ry) is on the circle, let
+
+ r = math.Sqrt(rx*rx + ry*ry)
+
+The transformation matrix maps (cx, cy) to (0, 0), maps (cx+r, cy) to (1, 0)
+and maps (cx, cy+r) to (0, 1). Solving those six simultaneous equations give:
+
+ a = +1 / r
+ b = +0 / r
+ c = -cx / r
+ d = +0 / r
+ e = +1 / r
+ f = -cy / r
+
+
+Elliptical Gradients
+
+For an elliptical gradient with center (cx, cy) and axis vectors (rx, ry) and
+(sx, sy), such that (cx+rx, cx+ry) and (cx+sx, cx+sy) are on the ellipse, the
+transformation matrix satisfies the six simultaneous equations:
+
+ a*(cx ) + b*(cy ) + c = 0 (eq E.0)
+ a*(cx+rx) + b*(cy+ry) + c = 1 (eq E.1)
+ a*(cx+sx) + b*(cy+sy) + c = 0 (eq E.2)
+ d*(cx ) + e*(cy ) + f = 0 (eq E.3)
+ d*(cx+rx) + e*(cy+ry) + f = 0 (eq E.4)
+ d*(cx+sx) + e*(cy+sy) + f = 1 (eq E.5)
+
+Subtracting equation E.0 from equations E.1 and E.2 yields:
+
+ a*rx + b*ry = 1
+ a*sx + b*sy = 0
+
+Solving these two simultaneous equations yields:
+
+ a = +sy / (rx*sy - sx*ry)
+ b = -sx / (rx*sy - sx*ry)
+
+Re-arranging E.0 yields:
+
+ c = -a*cx - b*cy
+
+Similarly for d, e and f so that, overall:
+
+ a = +sy / (rx*sy - sx*ry)
+ b = -sx / (rx*sy - sx*ry)
+ c = -a*cx - b*cy
+ d = -ry / (rx*sy - sx*ry)
+ e = +rx / (rx*sy - sx*ry)
+ f = -d*cx - e*cy
+
+Note that if rx = r, ry = 0, sx = 0 and sy = r then this simplifies to the
+circular gradient transformation matrix formula, above.
*/
package iconvg
-// TODO: elliptical gradients, not just circular?
-
// TODO: shapes (circles, rects) and strokes? Or can we assume that authoring
// tools will convert shapes and strokes to paths?
diff --git a/shiny/iconvg/encode.go b/shiny/iconvg/encode.go
index aa52041..ead030a 100644
--- a/shiny/iconvg/encode.go
+++ b/shiny/iconvg/encode.go
@@ -8,6 +8,8 @@
"errors"
"image/color"
"math"
+
+ "golang.org/x/image/math/f32"
)
var (
@@ -262,37 +264,30 @@
e.buf.encodeReal(lod1)
}
-// SetLinearGradient sets CREG[CSEL] to encode the linear gradient whose
-// geometry is defined by x1, y1, x2, y2 and colors defined by spread and
-// stops.
+// SetGradient sets CREG[CSEL] to encode the gradient whose colors defined by
+// spread and stops. Its geometry is either linear or radial, depending on the
+// radial argument, and the given affine transformation matrix maps from
+// graphic coordinate space defined by the metadata's viewBox (e.g. from (-32,
+// -32) to (+32, +32)) to gradient coordinate space. Gradient coordinate space
+// is where a linear gradient ranges from x=0 to x=1, and a radial gradient has
+// center (0, 0) and radius 1.
//
// The colors of the n stops are encoded at CREG[cBase+0], CREG[cBase+1], ...,
// CREG[cBase+n-1]. Similarly, the offsets of the n stops are encoded at
// NREG[nBase+0], NREG[nBase+1], ..., NREG[nBase+n-1]. Additional parameters
// are stored at NREG[nBase-4], NREG[nBase-3], NREG[nBase-2] and NREG[nBase-1].
//
-// See the package documentation for more details on the gradient encoding
-// format.
-//
// The CSEL and NSEL selector registers maintain the same values after the
// method returns as they had when the method was called.
-func (e *Encoder) SetLinearGradient(cBase, nBase uint8, x1, y1, x2, y2 float32, spread GradientSpread, stops []GradientStop) {
- e.setGradient(cBase, nBase, x1, y1, x2, y2, 0x80, spread, stops)
-}
-
-// SetRadialGradient is like SetLinearGradient except that the radial
-// gradient's geometry is defined by cx, cy and r.
-func (e *Encoder) SetRadialGradient(cBase, nBase uint8, cx, cy, r float32, spread GradientSpread, stops []GradientStop) {
- // TODO: two radii, r1 and r2, as per doc.go.
- e.setGradient(cBase, nBase, cx, cy, 0, r, 0xc0, spread, stops)
-}
-
-func (e *Encoder) setGradient(cBase, nBase uint8, a0, a1, a2, a3 float32, bFlags uint8, spread GradientSpread, stops []GradientStop) {
+//
+// See the package documentation for more details on the gradient encoding
+// format and the derivation of common transformation matrices.
+func (e *Encoder) SetGradient(cBase, nBase uint8, radial bool, transform f32.Aff3, spread GradientSpread, stops []GradientStop) {
e.checkModeStyling()
if e.err != nil {
return
}
- if len(stops) > 60 {
+ if len(stops) > 64-len(transform) {
e.err = errTooManyGradientStops
return
}
@@ -306,6 +301,10 @@
oldNSel := e.nSel
cBase &= 0x3f
nBase &= 0x3f
+ bFlags := uint8(0x80)
+ if radial {
+ bFlags = 0xc0
+ }
e.SetCReg(0, false, RGBAColor(color.RGBA{
R: uint8(len(stops)),
G: cBase | uint8(spread<<6),
@@ -314,10 +313,9 @@
}))
e.SetCSel(cBase)
e.SetNSel(nBase)
- e.SetNReg(4, false, a0)
- e.SetNReg(3, false, a1)
- e.SetNReg(2, false, a2)
- e.SetNReg(1, false, a3)
+ for i, v := range transform {
+ e.SetNReg(uint8(len(transform)-i), false, v)
+ }
for _, s := range stops {
r, g, b, a := s.Color.RGBA()
e.SetCReg(0, true, RGBAColor(color.RGBA{
@@ -332,6 +330,57 @@
e.SetNSel(oldNSel)
}
+// SetLinearGradient is like SetGradient with radial=false except that the
+// transformation matrix is implicitly defined by two boundary points (x1, y1)
+// and (x2, y2).
+func (e *Encoder) SetLinearGradient(cBase, nBase uint8, x1, y1, x2, y2 float32, spread GradientSpread, stops []GradientStop) {
+ // See the package documentation's appendix for a derivation of the
+ // transformation matrix.
+ dx, dy := x2-x1, y2-y1
+ d := dx*dx + dy*dy
+ ma := dx / d
+ mb := dy / d
+ e.SetGradient(cBase, nBase, false, f32.Aff3{
+ ma, mb, -ma*x1 - mb*y1,
+ 0, 0, 0,
+ }, spread, stops)
+}
+
+// SetCircularGradient is like SetGradient with radial=true except that the
+// transformation matrix is implicitly defined by a center (cx, cy) and a
+// radius vector (rx, ry) such that (cx+rx, cy+ry) is on the circle.
+func (e *Encoder) SetCircularGradient(cBase, nBase uint8, cx, cy, rx, ry float32, spread GradientSpread, stops []GradientStop) {
+ // See the package documentation's appendix for a derivation of the
+ // transformation matrix.
+ invR := float32(1 / math.Sqrt(float64(rx*rx+ry*ry)))
+ e.SetGradient(cBase, nBase, true, f32.Aff3{
+ invR, 0, -cx * invR,
+ 0, invR, -cy * invR,
+ }, spread, stops)
+}
+
+// SetEllipticalGradient is like SetGradient with radial=true except that the
+// transformation matrix is implicitly defined by a center (cx, cy) and two
+// axis vectors (rx, ry) and (sx, sy) such that (cx+rx, cy+ry) and (cx+sx,
+// cy+sy) are on the ellipse.
+func (e *Encoder) SetEllipticalGradient(cBase, nBase uint8, cx, cy, rx, ry, sx, sy float32, spread GradientSpread, stops []GradientStop) {
+ // See the package documentation's appendix for a derivation of the
+ // transformation matrix.
+ invRSSR := 1 / (rx*sy - sx*ry)
+
+ ma := +sy * invRSSR
+ mb := -sx * invRSSR
+ mc := -ma*cx - mb*cy
+ md := -ry * invRSSR
+ me := +rx * invRSSR
+ mf := -md*cx - me*cy
+
+ e.SetGradient(cBase, nBase, true, f32.Aff3{
+ ma, mb, mc,
+ md, me, mf,
+ }, spread, stops)
+}
+
func (e *Encoder) StartPath(adj uint8, x, y float32) {
e.checkModeStyling()
if e.err != nil {
diff --git a/shiny/iconvg/encode_test.go b/shiny/iconvg/encode_test.go
index 8766d21..94f3e0f 100644
--- a/shiny/iconvg/encode_test.go
+++ b/shiny/iconvg/encode_test.go
@@ -130,6 +130,171 @@
testEncode(t, &e, "testdata/arcs.ivg")
}
+var cowbellGradients = []struct {
+ radial bool
+
+ // Linear gradient coefficients.
+ x1, y1 float32
+ x2, y2 float32
+ tx, ty float32
+
+ // Radial gradient coefficients.
+ cx, cy, r float32
+ transform f32.Aff3
+
+ stops []GradientStop
+}{{
+// The 0th element is unused.
+}, {
+ radial: true,
+ cx: -102.14,
+ cy: 20.272,
+ r: 18.012,
+ transform: f32.Aff3{
+ .33050, -.50775, 65.204,
+ .17296, .97021, 16.495,
+ },
+ stops: []GradientStop{
+ {Offset: 0, Color: color.RGBA{0xed, 0xd4, 0x00, 0xff}},
+ {Offset: 1, Color: color.RGBA{0xfc, 0xe9, 0x4f, 0xff}},
+ },
+}, {
+ radial: true,
+ cx: -97.856,
+ cy: 26.719,
+ r: 18.61,
+ transform: f32.Aff3{
+ .35718, -.11527, 51.072,
+ .044280, .92977, 7.6124,
+ },
+ stops: []GradientStop{
+ {Offset: 0, Color: color.RGBA{0xed, 0xd4, 0x00, 0xff}},
+ {Offset: 1, Color: color.RGBA{0xfc, 0xe9, 0x4f, 0xff}},
+ },
+}, {
+ x1: -16.183,
+ y1: 35.723,
+ x2: -18.75,
+ y2: 29.808,
+ tx: 48.438,
+ ty: -.22321,
+ stops: []GradientStop{
+ {Offset: 0, Color: color.RGBA{0x39, 0x21, 0x00, 0xff}},
+ {Offset: 1, Color: color.RGBA{0x0f, 0x08, 0x00, 0xff}},
+ },
+}}
+
+var cowbellSVGData = []struct {
+ rgba color.RGBA
+ gradient int
+ d string
+ transform *f32.Aff3
+}{{
+ gradient: 2,
+ d: "m5.6684 17.968l.265-4.407 13.453 19.78.301 8.304-14.019-23.677z",
+}, {
+ gradient: 1,
+ 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",
+}, {
+ rgba: color.RGBA{0xfd * 127 / 255, 0xee * 127 / 255, 0x74 * 127 / 255, 127},
+ 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",
+}, {
+ rgba: color.RGBA{0xc4, 0xa0, 0x00, 0xff},
+ 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",
+}, {
+ gradient: 3,
+ 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",
+}, {
+ rgba: color.RGBA{0x00, 0x00, 0x00, 0xff},
+ 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",
+}}
+
+func inv(x *f32.Aff3) f32.Aff3 {
+ invDet := 1 / (x[0]*x[4] - x[1]*x[3])
+ return f32.Aff3{
+ +x[4] * invDet,
+ -x[1] * invDet,
+ (x[1]*x[5] - x[2]*x[4]) * invDet,
+ -x[3] * invDet,
+ +x[0] * invDet,
+ (x[2]*x[3] - x[0]*x[5]) * invDet,
+ }
+}
+
+func TestEncodeCowbell(t *testing.T) {
+ var e Encoder
+ e.Reset(Metadata{
+ ViewBox: Rectangle{
+ Min: f32.Vec2{0, 0},
+ Max: f32.Vec2{+48, +48},
+ },
+ Palette: DefaultPalette,
+ })
+
+ for _, data := range cowbellSVGData {
+ if data.rgba != (color.RGBA{}) {
+ e.SetCReg(0, false, RGBAColor(data.rgba))
+ } else if data.gradient != 0 {
+ g := cowbellGradients[data.gradient]
+ if g.radial {
+ iform := inv(&g.transform)
+ iform[2] -= g.cx
+ iform[5] -= g.cy
+ for i := range iform {
+ iform[i] /= g.r
+ }
+ e.SetGradient(10, 10, true, iform, GradientSpreadPad, g.stops)
+ } else {
+ x1 := g.x1 + g.tx
+ y1 := g.y1 + g.ty
+ x2 := g.x2 + g.tx
+ y2 := g.y2 + g.ty
+ e.SetLinearGradient(10, 10, x1, y1, x2, y2, GradientSpreadPad, g.stops)
+ }
+ }
+
+ if err := encodePathData(&e, data.d, 0, false); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ testEncode(t, &e, "testdata/cowbell.ivg")
+}
+
+func TestEncodeElliptical(t *testing.T) {
+ var e Encoder
+
+ const (
+ cx, cy = -20, -10
+ rx, ry = 0, 24
+ sx, sy = 30, 15
+ )
+
+ e.SetEllipticalGradient(10, 10, cx, cy, rx, ry, sx, sy, GradientSpreadReflect, []GradientStop{
+ {Offset: 0, Color: color.RGBA{0xc0, 0x00, 0x00, 0xff}},
+ {Offset: 1, Color: color.RGBA{0x00, 0x00, 0xc0, 0xff}},
+ })
+ e.StartPath(0, -32, -32)
+ e.AbsHLineTo(+32)
+ e.AbsVLineTo(+32)
+ e.AbsHLineTo(-32)
+ e.ClosePathEndPath()
+
+ e.SetCReg(0, false, RGBAColor(color.RGBA{0xff, 0xff, 0xff, 0xff}))
+ diamond := func(x, y float32) {
+ e.StartPath(0, x-1, y)
+ e.AbsLineTo(x, y-1)
+ e.AbsLineTo(x+1, y)
+ e.AbsLineTo(x, y+1)
+ e.ClosePathEndPath()
+ }
+ diamond(cx, cy)
+ diamond(cx+rx, cy+ry)
+ diamond(cx+sx, cy+sy)
+
+ testEncode(t, &e, "testdata/elliptical.ivg")
+}
+
var faviconColors = []color.RGBA{
{0x76, 0xe1, 0xfe, 0xff},
{0x38, 0x4e, 0x54, 0xff},
@@ -206,60 +371,77 @@
adj -= 2
}
- var args [7]float32
- prevN, prevVerb := 0, byte(0)
- for d, first := data.d, true; d[0] != 'z'; first = false {
- n, verb, implicit := 0, d[0], false
- switch d[0] {
- case 'H', 'h', 'V', 'v':
- n = 1
- case 'L', 'M', 'l', 'm':
- n = 2
- case 'S', 's':
- n = 4
- case 'C', 'c':
- n = 6
- case 'A', 'a':
- n = 7
- default:
- if prevVerb == '\x00' {
- panic("unrecognized verb")
- }
- n, verb, implicit = prevN, prevVerb, true
- }
- prevN, prevVerb = n, verb
- if !implicit {
- d = d[1:]
- }
+ if err := encodePathData(&e, data.d, adj, true); err != nil {
+ t.Fatal(err)
+ }
+ }
- for i := 0; i < n; i++ {
- nDots := 0
- if d[0] == '.' {
- nDots = 1
- }
- j := 1
- for ; ; j++ {
- switch d[j] {
- case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ testEncode(t, &e, "testdata/favicon.ivg")
+}
+
+func encodePathData(e *Encoder, d string, adj uint8, normalizeTo64X64 bool) error {
+ var args [7]float32
+ prevN, prevVerb := 0, byte(0)
+ for first := true; d != "z"; first = false {
+ n, verb, implicit := 0, d[0], false
+ switch d[0] {
+ case 'H', 'h', 'V', 'v':
+ n = 1
+ case 'L', 'M', 'l', 'm':
+ n = 2
+ case 'S', 's':
+ n = 4
+ case 'C', 'c':
+ n = 6
+ case 'A', 'a':
+ n = 7
+ case 'z':
+ n = 0
+ default:
+ if prevVerb == '\x00' {
+ panic("unrecognized verb")
+ }
+ n, verb, implicit = prevN, prevVerb, true
+ }
+ prevN, prevVerb = n, verb
+ if prevVerb == 'M' {
+ prevVerb = 'L'
+ } else if prevVerb == 'm' {
+ prevVerb = 'l'
+ }
+ if !implicit {
+ d = d[1:]
+ }
+
+ for i := 0; i < n; i++ {
+ nDots := 0
+ if d[0] == '.' {
+ nDots = 1
+ }
+ j := 1
+ for ; ; j++ {
+ switch d[j] {
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ continue
+ case '.':
+ nDots++
+ if nDots == 1 {
continue
- case '.':
- nDots++
- if nDots == 1 {
- continue
- }
}
- break
}
- f, err := strconv.ParseFloat(d[:j], 64)
- if err != nil {
- t.Fatal(err)
- }
- args[i] = float32(f)
- for ; d[j] == ' ' || d[j] == ','; j++ {
- }
- d = d[j:]
+ break
}
+ f, err := strconv.ParseFloat(d[:j], 64)
+ if err != nil {
+ return err
+ }
+ args[i] = float32(f)
+ for ; d[j] == ' ' || d[j] == ','; j++ {
+ }
+ d = d[j:]
+ }
+ if normalizeTo64X64 {
// The original SVG is 32x32 units, with the top left being (0, 0).
// Normalize to 64x64 units, with the center being (0, 0).
if verb == 'A' {
@@ -283,45 +465,50 @@
args[i] = 2 * args[i]
}
}
-
- if first {
- first = false
- e.StartPath(adj, args[0], args[1])
- continue
- }
- switch verb {
- case 'H':
- e.AbsHLineTo(args[0])
- case 'h':
- e.RelHLineTo(args[0])
- case 'V':
- e.AbsVLineTo(args[0])
- case 'v':
- e.RelVLineTo(args[0])
- case 'L':
- e.AbsLineTo(args[0], args[1])
- case 'l':
- e.RelLineTo(args[0], args[1])
- case 'S':
- e.AbsSmoothCubeTo(args[0], args[1], args[2], args[3])
- case 's':
- e.RelSmoothCubeTo(args[0], args[1], args[2], args[3])
- case 'C':
- e.AbsCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
- case 'c':
- e.RelCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
- case 'A':
- e.AbsArcTo(args[0], args[1], args[2], args[3] != 0, args[4] != 0, args[5], args[6])
- case 'a':
- e.RelArcTo(args[0], args[1], args[2], args[3] != 0, args[4] != 0, args[5], args[6])
- default:
- panic("unrecognized verb")
- }
+ } else if verb == 'A' || verb == 'a' {
+ args[2] /= 360
}
- e.ClosePathEndPath()
- }
- testEncode(t, &e, "testdata/favicon.ivg")
+ if first {
+ first = false
+ e.StartPath(adj, args[0], args[1])
+ continue
+ }
+ switch verb {
+ case 'H':
+ e.AbsHLineTo(args[0])
+ case 'h':
+ e.RelHLineTo(args[0])
+ case 'V':
+ e.AbsVLineTo(args[0])
+ case 'v':
+ e.RelVLineTo(args[0])
+ case 'L':
+ e.AbsLineTo(args[0], args[1])
+ case 'l':
+ e.RelLineTo(args[0], args[1])
+ case 'm':
+ e.ClosePathRelMoveTo(args[0], args[1])
+ case 'S':
+ e.AbsSmoothCubeTo(args[0], args[1], args[2], args[3])
+ case 's':
+ e.RelSmoothCubeTo(args[0], args[1], args[2], args[3])
+ case 'C':
+ e.AbsCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
+ case 'c':
+ e.RelCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
+ case 'A':
+ e.AbsArcTo(args[0], args[1], args[2], args[3] != 0, args[4] != 0, args[5], args[6])
+ case 'a':
+ e.RelArcTo(args[0], args[1], args[2], args[3] != 0, args[4] != 0, args[5], args[6])
+ case 'z':
+ // No-op.
+ default:
+ panic("unrecognized verb")
+ }
+ }
+ e.ClosePathEndPath()
+ return nil
}
func TestEncodeGradient(t *testing.T) {
@@ -355,14 +542,14 @@
e.AbsHLineTo(-30)
e.ClosePathEndPath()
- e.SetRadialGradient(10, 10, -8, 8, 16, GradientSpreadReflect, rgb)
+ e.SetCircularGradient(10, 10, -8, 8, 0, 16, GradientSpreadReflect, rgb)
e.StartPath(0, -30, +2)
e.AbsHLineTo(+30)
e.AbsVLineTo(+14)
e.AbsHLineTo(-30)
e.ClosePathEndPath()
- e.SetRadialGradient(10, 10, -8, 24, 16, GradientSpreadRepeat, cmy)
+ e.SetCircularGradient(10, 10, -8, 24, 0, 16, GradientSpreadRepeat, cmy)
e.StartPath(0, -30, +18)
e.AbsHLineTo(+30)
e.AbsVLineTo(+30)
diff --git a/shiny/iconvg/internal/gradient/gradient.go b/shiny/iconvg/internal/gradient/gradient.go
index 3baa944..92446e7 100644
--- a/shiny/iconvg/internal/gradient/gradient.go
+++ b/shiny/iconvg/internal/gradient/gradient.go
@@ -21,7 +21,9 @@
// the gradient.Gradient type and provide fast path code.
//
// Doing so requires coming up with a stable API that we'd be happy to support
-// in the long term.
+// in the long term. This would probably include an easier way to create
+// linear, circular and elliptical gradients, without having to explicitly
+// calculate the f64.Aff3 matrix.
// Shape is the gradient shape.
type Shape uint8
@@ -153,10 +155,6 @@
type Gradient struct {
Shape Shape
Spread Spread
- Ranges []Range
-
- // First and Last are the first and last stop's colors.
- First, Last color.RGBA64
// Pix2Grad transforms coordinates from pixel space (the arguments to the
// Image.At method) to gradient space. Gradient space is where a linear
@@ -168,10 +166,19 @@
//
// For a linear gradient, the bottom row is ignored.
Pix2Grad f64.Aff3
+
+ Ranges []Range
+
+ // First and Last are the first and last stop's colors.
+ First, Last color.RGBA64
}
-func (g *Gradient) init(spread Spread, stops []Stop) {
+// Init initializes g to a gradient whose geometry is defined by shape and
+// pix2Grad and whose colors are defined by spread and stops.
+func (g *Gradient) Init(shape Shape, spread Spread, pix2Grad f64.Aff3, stops []Stop) {
+ g.Shape = shape
g.Spread = spread
+ g.Pix2Grad = pix2Grad
g.Ranges = AppendRanges(g.Ranges[:0], stops)
if len(stops) == 0 {
g.First = color.RGBA64{}
@@ -182,50 +189,6 @@
}
}
-// InitLinear initializes g to be a linear gradient from (x1, y1) to (x2, y2),
-// in pixel space. Its colors are given by spread and stops.
-func (g *Gradient) InitLinear(x1, y1, x2, y2 float64, spread Spread, stops []Stop) {
- g.init(spread, stops)
- g.Shape = ShapeLinear
- dx, dy := x2-x1, y2-y1
- // The top row [a, b, c] of the Pix2Grad matrix satisfies the three
- // simultaneous equations:
- // a*(x1 ) + b*(y1 ) + c = 0 (eq #0)
- // a*(x1+dy) + b*(y1-dx) + c = 0 (eq #1)
- // a*(x1+dx) + b*(y1+dy) + c = 1 (eq #2)
- // Subtracting equation #0 from equations #1 and #2 give:
- // a*( +dy) + b*( -dx) = 0 (eq #3)
- // a*( +dx) + b*( +dy) = 1 (eq #4)
- // So that
- // a*(dy*dy) - b*(dy*dx) = 0 (eq #5)
- // a*(dx*dx) + b*(dx*dy) = dx (eq #6)
- // And that
- // a = dx / (dx*dx + dy*dy) (eq #7)
- // Equations #3 and #7 yield:
- // b = dy / (dx*dx + dy*dy) (eq #8)
- d := dx*dx + dy*dy
- a := dx / d
- b := dy / d
- g.Pix2Grad = f64.Aff3{
- a, b, -a*x1 - b*y1,
- 0, 0, 0,
- }
-}
-
-// InitCircular initializes g to be a circular gradient centered on (cx, cy)
-// with radius r, in pixel space. Its colors are given by spread and stops.
-func (g *Gradient) InitCircular(cx, cy, r float64, spread Spread, stops []Stop) {
- g.init(spread, stops)
- g.Shape = ShapeRadial
- invR := 1 / r
- g.Pix2Grad = f64.Aff3{
- invR, 0, -cx * invR,
- 0, invR, -cy * invR,
- }
-}
-
-// TODO: Gradient.InitElliptical?
-
// ColorModel satisfies the image.Image interface.
func (g *Gradient) ColorModel() color.Model {
return color.RGBA64Model
diff --git a/shiny/iconvg/rasterizer.go b/shiny/iconvg/rasterizer.go
index da06ec0..342a9c9 100644
--- a/shiny/iconvg/rasterizer.go
+++ b/shiny/iconvg/rasterizer.go
@@ -12,6 +12,7 @@
"golang.org/x/exp/shiny/iconvg/internal/gradient"
"golang.org/x/image/math/f32"
+ "golang.org/x/image/math/f64"
"golang.org/x/image/vector"
)
@@ -186,31 +187,43 @@
}
}
- if (rgba.B>>6)&0x01 == 0 {
- z.gradient.InitLinear(
- float64(z.absX(z.nReg[(nBase-4)&0x3f])),
- float64(z.absY(z.nReg[(nBase-3)&0x3f])),
- float64(z.absX(z.nReg[(nBase-2)&0x3f])),
- float64(z.absY(z.nReg[(nBase-1)&0x3f])),
- gradient.Spread(rgba.G>>6),
- z.stops[:nStops],
- )
- } else {
- // TODO: honor the r1 radius (at nBase-2), not just r2 (at nBase-1).
- //
- // TODO: relX can give a different scale/bias than relY. We should
- // really use an elliptical (not circular) gradient, in gradient space
- // (not pixel space).
- r := z.relX(z.nReg[(nBase-1)&0x3f])
+ // The affine transformation matrix in the IconVG graphic, stored in 6
+ // contiguous NREG registers, goes from graphic coordinate space (i.e. the
+ // metadata viewBox) to the gradient coordinate space. We need it to start
+ // in pixel space, not graphic coordinate space.
- z.gradient.InitCircular(
- float64(z.absX(z.nReg[(nBase-4)&0x3f])),
- float64(z.absY(z.nReg[(nBase-3)&0x3f])),
- float64(r),
- gradient.Spread(rgba.G>>6),
- z.stops[:nStops],
- )
+ invZSX := 1 / float64(z.scaleX)
+ invZSY := 1 / float64(z.scaleY)
+ zBX := float64(z.biasX)
+ zBY := float64(z.biasY)
+
+ a := float64(z.nReg[(nBase-6)&0x3f])
+ b := float64(z.nReg[(nBase-5)&0x3f])
+ c := float64(z.nReg[(nBase-4)&0x3f])
+ d := float64(z.nReg[(nBase-3)&0x3f])
+ e := float64(z.nReg[(nBase-2)&0x3f])
+ f := float64(z.nReg[(nBase-1)&0x3f])
+
+ pix2Grad := f64.Aff3{
+ a * invZSX,
+ b * invZSY,
+ c - a*zBX - b*zBY,
+ d * invZSX,
+ e * invZSY,
+ f - d*zBX - e*zBY,
}
+
+ shape := gradient.ShapeLinear
+ if (rgba.B>>6)&0x01 != 0 {
+ shape = gradient.ShapeRadial
+ }
+ z.gradient.Init(
+ shape,
+ gradient.Spread(rgba.G>>6),
+ pix2Grad,
+ z.stops[:nStops],
+ )
+
return true
}
diff --git a/shiny/iconvg/testdata/README b/shiny/iconvg/testdata/README
index 329454d..21c5ee6 100644
--- a/shiny/iconvg/testdata/README
+++ b/shiny/iconvg/testdata/README
@@ -32,6 +32,24 @@
+cowbell.svg is an original artwork by nigeltao@golang.org.
+
+cowbell.ivg is an IconVG version of that SVG file.
+
+cowbell.ivg.disassembly is a disassembly of that IconVG file.
+
+cowbell.png is a rendering of that IconVG file.
+
+
+
+elliptical.ivg was created manually.
+
+elliptical.ivg.disassembly is a disassembly of that IconVG file.
+
+elliptical.png is a rendering of that IconVG file.
+
+
+
favicon.svg is based on doc/gopher/favicon.svg from the Go 1.7 release, after
using Inkscape to convert strokes and circles to paths, and saving it as an
"Optimized SVG".
@@ -66,7 +84,7 @@
video-005.primitive.svg was based on running github.com/fogleman/primitive on a
256x192 scaled version of video-005.jpeg.
-video-005.primitive.ivg is an IconVG version of video-005.primitive.svg.
+video-005.primitive.ivg is an IconVG version of that SVG file.
video-005.primitive.ivg.disassembly is a disassembly of that IconVG file.
diff --git a/shiny/iconvg/testdata/cowbell.ivg b/shiny/iconvg/testdata/cowbell.ivg
new file mode 100644
index 0000000..f98ce1f
--- /dev/null
+++ b/shiny/iconvg/testdata/cowbell.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/cowbell.ivg.disassembly b/shiny/iconvg/testdata/cowbell.ivg.disassembly
new file mode 100644
index 0000000..a97bfd6
--- /dev/null
+++ b/shiny/iconvg/testdata/cowbell.ivg.disassembly
@@ -0,0 +1,611 @@
+89 49 56 47 IconVG Magic identifier
+02 Number of metadata chunks: 1
+0a Metadata chunk length: 5
+00 Metadata Identifier: 0 (viewBox)
+80 +0
+80 +0
+e0 +48
+e0 +48
+98 Set CREG[CSEL-0] to a 4 byte color
+02 4a ca 00 gradient (NSTOPS=2, CBASE=10, NBASE=10, radial, pad)
+0a Set CSEL = 10
+4a Set NSEL = 10
+ae Set NREG[NSEL-6] to a real number
+4b b8 17 3e 0.14816391
+ad Set NREG[NSEL-5] to a real number
+63 7a 96 3c 0.0183689
+ac Set NREG[NSEL-4] to a real number
+03 b6 1c c0 -2.4486084
+ab Set NREG[NSEL-3] to a real number
+33 38 e7 bb -0.0070562586
+aa Set NREG[NSEL-2] to a real number
+73 23 69 3d 0.05691856
+a9 Set NREG[NSEL-1] to a real number
+3b 1b c1 bf -1.5086432
+97 Set CREG[CSEL-0] to a 3 byte (direct) color; CSEL++
+ed d4 00 RGBA edd400ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+00 0
+97 Set CREG[CSEL-0] to a 3 byte (direct) color; CSEL++
+fc e9 4f RGBA fce94fff
+af Set NREG[NSEL-0] to a real number; NSEL++
+02 1
+00 Set CSEL = 0
+40 Set NSEL = 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+ad 85 +5.671875
+f9 91 +17.96875
+23 l (relative lineTo), 4 reps
+45 80 +0.265625
+99 7b -4.40625
+ l (relative lineTo), implicit
+75 8d +13.453125
+c9 93 +19.78125
+ l (relative lineTo), implicit
+4d 80 +0.296875
+4d 88 +8.296875
+ l (relative lineTo), implicit
+fd 71 -14.015625
+55 68 -23.671875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+02 4a ca 00 gradient (NSTOPS=2, CBASE=10, NBASE=10, radial, pad)
+0a Set CSEL = 10
+4a Set NSEL = 10
+ae Set NREG[NSEL-6] to a real number
+57 08 07 3e 0.1318677
+ad Set NREG[NSEL-5] to a real number
+03 56 8d 3d 0.06901169
+ac Set NREG[NSEL-4] to a real number
+93 1c 82 c0 -4.0659866
+ab Set NREG[NSEL-3] to a real number
+2b 94 c0 bc -0.023508146
+aa Set NREG[NSEL-2] to a real number
+87 fe 37 3d 0.04492046
+a9 Set NREG[NSEL-1] to a real number
+e3 ce aa be -0.33360958
+97 Set CREG[CSEL-0] to a 3 byte (direct) color; CSEL++
+ed d4 00 RGBA edd400ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+00 0
+97 Set CREG[CSEL-0] to a 3 byte (direct) color; CSEL++
+fc e9 4f RGBA fce94fff
+af Set NREG[NSEL-0] to a real number; NSEL++
+02 1
+00 Set CSEL = 0
+40 Set NSEL = 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+4d 93 +19.296875
+7d a1 +33.484375
+27 l (relative lineTo), 8 reps
+61 72 -13.625
+51 6c -19.6875
+ l (relative lineTo), implicit
+d9 83 +3.84375
+51 7d -2.6875
+ l (relative lineTo), implicit
+19 80 +0.09375
+e1 7d -2.125
+ l (relative lineTo), implicit
+b5 84 +4.703125
+bd 7d -2.265625
+ l (relative lineTo), implicit
+fd 82 +2.984375
+21 81 +1.125
+ l (relative lineTo), implicit
+91 84 +4.5625
+95 7e -1.421875
+ l (relative lineTo), implicit
+b9 94 +20.71875
+45 90 +16.265625
+ l (relative lineTo), implicit
+b9 68 -23.28125
+c9 8a +10.78125
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+7e 76 39 7f RGBA 7e76397f
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+49 93 +19.28125
+d9 a0 +32.84375
+25 l (relative lineTo), 6 reps
+69 72 -13.59375
+ed 6c -19.078125
+ l (relative lineTo), implicit
+88 +4
+2d 7d -2.828125
+ l (relative lineTo), implicit
+2d 80 +0.171875
+f9 7d -2.03125
+ l (relative lineTo), implicit
+ed 81 +1.921875
+25 7f -0.859375
+ l (relative lineTo), implicit
+d5 92 +18.828125
+f9 92 +18.96875
+ l (relative lineTo), implicit
+b1 74 -11.3125
+d9 85 +5.84375
+e1 z (closePath); end path
+90 Set CREG[CSEL-0] to a 3 byte (direct) color
+c4 a0 00 RGBA c4a000ff
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+39 93 +19.21875
+11 a8 +40.0625
+b0 c (relative cubeTo), 1 reps
+e5 7f -0.109375
+55 7f -0.671875
+cd 7f -0.203125
+b5 7d -2.296875
+cd 7f -0.203125
+61 7c -3.625
+21 l (relative lineTo), 2 reps
+80 +0
+99 7d -2.40625
+ l (relative lineTo), implicit
+85 7d -2.484375
+3d 7c -3.765625
+b9 c (relative cubeTo), 10 reps
+ad 7c -3.328125
+f5 7a -5.046875
+8d 74 -11.453125
+c9 6e -17.21875
+55 76 -9.671875
+41 71 -14.75
+ c (relative cubeTo), implicit
+55 80 +0.328125
+75 80 +0.453125
+b5 81 +1.703125
+21 82 +2.125
+31 82 +2.1875
+a9 82 +2.65625
+ c (relative cubeTo), implicit
+7d 80 +0.484375
+89 80 +0.53125
+5d 81 +1.359375
+b5 81 +1.703125
+ed 81 +1.921875
+99 82 +2.59375
+ c (relative cubeTo), implicit
+49 82 +2.28125
+9d 83 +3.609375
+3d 88 +8.234375
+11 8c +12.0625
+69 88 +8.40625
+11 8c +12.0625
+ c (relative cubeTo), implicit
+19 80 +0.09375
+80 +0
+69 8a +10.40625
+ad 7a -5.328125
+4d 8b +11.296875
+55 7a -5.671875
+ c (relative cubeTo), implicit
+e5 80 +0.890625
+a5 7f -0.359375
+41 8b +11.25
+75 7b -4.546875
+75 8b +11.453125
+a9 7b -4.34375
+ c (relative cubeTo), implicit
+81 80 +0.5
+81 80 +0.5
+45 81 +1.265625
+79 87 +7.46875
+c5 80 +0.765625
+15 88 +8.078125
+ c (relative cubeTo), implicit
+9d 7f -0.390625
+79 80 +0.46875
+f1 7a -5.0625
+ad 83 +3.671875
+c1 75 -10.25
+21 86 +6.125
+ c (relative cubeTo), implicit
+d1 7a -5.1875
+75 82 +2.453125
+05 74 -11.984375
+45 84 +4.265625
+69 73 -12.59375
+45 84 +4.265625
+ c (relative cubeTo), implicit
+95 7f -0.421875
+80 +0
+5d 7f -0.640625
+a9 7f -0.34375
+39 7f -0.78125
+c9 7e -1.21875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+02 4a 8a 00 gradient (NSTOPS=2, CBASE=10, NBASE=10, linear, pad)
+0a Set CSEL = 10
+4a Set NSEL = 10
+ae Set NREG[NSEL-6] to a real number
+6b e4 7c bd -0.061741263
+ad Set NREG[NSEL-5] to a real number
+83 ae 11 be -0.14226723
+ac Set NREG[NSEL-4] to a real number
+6b 57 e1 40 7.0419197
+ab Set NREG[NSEL-3] to a real number
+00 0
+aa Set NREG[NSEL-2] to a real number
+00 0
+a9 Set NREG[NSEL-1] to a real number
+00 0
+97 Set CREG[CSEL-0] to a 3 byte (direct) color; CSEL++
+39 21 00 RGBA 392100ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+00 0
+97 Set CREG[CSEL-0] to a 3 byte (direct) color; CSEL++
+0f 08 00 RGBA 0f0800ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+02 1
+00 Set CSEL = 0
+40 Set NSEL = 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+d5 93 +19.828125
+a5 a1 +33.640625
+b0 c (relative cubeTo), 1 reps
+6d 80 +0.421875
+51 7f -0.6875
+1d 8a +10.109375
+a5 7a -5.359375
+fd 8a +10.984375
+41 7a -5.75
+90 s (relative smooth cubeTo), 1 reps
+e1 89 +9.875
+e1 7b -4.125
+79 8a +10.46875
+fd 7b -4.015625
+b3 c (relative cubeTo), 4 reps
+85 80 +0.515625
+19 80 +0.09375
+d9 80 +0.84375
+05 86 +6.015625
+8d 80 +0.546875
+b5 86 +6.703125
+ c (relative cubeTo), implicit
+c5 7f -0.234375
+89 80 +0.53125
+91 77 -8.4375
+fd 84 +4.984375
+7d 76 -9.515625
+85 85 +5.515625
+ c (relative cubeTo), implicit
+c9 7e -1.21875
+9d 80 +0.609375
+5d 74 -11.640625
+b5 84 +4.703125
+dd 73 -12.140625
+61 84 +4.375
+ c (relative cubeTo), implicit
+75 7f -0.546875
+a1 7f -0.375
+4d 7f -0.703125
+c1 79 -6.25
+a9 7f -0.34375
+2d 79 -6.828125
+e1 z (closePath); end path
+80 Set CREG[CSEL-0] to a 1 byte color
+00 RGBA 000000ff
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+fd 95 +21.984375
+e1 85 +5.875
+27 l (relative lineTo), 8 reps
+25 7b -4.859375
+75 81 +1.453125
+ l (relative lineTo), implicit
+75 7d -2.546875
+d1 7e -1.1875
+ l (relative lineTo), implicit
+ad 7a -5.328125
+95 82 +2.578125
+ l (relative lineTo), implicit
+fd 7f -0.015625
+4d 80 +0.296875
+ l (relative lineTo), implicit
+e9 7f -0.09375
+e1 81 +1.875
+ l (relative lineTo), implicit
+d1 7b -4.1875
+bd 82 +2.734375
+ l (relative lineTo), implicit
+61 80 +0.375
+99 84 +4.59375
+ l (relative lineTo), implicit
+0d 80 +0.046875
+19 80 +0.09375
+90 s (relative smooth cubeTo), 1 reps
+41 83 +3.25
+cd 85 +5.796875
+95 86 +6.578125
+a9 8b +11.65625
+b9 c (relative cubeTo), 10 reps
+ad 81 +1.671875
+ed 82 +2.921875
+5d 83 +3.359375
+dd 85 +5.859375
+b1 84 +4.6875
+19 88 +8.09375
+ c (relative cubeTo), implicit
+ad 80 +0.671875
+1d 81 +1.109375
+3d 81 +1.234375
+11 82 +2.0625
+ad 81 +1.671875
+bd 82 +2.734375
+ c (relative cubeTo), implicit
+39 80 +0.21875
+59 80 +0.34375
+69 80 +0.40625
+9d 80 +0.609375
+8d 80 +0.546875
+d1 80 +0.8125
+ c (relative cubeTo), implicit
+15 80 +0.078125
+1d 80 +0.109375
+25 80 +0.140625
+31 80 +0.1875
+35 80 +0.203125
+45 80 +0.265625
+ c (relative cubeTo), implicit
+11 80 +0.0625
+15 80 +0.078125
+15 80 +0.078125
+25 80 +0.140625
+45 80 +0.265625
+39 80 +0.21875
+ c (relative cubeTo), implicit
+45 80 +0.265625
+21 80 +0.125
+61 80 +0.375
+11 80 +0.0625
+8d 80 +0.546875
+0d 80 +0.046875
+ c (relative cubeTo), implicit
+29 80 +0.15625
+fd 7f -0.015625
+59 80 +0.34375
+f1 7f -0.0625
+8d 80 +0.546875
+e5 7f -0.109375
+ c (relative cubeTo), implicit
+6d 80 +0.421875
+e5 7f -0.109375
+f5 80 +0.953125
+bd 7f -0.265625
+95 81 +1.578125
+89 7f -0.46875
+ c (relative cubeTo), implicit
+41 81 +1.25
+99 7f -0.40625
+d5 82 +2.828125
+05 7f -0.984375
+65 84 +4.390625
+71 7e -1.5625
+ c (relative cubeTo), implicit
+21 83 +3.125
+d9 7e -1.15625
+29 86 +6.15625
+a5 7d -2.359375
+29 86 +6.15625
+a5 7d -2.359375
+21 l (relative lineTo), 2 reps
+09 80 +0.03125
+fd 7f -0.015625
+ l (relative lineTo), implicit
+09 80 +0.03125
+fd 7f -0.015625
+90 s (relative smooth cubeTo), 1 reps
+89 82 +2.53125
+a5 7e -1.359375
+21 85 +5.125
+35 7d -2.796875
+b4 c (relative cubeTo), 5 reps
+4d 81 +1.296875
+49 7f -0.71875
+99 82 +2.59375
+89 7e -1.46875
+9d 83 +3.609375
+ed 7d -2.078125
+ c (relative cubeTo), implicit
+81 80 +0.5
+b5 7f -0.296875
+f1 80 +0.9375
+6d 7f -0.578125
+45 81 +1.265625
+35 7f -0.796875
+ c (relative cubeTo), implicit
+29 80 +0.15625
+e5 7f -0.109375
+4d 80 +0.296875
+c9 7f -0.21875
+69 80 +0.40625
+b5 7f -0.296875
+ c (relative cubeTo), implicit
+1d 80 +0.109375
+e9 7f -0.09375
+35 80 +0.203125
+dd 7f -0.140625
+4d 80 +0.296875
+ad 7f -0.328125
+ c (relative cubeTo), implicit
+29 80 +0.15625
+b5 7f -0.296875
+25 80 +0.140625
+85 7f -0.484375
+29 80 +0.15625
+39 7f -0.78125
+90 s (relative smooth cubeTo), 1 reps
+05 80 +0.015625
+51 7f -0.6875
+80 +0
+e5 7e -1.109375
+b1 c (relative cubeTo), 2 reps
+f9 7f -0.03125
+25 7f -0.859375
+e9 7f -0.09375
+19 7e -1.90625
+d5 7f -0.171875
+0d 7d -2.953125
+ c (relative cubeTo), implicit
+d9 7f -0.15625
+f1 7d -2.0625
+a1 7f -0.375
+f5 7b -4.046875
+a1 7f -0.375
+f5 7b -4.046875
+21 l (relative lineTo), 2 reps
+fd 7f -0.015625
+c9 7f -0.21875
+ l (relative lineTo), implicit
+19 6b -20.90625
+a9 6f -16.34375
+e3 z (closePath); m (relative moveTo)
+cd 7f -0.203125
+1d 81 +1.109375
+20 l (relative lineTo), 1 reps
+29 94 +20.15625
+c5 8f +15.765625
+b4 c (relative cubeTo), 5 reps
+05 80 +0.015625
+31 80 +0.1875
+35 80 +0.203125
+dd 81 +1.859375
+59 80 +0.34375
+d1 83 +3.8125
+ c (relative cubeTo), implicit
+15 80 +0.078125
+09 81 +1.03125
+29 80 +0.15625
+15 82 +2.078125
+2d 80 +0.171875
+e5 82 +2.890625
+ c (relative cubeTo), implicit
+05 80 +0.015625
+69 80 +0.40625
+05 80 +0.015625
+c5 80 +0.765625
+80 +0
+09 81 +1.03125
+ c (relative cubeTo), implicit
+fd 7f -0.015625
+45 80 +0.265625
+e9 7f -0.09375
+79 80 +0.46875
+f9 7f -0.03125
+5d 80 +0.359375
+ c (relative cubeTo), implicit
+11 80 +0.0625
+e5 7f -0.109375
+05 80 +0.015625
+f9 7f -0.03125
+f1 7f -0.0625
+09 80 +0.03125
+90 s (relative smooth cubeTo), 1 reps
+d1 7f -0.1875
+25 80 +0.140625
+a9 7f -0.34375
+41 80 +0.25
+bb c (relative cubeTo), 12 reps
+b5 7f -0.296875
+35 80 +0.203125
+49 7f -0.71875
+79 80 +0.46875
+c9 7e -1.21875
+c5 80 +0.765625
+ c (relative cubeTo), implicit
+7e -1
+99 80 +0.59375
+b5 7d -2.296875
+59 81 +1.34375
+6d 7c -3.578125
+11 82 +2.0625
+ c (relative cubeTo), implicit
+71 7d -2.5625
+71 81 +1.4375
+f1 7a -5.0625
+c5 82 +2.765625
+ed 7a -5.078125
+c9 82 +2.78125
+ c (relative cubeTo), implicit
+f9 7f -0.03125
+05 80 +0.015625
+7a -3
+31 81 +1.1875
+e9 79 -6.09375
+59 82 +2.34375
+ c (relative cubeTo), implicit
+75 7e -1.546875
+95 80 +0.578125
+e1 7c -3.125
+25 81 +1.140625
+a5 7b -4.359375
+8d 81 +1.546875
+ c (relative cubeTo), implicit
+65 7f -0.609375
+35 80 +0.203125
+dd 7e -1.140625
+5d 80 +0.359375
+7d 7e -1.515625
+75 80 +0.453125
+ c (relative cubeTo), implicit
+d1 7f -0.1875
+0d 80 +0.046875
+ad 7f -0.328125
+15 80 +0.078125
+95 7f -0.421875
+19 80 +0.09375
+ c (relative cubeTo), implicit
+f9 7f -0.03125
+f9 7f -0.03125
+f5 7f -0.046875
+f1 7f -0.0625
+e9 7f -0.09375
+e1 7f -0.125
+ c (relative cubeTo), implicit
+e1 7f -0.125
+d5 7f -0.171875
+b1 7f -0.3125
+91 7f -0.4375
+7d 7f -0.515625
+3d 7f -0.765625
+ c (relative cubeTo), implicit
+95 7f -0.421875
+59 7f -0.65625
+7e -1
+69 7e -1.59375
+59 7e -1.65625
+4d 7d -2.703125
+ c (relative cubeTo), implicit
+ad 7e -1.328125
+c9 7d -2.21875
+fd 7c -3.015625
+d9 7a -5.15625
+51 7b -4.6875
+ed 77 -8.078125
+ c (relative cubeTo), implicit
+b1 7c -3.3125
+35 7a -5.796875
+85 79 -6.484375
+89 74 -11.46875
+79 79 -6.53125
+71 74 -11.5625
+25 l (relative lineTo), 6 reps
+b5 7f -0.296875
+39 7c -3.78125
+ l (relative lineTo), implicit
+1d 84 +4.109375
+51 7d -2.6875
+ l (relative lineTo), implicit
+1d 80 +0.109375
+ed 7d -2.078125
+ l (relative lineTo), implicit
+61 84 +4.375
+e5 7d -2.109375
+ l (relative lineTo), implicit
+79 82 +2.46875
+29 81 +1.15625
+ l (relative lineTo), implicit
+bd 84 +4.734375
+95 7e -1.421875
+e1 z (closePath); end path
diff --git a/shiny/iconvg/testdata/cowbell.png b/shiny/iconvg/testdata/cowbell.png
new file mode 100644
index 0000000..e962ff1
--- /dev/null
+++ b/shiny/iconvg/testdata/cowbell.png
Binary files differ
diff --git a/shiny/iconvg/testdata/cowbell.svg b/shiny/iconvg/testdata/cowbell.svg
new file mode 100644
index 0000000..34ec496
--- /dev/null
+++ b/shiny/iconvg/testdata/cowbell.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg id="svg1921" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="256" width="256" viewBox="0 0 48 48" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs id="defs3">
+ <radialGradient id="radialGradient3107" gradientUnits="userSpaceOnUse" cy="20.272" cx="-102.14" gradientTransform="matrix(.33050 .17296 -.50775 .97021 65.204 16.495)" r="18.012">
+ <stop id="stop6387" stop-color="#edd400" offset="0"/>
+ <stop id="stop6389" stop-color="#fce94f" offset="1"/>
+ </radialGradient>
+ <radialGradient id="radialGradient3991" gradientUnits="userSpaceOnUse" cy="26.719" cx="-97.856" gradientTransform="matrix(.35718 .044280 -.11527 .92977 51.072 7.6124)" r="18.61">
+ <stop id="stop3995" stop-color="#edd400" offset="0"/>
+ <stop id="stop3997" stop-color="#fce94f" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient5756" x1="-16.183" gradientUnits="userSpaceOnUse" y1="35.723" gradientTransform="translate(48.438 -.22321)" x2="-18.75" y2="29.808">
+ <stop id="stop5752" stop-color="#392100" offset="0"/>
+ <stop id="stop5754" stop-color="#0f0800" offset="1"/>
+ </linearGradient>
+ </defs>
+ <g id="layer1">
+ <path id="path1463" fill="url(#radialGradient3991)" d="m5.6684 17.968l.265-4.407 13.453 19.78.301 8.304-14.019-23.677z"/>
+ <path id="path1451" fill="url(#radialGradient3107)" 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"/>
+ <path id="path4875" fill-opacity=".49804" fill="#fdee74" 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"/>
+ <path id="path1441" fill="#c4a000" 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"/>
+ <path id="path1433" fill="url(#linearGradient5756)" 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"/>
+ <path id="path1350" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none;text-decoration-style:solid" fill-rule="evenodd" 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"/>
+ </g>
+</svg>
diff --git a/shiny/iconvg/testdata/elliptical.ivg b/shiny/iconvg/testdata/elliptical.ivg
new file mode 100644
index 0000000..0ee0321
--- /dev/null
+++ b/shiny/iconvg/testdata/elliptical.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/elliptical.ivg.disassembly b/shiny/iconvg/testdata/elliptical.ivg.disassembly
new file mode 100644
index 0000000..504194b
--- /dev/null
+++ b/shiny/iconvg/testdata/elliptical.ivg.disassembly
@@ -0,0 +1,79 @@
+89 49 56 47 IconVG Magic identifier
+00 Number of metadata chunks: 0
+98 Set CREG[CSEL-0] to a 4 byte color
+02 8a ca 00 gradient (NSTOPS=2, CBASE=10, NBASE=10, radial, reflect)
+0a Set CSEL = 10
+4a Set NSEL = 10
+ae Set NREG[NSEL-6] to a real number
+af aa aa bc -0.020833336
+bd Set NREG[NSEL-5] to a zero-to-one number
+0a 0.041666668
+ac Set NREG[NSEL-4] to a real number
+00 0
+ab Set NREG[NSEL-3] to a real number
+8b 88 08 3d 0.03333333
+aa Set NREG[NSEL-2] to a real number
+00 0
+b9 Set NREG[NSEL-1] to a zero-to-one number
+a0 0.6666667
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+4b RGBA c00000ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+00 0
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+03 RGBA 0000c0ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+02 1
+00 Set CSEL = 0
+40 Set NSEL = 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+40 -32
+40 -32
+e6 H (absolute horizontal lineTo)
+c0 +32
+e8 V (absolute vertical lineTo)
+c0 +32
+e6 H (absolute horizontal lineTo)
+40 -32
+e1 z (closePath); end path
+80 Set CREG[CSEL-0] to a 1 byte color
+7c RGBA ffffffff
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+56 -21
+6c -10
+02 L (absolute lineTo), 3 reps
+58 -20
+6a -11
+ L (absolute lineTo), implicit
+5a -19
+6c -10
+ L (absolute lineTo), implicit
+58 -20
+6e -9
+e1 z (closePath); end path
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+56 -21
+9c +14
+02 L (absolute lineTo), 3 reps
+58 -20
+9a +13
+ L (absolute lineTo), implicit
+5a -19
+9c +14
+ L (absolute lineTo), implicit
+58 -20
+9e +15
+e1 z (closePath); end path
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+92 +9
+8a +5
+02 L (absolute lineTo), 3 reps
+94 +10
+88 +4
+ L (absolute lineTo), implicit
+96 +11
+8a +5
+ L (absolute lineTo), implicit
+94 +10
+8c +6
+e1 z (closePath); end path
diff --git a/shiny/iconvg/testdata/elliptical.png b/shiny/iconvg/testdata/elliptical.png
new file mode 100644
index 0000000..f67a08f
--- /dev/null
+++ b/shiny/iconvg/testdata/elliptical.png
Binary files differ
diff --git a/shiny/iconvg/testdata/gradient.ivg b/shiny/iconvg/testdata/gradient.ivg
index 9752286..865067b 100644
--- a/shiny/iconvg/testdata/gradient.ivg
+++ b/shiny/iconvg/testdata/gradient.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/gradient.ivg.disassembly b/shiny/iconvg/testdata/gradient.ivg.disassembly
index 697a91d..162cc57 100644
--- a/shiny/iconvg/testdata/gradient.ivg.disassembly
+++ b/shiny/iconvg/testdata/gradient.ivg.disassembly
@@ -4,14 +4,18 @@
04 0a 8a 00 gradient (NSTOPS=4, CBASE=10, NBASE=10, linear, none)
0a Set CSEL = 10
4a Set NSEL = 10
-b4 Set NREG[NSEL-4] to a coordinate number
-68 -12
-b3 Set NREG[NSEL-3] to a coordinate number
-44 -30
+ae Set NREG[NSEL-6] to a real number
+8b 88 08 3d 0.03333333
+ad Set NREG[NSEL-5] to a real number
+8b 88 88 3c 0.016666666
+ac Set NREG[NSEL-4] to a real number
+6b 66 66 3f 0.9000001
+ab Set NREG[NSEL-3] to a real number
+00 0
aa Set NREG[NSEL-2] to a real number
-18 12
-b1 Set NREG[NSEL-1] to a coordinate number
-5c -18
+00 0
+a9 Set NREG[NSEL-1] to a real number
+00 0
87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
64 RGBA ff0000ff
af Set NREG[NSEL-0] to a real number; NSEL++
@@ -44,14 +48,18 @@
05 4a 8a 00 gradient (NSTOPS=5, CBASE=10, NBASE=10, linear, pad)
0a Set CSEL = 10
4a Set NSEL = 10
-b4 Set NREG[NSEL-4] to a coordinate number
-68 -12
-b3 Set NREG[NSEL-3] to a coordinate number
-64 -14
+ae Set NREG[NSEL-6] to a real number
+8b 88 08 3d 0.03333333
+ad Set NREG[NSEL-5] to a real number
+8b 88 88 3c 0.016666666
+ac Set NREG[NSEL-4] to a real number
+27 22 22 3f 0.63333344
+ab Set NREG[NSEL-3] to a real number
+00 0
aa Set NREG[NSEL-2] to a real number
-18 12
-b1 Set NREG[NSEL-1] to a coordinate number
-7c -2
+00 0
+a9 Set NREG[NSEL-1] to a real number
+00 0
87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
18 RGBA 00ffffff
af Set NREG[NSEL-0] to a real number; NSEL++
@@ -88,14 +96,18 @@
04 8a ca 00 gradient (NSTOPS=4, CBASE=10, NBASE=10, radial, reflect)
0a Set CSEL = 10
4a Set NSEL = 10
-b4 Set NREG[NSEL-4] to a coordinate number
-70 -8
-ab Set NREG[NSEL-3] to a real number
-10 8
-aa Set NREG[NSEL-2] to a real number
+b6 Set NREG[NSEL-6] to a coordinate number
+11 80 0.0625
+ad Set NREG[NSEL-5] to a real number
00 0
-a9 Set NREG[NSEL-1] to a real number
-20 16
+bc Set NREG[NSEL-4] to a zero-to-one number
+78 0.5
+ab Set NREG[NSEL-3] to a real number
+00 0
+b2 Set NREG[NSEL-2] to a coordinate number
+11 80 0.0625
+b1 Set NREG[NSEL-1] to a coordinate number
+81 7f -0.5
87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
64 RGBA ff0000ff
af Set NREG[NSEL-0] to a real number; NSEL++
@@ -128,14 +140,18 @@
05 ca ca 00 gradient (NSTOPS=5, CBASE=10, NBASE=10, radial, repeat)
0a Set CSEL = 10
4a Set NSEL = 10
-b4 Set NREG[NSEL-4] to a coordinate number
-70 -8
-ab Set NREG[NSEL-3] to a real number
-30 24
-aa Set NREG[NSEL-2] to a real number
+b6 Set NREG[NSEL-6] to a coordinate number
+11 80 0.0625
+ad Set NREG[NSEL-5] to a real number
00 0
-a9 Set NREG[NSEL-1] to a real number
-20 16
+bc Set NREG[NSEL-4] to a zero-to-one number
+78 0.5
+ab Set NREG[NSEL-3] to a real number
+00 0
+b2 Set NREG[NSEL-2] to a coordinate number
+11 80 0.0625
+b1 Set NREG[NSEL-1] to a coordinate number
+81 7e -1.5
87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
18 RGBA 00ffffff
af Set NREG[NSEL-0] to a real number; NSEL++