chore(v2): vendor dependencies for offline/China builds
go mod vendor pins onnxruntime_go v1.12.1, Gio and the rest into v2/vendor so go run/build work without hitting proxy.golang.org (blocked/slow in China). Verified: CGO_ENABLED=1 go build -mod=vendor ./internal/spike and GOOS=windows go build -mod=vendor ./internal/ui both pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+343
@@ -0,0 +1,343 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package iconvg
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
// buffer holds an encoded IconVG graphic.
|
||||
//
|
||||
// The decodeXxx methods return the decoded value and an integer n, the number
|
||||
// of bytes that value was encoded in. They return n == 0 if an error occurred.
|
||||
//
|
||||
// The encodeXxx methods append to the buffer, modifying the slice in place.
|
||||
type buffer []byte
|
||||
|
||||
func (b buffer) decodeNatural() (u uint32, n int) {
|
||||
if len(b) < 1 {
|
||||
return 0, 0
|
||||
}
|
||||
x := b[0]
|
||||
if x&0x01 == 0 {
|
||||
return uint32(x) >> 1, 1
|
||||
}
|
||||
if x&0x02 == 0 {
|
||||
if len(b) >= 2 {
|
||||
y := uint16(b[0]) | uint16(b[1])<<8
|
||||
return uint32(y) >> 2, 2
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
if len(b) >= 4 {
|
||||
y := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||
return y >> 2, 4
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// decodeNaturalFFV1 is like decodeNatural but for File Format Version 1. See
|
||||
// https://github.com/google/iconvg/issues/33
|
||||
func (b buffer) decodeNaturalFFV1() (u uint32, n int) {
|
||||
if len(b) < 1 {
|
||||
return 0, 0
|
||||
}
|
||||
x := b[0]
|
||||
if x&0x01 != 0 {
|
||||
return uint32(x) >> 1, 1
|
||||
}
|
||||
if x&0x02 != 0 {
|
||||
if len(b) >= 2 {
|
||||
y := uint16(b[0]) | uint16(b[1])<<8
|
||||
return uint32(y) >> 2, 2
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
if len(b) >= 4 {
|
||||
y := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||
return y >> 2, 4
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (b buffer) decodeReal() (f float32, n int) {
|
||||
switch u, n := b.decodeNatural(); n {
|
||||
case 0:
|
||||
return 0, n
|
||||
case 1:
|
||||
return float32(u), n
|
||||
case 2:
|
||||
return float32(u), n
|
||||
default:
|
||||
return math.Float32frombits(u << 2), n
|
||||
}
|
||||
}
|
||||
|
||||
func (b buffer) decodeCoordinate() (f float32, n int) {
|
||||
switch u, n := b.decodeNatural(); n {
|
||||
case 0:
|
||||
return 0, n
|
||||
case 1:
|
||||
return float32(int32(u) - 64), n
|
||||
case 2:
|
||||
return float32(int32(u)-64*128) / 64, n
|
||||
default:
|
||||
return math.Float32frombits(u << 2), n
|
||||
}
|
||||
}
|
||||
|
||||
func (b buffer) decodeZeroToOne() (f float32, n int) {
|
||||
switch u, n := b.decodeNatural(); n {
|
||||
case 0:
|
||||
return 0, n
|
||||
case 1:
|
||||
return float32(u) / 120, n
|
||||
case 2:
|
||||
return float32(u) / 15120, n
|
||||
default:
|
||||
return math.Float32frombits(u << 2), n
|
||||
}
|
||||
}
|
||||
|
||||
func (b buffer) decodeColor1() (c Color, n int) {
|
||||
if len(b) < 1 {
|
||||
return Color{}, 0
|
||||
}
|
||||
return decodeColor1(b[0]), 1
|
||||
}
|
||||
|
||||
func (b buffer) decodeColor2() (c Color, n int) {
|
||||
if len(b) < 2 {
|
||||
return Color{}, 0
|
||||
}
|
||||
return RGBAColor(color.RGBA{
|
||||
R: 0x11 * (b[0] >> 4),
|
||||
G: 0x11 * (b[0] & 0x0f),
|
||||
B: 0x11 * (b[1] >> 4),
|
||||
A: 0x11 * (b[1] & 0x0f),
|
||||
}), 2
|
||||
}
|
||||
|
||||
func (b buffer) decodeColor3Direct() (c Color, n int) {
|
||||
if len(b) < 3 {
|
||||
return Color{}, 0
|
||||
}
|
||||
return RGBAColor(color.RGBA{
|
||||
R: b[0],
|
||||
G: b[1],
|
||||
B: b[2],
|
||||
A: 0xff,
|
||||
}), 3
|
||||
}
|
||||
|
||||
func (b buffer) decodeColor4() (c Color, n int) {
|
||||
if len(b) < 4 {
|
||||
return Color{}, 0
|
||||
}
|
||||
return RGBAColor(color.RGBA{
|
||||
R: b[0],
|
||||
G: b[1],
|
||||
B: b[2],
|
||||
A: b[3],
|
||||
}), 4
|
||||
}
|
||||
|
||||
func (b buffer) decodeColor3Indirect() (c Color, n int) {
|
||||
if len(b) < 3 {
|
||||
return Color{}, 0
|
||||
}
|
||||
return BlendColor(b[0], b[1], b[2]), 3
|
||||
}
|
||||
|
||||
func (b *buffer) encodeNatural(u uint32) {
|
||||
if u < 1<<7 {
|
||||
u = (u << 1)
|
||||
*b = append(*b, uint8(u))
|
||||
return
|
||||
}
|
||||
if u < 1<<14 {
|
||||
u = (u << 2) | 1
|
||||
*b = append(*b, uint8(u), uint8(u>>8))
|
||||
return
|
||||
}
|
||||
u = (u << 2) | 3
|
||||
*b = append(*b, uint8(u), uint8(u>>8), uint8(u>>16), uint8(u>>24))
|
||||
}
|
||||
|
||||
// encodeNaturalFFV1 is like encodeNatural but for File Format Version 1. See
|
||||
// https://github.com/google/iconvg/issues/33
|
||||
func (b *buffer) encodeNaturalFFV1(u uint32) {
|
||||
if u < 1<<7 {
|
||||
u = (u << 1) | 0x01
|
||||
*b = append(*b, uint8(u))
|
||||
return
|
||||
}
|
||||
if u < 1<<14 {
|
||||
u = (u << 2) | 0x02
|
||||
*b = append(*b, uint8(u), uint8(u>>8))
|
||||
return
|
||||
}
|
||||
u = (u << 2)
|
||||
*b = append(*b, uint8(u), uint8(u>>8), uint8(u>>16), uint8(u>>24))
|
||||
}
|
||||
|
||||
func (b *buffer) encodeReal(f float32) int {
|
||||
if u := uint32(f); float32(u) == f && u < 1<<14 {
|
||||
if u < 1<<7 {
|
||||
u = (u << 1)
|
||||
*b = append(*b, uint8(u))
|
||||
return 1
|
||||
}
|
||||
u = (u << 2) | 1
|
||||
*b = append(*b, uint8(u), uint8(u>>8))
|
||||
return 2
|
||||
}
|
||||
b.encode4ByteReal(f)
|
||||
return 4
|
||||
}
|
||||
|
||||
func (b *buffer) encode4ByteReal(f float32) {
|
||||
u := math.Float32bits(f)
|
||||
|
||||
// Round the fractional bits (the low 23 bits) to the nearest multiple of
|
||||
// 4, being careful not to overflow into the upper bits.
|
||||
v := u & 0x007fffff
|
||||
if v < 0x007ffffe {
|
||||
v += 2
|
||||
}
|
||||
u = (u & 0xff800000) | v
|
||||
|
||||
// A 4 byte encoding has the low two bits set.
|
||||
u |= 0x03
|
||||
*b = append(*b, uint8(u), uint8(u>>8), uint8(u>>16), uint8(u>>24))
|
||||
}
|
||||
|
||||
// encode4ByteRealFFV1 is like encode4ByteReal but for File Format Version 1.
|
||||
// See https://github.com/google/iconvg/issues/33
|
||||
func (b *buffer) encode4ByteRealFFV1(f float32) {
|
||||
u := math.Float32bits(f)
|
||||
|
||||
// Round the fractional bits (the low 23 bits) to the nearest multiple of
|
||||
// 4, being careful not to overflow into the upper bits.
|
||||
v := u & 0x007fffff
|
||||
if v < 0x007ffffe {
|
||||
v += 2
|
||||
}
|
||||
u = (u & 0xff800000) | v
|
||||
|
||||
// A 4 byte encoding has the low two bits unset.
|
||||
u &= 0xfffffffc
|
||||
*b = append(*b, uint8(u), uint8(u>>8), uint8(u>>16), uint8(u>>24))
|
||||
}
|
||||
|
||||
func (b *buffer) encodeCoordinate(f float32) int {
|
||||
if i := int32(f); -64 <= i && i < +64 && float32(i) == f {
|
||||
u := uint32(i + 64)
|
||||
u = (u << 1)
|
||||
*b = append(*b, uint8(u))
|
||||
return 1
|
||||
}
|
||||
if i := int32(f * 64); -128*64 <= i && i < +128*64 && float32(i) == f*64 {
|
||||
u := uint32(i + 128*64)
|
||||
u = (u << 2) | 1
|
||||
*b = append(*b, uint8(u), uint8(u>>8))
|
||||
return 2
|
||||
}
|
||||
b.encode4ByteReal(f)
|
||||
return 4
|
||||
}
|
||||
|
||||
// encodeCoordinateFFV1 is like encodeCoordinate but for File Format Version 1.
|
||||
// See https://github.com/google/iconvg/issues/33
|
||||
func (b *buffer) encodeCoordinateFFV1(f float32) int {
|
||||
if i := int32(f); -64 <= i && i < +64 && float32(i) == f {
|
||||
u := uint32(i + 64)
|
||||
u = (u << 1) | 0x01
|
||||
*b = append(*b, uint8(u))
|
||||
return 1
|
||||
}
|
||||
if i := int32(f * 64); -128*64 <= i && i < +128*64 && float32(i) == f*64 {
|
||||
u := uint32(i + 128*64)
|
||||
u = (u << 2) | 0x02
|
||||
*b = append(*b, uint8(u), uint8(u>>8))
|
||||
return 2
|
||||
}
|
||||
b.encode4ByteRealFFV1(f)
|
||||
return 4
|
||||
}
|
||||
|
||||
func (b *buffer) encodeCoordinatePairFFV1(f [2]float32) int {
|
||||
n0 := b.encodeCoordinateFFV1(f[0])
|
||||
n1 := b.encodeCoordinateFFV1(f[1])
|
||||
return n0 + n1
|
||||
}
|
||||
|
||||
func (b *buffer) encodeAngle(f float32) int {
|
||||
// Normalize f to the range [0, 1).
|
||||
g := float64(f)
|
||||
g -= math.Floor(g)
|
||||
return b.encodeZeroToOne(float32(g))
|
||||
}
|
||||
|
||||
func (b *buffer) encodeZeroToOne(f float32) int {
|
||||
if u := uint32(f * 15120); float32(u) == f*15120 && u < 15120 {
|
||||
if u%126 == 0 {
|
||||
u = ((u / 126) << 1)
|
||||
*b = append(*b, uint8(u))
|
||||
return 1
|
||||
}
|
||||
u = (u << 2) | 1
|
||||
*b = append(*b, uint8(u), uint8(u>>8))
|
||||
return 2
|
||||
}
|
||||
b.encode4ByteReal(f)
|
||||
return 4
|
||||
}
|
||||
|
||||
func (b *buffer) encodeColor1(c Color) {
|
||||
if x, ok := encodeColor1(c); ok {
|
||||
*b = append(*b, x)
|
||||
return
|
||||
}
|
||||
// Default to opaque black.
|
||||
*b = append(*b, 0x00)
|
||||
}
|
||||
|
||||
func (b *buffer) encodeColor2(c Color) {
|
||||
if x, ok := encodeColor2(c); ok {
|
||||
*b = append(*b, x[0], x[1])
|
||||
return
|
||||
}
|
||||
// Default to opaque black.
|
||||
*b = append(*b, 0x00, 0x0f)
|
||||
}
|
||||
|
||||
func (b *buffer) encodeColor3Direct(c Color) {
|
||||
if x, ok := encodeColor3Direct(c); ok {
|
||||
*b = append(*b, x[0], x[1], x[2])
|
||||
return
|
||||
}
|
||||
// Default to opaque black.
|
||||
*b = append(*b, 0x00, 0x00, 0x00)
|
||||
}
|
||||
|
||||
func (b *buffer) encodeColor4(c Color) {
|
||||
if x, ok := encodeColor4(c); ok {
|
||||
*b = append(*b, x[0], x[1], x[2], x[3])
|
||||
return
|
||||
}
|
||||
// Default to opaque black.
|
||||
*b = append(*b, 0x00, 0x00, 0x00, 0xff)
|
||||
}
|
||||
|
||||
func (b *buffer) encodeColor3Indirect(c Color) {
|
||||
if x, ok := encodeColor3Indirect(c); ok {
|
||||
*b = append(*b, x[0], x[1], x[2])
|
||||
return
|
||||
}
|
||||
// Default to opaque black.
|
||||
*b = append(*b, 0x00, 0x00, 0x00)
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package iconvg
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
)
|
||||
|
||||
func validAlphaPremulColor(c color.RGBA) bool {
|
||||
return c.R <= c.A && c.G <= c.A && c.B <= c.A
|
||||
}
|
||||
|
||||
// ColorType distinguishes types of Colors.
|
||||
type ColorType uint8
|
||||
|
||||
const (
|
||||
// ColorTypeRGBA is a direct RGBA color.
|
||||
ColorTypeRGBA ColorType = iota
|
||||
|
||||
// ColorTypePaletteIndex is an indirect color, indexing the custom palette.
|
||||
ColorTypePaletteIndex
|
||||
|
||||
// ColorTypeCReg is an indirect color, indexing the CREG color registers.
|
||||
ColorTypeCReg
|
||||
|
||||
// ColorTypeBlend is an indirect color, blending two other colors.
|
||||
ColorTypeBlend
|
||||
)
|
||||
|
||||
// Color is an IconVG color, whose RGBA values can depend on context. Some
|
||||
// Colors are direct RGBA values. Other Colors are indirect, referring to an
|
||||
// index of the custom palette, a color register of the decoder virtual
|
||||
// machine, or a blend of two other Colors.
|
||||
//
|
||||
// See the "Colors" section in the package documentation for details.
|
||||
type Color struct {
|
||||
typ ColorType
|
||||
data color.RGBA
|
||||
}
|
||||
|
||||
func (c Color) rgba() color.RGBA { return c.data }
|
||||
func (c Color) paletteIndex() uint8 { return c.data.R }
|
||||
func (c Color) cReg() uint8 { return c.data.R }
|
||||
func (c Color) blend() (t, c0, c1 uint8) { return c.data.R, c.data.G, c.data.B }
|
||||
|
||||
// Resolve resolves the Color's RGBA value, given its context: the custom
|
||||
// palette and the color registers of the decoder virtual machine.
|
||||
func (c Color) Resolve(pal *Palette, cReg *[64]color.RGBA) color.RGBA {
|
||||
switch c.typ {
|
||||
case ColorTypeRGBA:
|
||||
return c.rgba()
|
||||
case ColorTypePaletteIndex:
|
||||
return pal[c.paletteIndex()&0x3f]
|
||||
case ColorTypeCReg:
|
||||
return cReg[c.cReg()&0x3f]
|
||||
}
|
||||
t, c0, c1 := c.blend()
|
||||
p, q := uint32(255-t), uint32(t)
|
||||
rgba0 := decodeColor1(c0).Resolve(pal, cReg)
|
||||
rgba1 := decodeColor1(c1).Resolve(pal, cReg)
|
||||
return color.RGBA{
|
||||
uint8(((p * uint32(rgba0.R)) + q*uint32(rgba1.R) + 128) / 255),
|
||||
uint8(((p * uint32(rgba0.G)) + q*uint32(rgba1.G) + 128) / 255),
|
||||
uint8(((p * uint32(rgba0.B)) + q*uint32(rgba1.B) + 128) / 255),
|
||||
uint8(((p * uint32(rgba0.A)) + q*uint32(rgba1.A) + 128) / 255),
|
||||
}
|
||||
}
|
||||
|
||||
// RGBAColor returns a direct Color.
|
||||
func RGBAColor(c color.RGBA) Color { return Color{ColorTypeRGBA, c} }
|
||||
|
||||
// PaletteIndexColor returns an indirect Color referring to an index of the
|
||||
// custom palette.
|
||||
func PaletteIndexColor(i uint8) Color { return Color{ColorTypePaletteIndex, color.RGBA{R: i & 0x3f}} }
|
||||
|
||||
// CRegColor returns an indirect Color referring to a color register of the
|
||||
// decoder virtual machine.
|
||||
func CRegColor(i uint8) Color { return Color{ColorTypeCReg, color.RGBA{R: i & 0x3f}} }
|
||||
|
||||
// BlendColor returns an indirect Color that blends two other Colors. Those two
|
||||
// other Colors must both be encodable as a 1 byte color.
|
||||
//
|
||||
// To blend a Color that is not encodable as a 1 byte color, first load that
|
||||
// Color into a CREG color register, then call CRegColor to produce a Color
|
||||
// that is encodable as a 1 byte color. See testdata/favicon.ivg for an
|
||||
// example.
|
||||
//
|
||||
// See the "Colors" section in the package documentation for details.
|
||||
func BlendColor(t, c0, c1 uint8) Color { return Color{ColorTypeBlend, color.RGBA{R: t, G: c0, B: c1}} }
|
||||
|
||||
func decodeColor1(x byte) Color {
|
||||
if x >= 0x80 {
|
||||
if x >= 0xc0 {
|
||||
return CRegColor(x)
|
||||
} else {
|
||||
return PaletteIndexColor(x)
|
||||
}
|
||||
}
|
||||
if x >= 125 {
|
||||
switch x - 125 {
|
||||
case 0:
|
||||
return RGBAColor(color.RGBA{0xc0, 0xc0, 0xc0, 0xc0})
|
||||
case 1:
|
||||
return RGBAColor(color.RGBA{0x80, 0x80, 0x80, 0x80})
|
||||
case 2:
|
||||
return RGBAColor(color.RGBA{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
}
|
||||
blue := dc1Table[x%5]
|
||||
x = x / 5
|
||||
green := dc1Table[x%5]
|
||||
x = x / 5
|
||||
red := dc1Table[x]
|
||||
return RGBAColor(color.RGBA{red, green, blue, 0xff})
|
||||
}
|
||||
|
||||
var dc1Table = [5]byte{0x00, 0x40, 0x80, 0xc0, 0xff}
|
||||
|
||||
func is1(u uint8) bool { return u&0x3f == 0 || u == 0xff }
|
||||
|
||||
func encodeColor1(c Color) (x byte, ok bool) {
|
||||
switch c.typ {
|
||||
case ColorTypeRGBA:
|
||||
if c.data.A != 0xff {
|
||||
switch c.data {
|
||||
case color.RGBA{0x00, 0x00, 0x00, 0x00}:
|
||||
return 127, true
|
||||
case color.RGBA{0x80, 0x80, 0x80, 0x80}:
|
||||
return 126, true
|
||||
case color.RGBA{0xc0, 0xc0, 0xc0, 0xc0}:
|
||||
return 125, true
|
||||
}
|
||||
} else if is1(c.data.R) && is1(c.data.G) && is1(c.data.B) && is1(c.data.A) {
|
||||
r := c.data.R / 0x3f
|
||||
g := c.data.G / 0x3f
|
||||
b := c.data.B / 0x3f
|
||||
return 25*r + 5*g + b, true
|
||||
}
|
||||
case ColorTypePaletteIndex:
|
||||
return c.data.R | 0x80, true
|
||||
case ColorTypeCReg:
|
||||
return c.data.R | 0xc0, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func is2(u uint8) bool { return u%0x11 == 0 }
|
||||
|
||||
func encodeColor2(c Color) (x [2]byte, ok bool) {
|
||||
if c.typ == ColorTypeRGBA && is2(c.data.R) && is2(c.data.G) && is2(c.data.B) && is2(c.data.A) {
|
||||
return [2]byte{
|
||||
(c.data.R/0x11)<<4 | (c.data.G / 0x11),
|
||||
(c.data.B/0x11)<<4 | (c.data.A / 0x11),
|
||||
}, true
|
||||
}
|
||||
return [2]byte{}, false
|
||||
}
|
||||
|
||||
func encodeColor3Direct(c Color) (x [3]byte, ok bool) {
|
||||
if c.typ == ColorTypeRGBA && c.data.A == 0xff {
|
||||
return [3]byte{c.data.R, c.data.G, c.data.B}, true
|
||||
}
|
||||
return [3]byte{}, false
|
||||
}
|
||||
|
||||
func encodeColor4(c Color) (x [4]byte, ok bool) {
|
||||
if c.typ == ColorTypeRGBA {
|
||||
return [4]byte{c.data.R, c.data.G, c.data.B, c.data.A}, true
|
||||
}
|
||||
return [4]byte{}, false
|
||||
}
|
||||
|
||||
func encodeColor3Indirect(c Color) (x [3]byte, ok bool) {
|
||||
if c.typ == ColorTypeBlend {
|
||||
return [3]byte{c.data.R, c.data.G, c.data.B}, true
|
||||
}
|
||||
return [3]byte{}, false
|
||||
}
|
||||
+699
@@ -0,0 +1,699 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package iconvg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
var (
|
||||
errInconsistentMetadataChunkLength = errors.New("iconvg: inconsistent metadata chunk length")
|
||||
errInvalidColor = errors.New("iconvg: invalid color")
|
||||
errInvalidMagicIdentifier = errors.New("iconvg: invalid magic identifier")
|
||||
errInvalidMetadataChunkLength = errors.New("iconvg: invalid metadata chunk length")
|
||||
errInvalidMetadataIdentifier = errors.New("iconvg: invalid metadata identifier")
|
||||
errInvalidNumber = errors.New("iconvg: invalid number")
|
||||
errInvalidNumberOfMetadataChunks = errors.New("iconvg: invalid number of metadata chunks")
|
||||
errInvalidSuggestedPalette = errors.New("iconvg: invalid suggested palette")
|
||||
errInvalidViewBox = errors.New("iconvg: invalid view box")
|
||||
errUnsupportedDrawingOpcode = errors.New("iconvg: unsupported drawing opcode")
|
||||
errUnsupportedMetadataIdentifier = errors.New("iconvg: unsupported metadata identifier")
|
||||
errUnsupportedStylingOpcode = errors.New("iconvg: unsupported styling opcode")
|
||||
errUnsupportedUpgrade = errors.New("iconvg: unsupported upgrade")
|
||||
)
|
||||
|
||||
var midDescriptions = [...]string{
|
||||
midViewBox: "viewBox",
|
||||
midSuggestedPalette: "suggested palette",
|
||||
}
|
||||
|
||||
// Destination handles the actions decoded from an IconVG graphic's opcodes.
|
||||
//
|
||||
// When passed to Decode, the first method called (if any) will be Reset. No
|
||||
// methods will be called at all if an error is encountered in the encoded form
|
||||
// before the metadata is fully decoded.
|
||||
type Destination interface {
|
||||
Reset(m Metadata)
|
||||
|
||||
SetCSel(cSel uint8)
|
||||
SetNSel(nSel uint8)
|
||||
SetCReg(adj uint8, incr bool, c Color)
|
||||
SetNReg(adj uint8, incr bool, f float32)
|
||||
SetLOD(lod0, lod1 float32)
|
||||
|
||||
StartPath(adj uint8, x, y float32)
|
||||
ClosePathEndPath()
|
||||
ClosePathAbsMoveTo(x, y float32)
|
||||
ClosePathRelMoveTo(x, y float32)
|
||||
|
||||
AbsHLineTo(x float32)
|
||||
RelHLineTo(x float32)
|
||||
AbsVLineTo(y float32)
|
||||
RelVLineTo(y float32)
|
||||
AbsLineTo(x, y float32)
|
||||
RelLineTo(x, y float32)
|
||||
AbsSmoothQuadTo(x, y float32)
|
||||
RelSmoothQuadTo(x, y float32)
|
||||
AbsQuadTo(x1, y1, x, y float32)
|
||||
RelQuadTo(x1, y1, x, y float32)
|
||||
AbsSmoothCubeTo(x2, y2, x, y float32)
|
||||
RelSmoothCubeTo(x2, y2, x, y float32)
|
||||
AbsCubeTo(x1, y1, x2, y2, x, y float32)
|
||||
RelCubeTo(x1, y1, x2, y2, x, y float32)
|
||||
AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)
|
||||
RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)
|
||||
}
|
||||
|
||||
type printer func(b []byte, format string, args ...interface{})
|
||||
|
||||
// DecodeOptions are the optional parameters to the Decode function.
|
||||
type DecodeOptions struct {
|
||||
// Palette is an optional 64 color palette. If one isn't provided, the
|
||||
// IconVG graphic's suggested palette will be used.
|
||||
Palette *Palette
|
||||
}
|
||||
|
||||
// DecodeMetadata decodes only the metadata in an IconVG graphic.
|
||||
func DecodeMetadata(src []byte) (m Metadata, err error) {
|
||||
m.ViewBox = DefaultViewBox
|
||||
m.Palette = DefaultPalette
|
||||
if err = decode(nil, nil, &m, true, src, nil); err != nil {
|
||||
return Metadata{}, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Decode decodes an IconVG graphic.
|
||||
func Decode(dst Destination, src []byte, opts *DecodeOptions) error {
|
||||
m := Metadata{
|
||||
ViewBox: DefaultViewBox,
|
||||
Palette: DefaultPalette,
|
||||
}
|
||||
if opts != nil && opts.Palette != nil {
|
||||
m.Palette = *opts.Palette
|
||||
}
|
||||
return decode(dst, nil, &m, false, src, opts)
|
||||
}
|
||||
|
||||
func decode(dst Destination, p printer, m *Metadata, metadataOnly bool, src buffer, opts *DecodeOptions) (err error) {
|
||||
if !bytes.HasPrefix(src, magicBytes) {
|
||||
// TODO: detect FFV 1 (File Format Version 1), as opposed to the FFV 0
|
||||
// that this package implements, and delegate to a FFV 1 decoder.
|
||||
return errInvalidMagicIdentifier
|
||||
}
|
||||
if p != nil {
|
||||
p(src[:len(magic)], "IconVG Magic identifier\n")
|
||||
}
|
||||
src = src[len(magic):]
|
||||
|
||||
nMetadataChunks, n := src.decodeNatural()
|
||||
if n == 0 {
|
||||
return errInvalidNumberOfMetadataChunks
|
||||
}
|
||||
if p != nil {
|
||||
p(src[:n], "Number of metadata chunks: %d\n", nMetadataChunks)
|
||||
}
|
||||
src = src[n:]
|
||||
|
||||
for ; nMetadataChunks > 0; nMetadataChunks-- {
|
||||
src, err = decodeMetadataChunk(p, m, src, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if metadataOnly {
|
||||
return nil
|
||||
}
|
||||
if dst != nil {
|
||||
dst.Reset(*m)
|
||||
}
|
||||
|
||||
mf := modeFunc(decodeStyling)
|
||||
for len(src) > 0 {
|
||||
mf, src, err = mf(dst, p, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeMetadataChunk(p printer, m *Metadata, src buffer, opts *DecodeOptions) (src1 buffer, err error) {
|
||||
length, n := src.decodeNatural()
|
||||
if n == 0 {
|
||||
return nil, errInvalidMetadataChunkLength
|
||||
}
|
||||
if p != nil {
|
||||
p(src[:n], "Metadata chunk length: %d\n", length)
|
||||
}
|
||||
src = src[n:]
|
||||
lenSrcWant := int64(len(src)) - int64(length)
|
||||
|
||||
mid, n := src.decodeNatural()
|
||||
if n == 0 {
|
||||
return nil, errInvalidMetadataIdentifier
|
||||
}
|
||||
if mid >= uint32(len(midDescriptions)) {
|
||||
return nil, errUnsupportedMetadataIdentifier
|
||||
}
|
||||
if p != nil {
|
||||
p(src[:n], "Metadata Identifier: %d (%s)\n", mid, midDescriptions[mid])
|
||||
}
|
||||
src = src[n:]
|
||||
|
||||
switch mid {
|
||||
case midViewBox:
|
||||
if m.ViewBox.Min[0], src, err = decodeNumber(p, src, buffer.decodeCoordinate); err != nil {
|
||||
return nil, errInvalidViewBox
|
||||
}
|
||||
if m.ViewBox.Min[1], src, err = decodeNumber(p, src, buffer.decodeCoordinate); err != nil {
|
||||
return nil, errInvalidViewBox
|
||||
}
|
||||
if m.ViewBox.Max[0], src, err = decodeNumber(p, src, buffer.decodeCoordinate); err != nil {
|
||||
return nil, errInvalidViewBox
|
||||
}
|
||||
if m.ViewBox.Max[1], src, err = decodeNumber(p, src, buffer.decodeCoordinate); err != nil {
|
||||
return nil, errInvalidViewBox
|
||||
}
|
||||
if m.ViewBox.Min[0] > m.ViewBox.Max[0] || m.ViewBox.Min[1] > m.ViewBox.Max[1] ||
|
||||
isNaNOrInfinity(m.ViewBox.Min[0]) || isNaNOrInfinity(m.ViewBox.Min[1]) ||
|
||||
isNaNOrInfinity(m.ViewBox.Max[0]) || isNaNOrInfinity(m.ViewBox.Max[1]) {
|
||||
return nil, errInvalidViewBox
|
||||
}
|
||||
|
||||
case midSuggestedPalette:
|
||||
if len(src) == 0 {
|
||||
return nil, errInvalidSuggestedPalette
|
||||
}
|
||||
length, format := 1+int(src[0]&0x3f), src[0]>>6
|
||||
decode := buffer.decodeColor4
|
||||
switch format {
|
||||
case 0:
|
||||
decode = buffer.decodeColor1
|
||||
case 1:
|
||||
decode = buffer.decodeColor2
|
||||
case 2:
|
||||
decode = buffer.decodeColor3Direct
|
||||
}
|
||||
if p != nil {
|
||||
p(src[:1], " %d palette colors, %d bytes per color\n", length, 1+format)
|
||||
}
|
||||
src = src[1:]
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
c, n := decode(src)
|
||||
if n == 0 {
|
||||
return nil, errInvalidSuggestedPalette
|
||||
}
|
||||
rgba := c.rgba()
|
||||
if c.typ != ColorTypeRGBA || !validAlphaPremulColor(rgba) {
|
||||
rgba = color.RGBA{0x00, 0x00, 0x00, 0xff}
|
||||
}
|
||||
if p != nil {
|
||||
p(src[:n], " RGBA %02x%02x%02x%02x\n", rgba.R, rgba.G, rgba.B, rgba.A)
|
||||
}
|
||||
src = src[n:]
|
||||
if opts == nil || opts.Palette == nil {
|
||||
m.Palette[i] = rgba
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, errUnsupportedMetadataIdentifier
|
||||
}
|
||||
|
||||
if int64(len(src)) != lenSrcWant {
|
||||
return nil, errInconsistentMetadataChunkLength
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// modeFunc is the decoding mode: whether we are decoding styling or drawing
|
||||
// opcodes.
|
||||
//
|
||||
// It is a function type. The decoding loop calls this function to decode and
|
||||
// execute the next opcode from the src buffer, returning the subsequent mode
|
||||
// and the remaining source bytes.
|
||||
type modeFunc func(dst Destination, p printer, src buffer) (modeFunc, buffer, error)
|
||||
|
||||
func decodeStyling(dst Destination, p printer, src buffer) (modeFunc, buffer, error) {
|
||||
switch opcode := src[0]; {
|
||||
case opcode < 0x80:
|
||||
if opcode < 0x40 {
|
||||
opcode &= 0x3f
|
||||
if p != nil {
|
||||
p(src[:1], "Set CSEL = %d\n", opcode)
|
||||
}
|
||||
src = src[1:]
|
||||
if dst != nil {
|
||||
dst.SetCSel(opcode)
|
||||
}
|
||||
} else {
|
||||
opcode &= 0x3f
|
||||
if p != nil {
|
||||
p(src[:1], "Set NSEL = %d\n", opcode)
|
||||
}
|
||||
src = src[1:]
|
||||
if dst != nil {
|
||||
dst.SetNSel(opcode)
|
||||
}
|
||||
}
|
||||
return decodeStyling, src, nil
|
||||
case opcode < 0xa8:
|
||||
return decodeSetCReg(dst, p, src, opcode)
|
||||
case opcode < 0xc0:
|
||||
return decodeSetNReg(dst, p, src, opcode)
|
||||
case opcode < 0xc7:
|
||||
return decodeStartPath(dst, p, src, opcode)
|
||||
case opcode == 0xc7:
|
||||
return decodeSetLOD(dst, p, src)
|
||||
}
|
||||
return nil, nil, errUnsupportedStylingOpcode
|
||||
}
|
||||
|
||||
func decodeSetCReg(dst Destination, p printer, src buffer, opcode byte) (modeFunc, buffer, error) {
|
||||
nBytes, directness, adj := 0, "", opcode&0x07
|
||||
var decode func(buffer) (Color, int)
|
||||
incr := adj == 7
|
||||
if incr {
|
||||
adj = 0
|
||||
}
|
||||
|
||||
switch (opcode - 0x80) >> 3 {
|
||||
case 0:
|
||||
nBytes, directness, decode = 1, "", buffer.decodeColor1
|
||||
case 1:
|
||||
nBytes, directness, decode = 2, "", buffer.decodeColor2
|
||||
case 2:
|
||||
nBytes, directness, decode = 3, " (direct)", buffer.decodeColor3Direct
|
||||
case 3:
|
||||
nBytes, directness, decode = 4, "", buffer.decodeColor4
|
||||
case 4:
|
||||
nBytes, directness, decode = 3, " (indirect)", buffer.decodeColor3Indirect
|
||||
}
|
||||
if p != nil {
|
||||
if incr {
|
||||
p(src[:1], "Set CREG[CSEL-0] to a %d byte%s color; CSEL++\n", nBytes, directness)
|
||||
} else {
|
||||
p(src[:1], "Set CREG[CSEL-%d] to a %d byte%s color\n", adj, nBytes, directness)
|
||||
}
|
||||
}
|
||||
src = src[1:]
|
||||
|
||||
c, n := decode(src)
|
||||
if n == 0 {
|
||||
return nil, nil, errInvalidColor
|
||||
}
|
||||
|
||||
if p != nil {
|
||||
printColor(src[:n], p, c, "")
|
||||
}
|
||||
src = src[n:]
|
||||
|
||||
if dst != nil {
|
||||
dst.SetCReg(adj, incr, c)
|
||||
}
|
||||
|
||||
return decodeStyling, src, nil
|
||||
}
|
||||
|
||||
func printColor(src []byte, p printer, c Color, prefix string) {
|
||||
switch c.typ {
|
||||
case ColorTypeRGBA:
|
||||
if rgba := c.rgba(); validAlphaPremulColor(rgba) {
|
||||
p(src, " %sRGBA %02x%02x%02x%02x\n", prefix, rgba.R, rgba.G, rgba.B, rgba.A)
|
||||
} else if rgba.A == 0 && rgba.B&0x80 != 0 {
|
||||
p(src, " %sgradient (NSTOPS=%d, CBASE=%d, NBASE=%d, %s, %s)\n",
|
||||
prefix,
|
||||
rgba.R&0x3f,
|
||||
rgba.G&0x3f,
|
||||
rgba.B&0x3f,
|
||||
gradientShapeNames[(rgba.B>>6)&0x01],
|
||||
gradientSpreadNames[rgba.G>>6],
|
||||
)
|
||||
} else {
|
||||
p(src, " %snonsensical color\n", prefix)
|
||||
}
|
||||
case ColorTypePaletteIndex:
|
||||
p(src, " %scustomPalette[%d]\n", prefix, c.paletteIndex())
|
||||
case ColorTypeCReg:
|
||||
p(src, " %sCREG[%d]\n", prefix, c.cReg())
|
||||
case ColorTypeBlend:
|
||||
t, c0, c1 := c.blend()
|
||||
p(src[:1], " blend %d:%d c0:c1\n", 0xff-t, t)
|
||||
printColor(src[1:2], p, decodeColor1(c0), " c0: ")
|
||||
printColor(src[2:3], p, decodeColor1(c1), " c1: ")
|
||||
}
|
||||
}
|
||||
|
||||
func decodeSetNReg(dst Destination, p printer, src buffer, opcode byte) (modeFunc, buffer, error) {
|
||||
decode, typ, adj := buffer.decodeZeroToOne, "zero-to-one", opcode&0x07
|
||||
incr := adj == 7
|
||||
if incr {
|
||||
adj = 0
|
||||
}
|
||||
|
||||
switch (opcode - 0xa8) >> 3 {
|
||||
case 0:
|
||||
decode, typ = buffer.decodeReal, "real"
|
||||
case 1:
|
||||
decode, typ = buffer.decodeCoordinate, "coordinate"
|
||||
}
|
||||
if p != nil {
|
||||
if incr {
|
||||
p(src[:1], "Set NREG[NSEL-0] to a %s number; NSEL++\n", typ)
|
||||
} else {
|
||||
p(src[:1], "Set NREG[NSEL-%d] to a %s number\n", adj, typ)
|
||||
}
|
||||
}
|
||||
src = src[1:]
|
||||
|
||||
f, n := decode(src)
|
||||
if n == 0 {
|
||||
return nil, nil, errInvalidNumber
|
||||
}
|
||||
if p != nil {
|
||||
p(src[:n], " %g\n", f)
|
||||
}
|
||||
src = src[n:]
|
||||
|
||||
if dst != nil {
|
||||
dst.SetNReg(adj, incr, f)
|
||||
}
|
||||
|
||||
return decodeStyling, src, nil
|
||||
}
|
||||
|
||||
func decodeStartPath(dst Destination, p printer, src buffer, opcode byte) (modeFunc, buffer, error) {
|
||||
adj := opcode & 0x07
|
||||
if p != nil {
|
||||
p(src[:1], "Start path, filled with CREG[CSEL-%d]; M (absolute moveTo)\n", adj)
|
||||
}
|
||||
src = src[1:]
|
||||
|
||||
x, src, err := decodeNumber(p, src, buffer.decodeCoordinate)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
y, src, err := decodeNumber(p, src, buffer.decodeCoordinate)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if dst != nil {
|
||||
dst.StartPath(adj, x, y)
|
||||
}
|
||||
|
||||
return decodeDrawing, src, nil
|
||||
}
|
||||
|
||||
func decodeSetLOD(dst Destination, p printer, src buffer) (modeFunc, buffer, error) {
|
||||
if p != nil {
|
||||
p(src[:1], "Set LOD\n")
|
||||
}
|
||||
src = src[1:]
|
||||
|
||||
lod0, src, err := decodeNumber(p, src, buffer.decodeReal)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
lod1, src, err := decodeNumber(p, src, buffer.decodeReal)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if dst != nil {
|
||||
dst.SetLOD(lod0, lod1)
|
||||
}
|
||||
return decodeStyling, src, nil
|
||||
}
|
||||
|
||||
func decodeDrawing(dst Destination, p printer, src buffer) (mf modeFunc, src1 buffer, err error) {
|
||||
var coords [6]float32
|
||||
|
||||
switch opcode := src[0]; {
|
||||
case opcode < 0xe0:
|
||||
op, nCoords, nReps := "", 0, 1+int(opcode&0x0f)
|
||||
switch opcode >> 4 {
|
||||
case 0x00, 0x01:
|
||||
op = "L (absolute lineTo)"
|
||||
nCoords = 2
|
||||
nReps = 1 + int(opcode&0x1f)
|
||||
case 0x02, 0x03:
|
||||
op = "l (relative lineTo)"
|
||||
nCoords = 2
|
||||
nReps = 1 + int(opcode&0x1f)
|
||||
case 0x04:
|
||||
op = "T (absolute smooth quadTo)"
|
||||
nCoords = 2
|
||||
case 0x05:
|
||||
op = "t (relative smooth quadTo)"
|
||||
nCoords = 2
|
||||
case 0x06:
|
||||
op = "Q (absolute quadTo)"
|
||||
nCoords = 4
|
||||
case 0x07:
|
||||
op = "q (relative quadTo)"
|
||||
nCoords = 4
|
||||
case 0x08:
|
||||
op = "S (absolute smooth cubeTo)"
|
||||
nCoords = 4
|
||||
case 0x09:
|
||||
op = "s (relative smooth cubeTo)"
|
||||
nCoords = 4
|
||||
case 0x0a:
|
||||
op = "C (absolute cubeTo)"
|
||||
nCoords = 6
|
||||
case 0x0b:
|
||||
op = "c (relative cubeTo)"
|
||||
nCoords = 6
|
||||
case 0x0c:
|
||||
op = "A (absolute arcTo)"
|
||||
nCoords = 0
|
||||
case 0x0d:
|
||||
op = "a (relative arcTo)"
|
||||
nCoords = 0
|
||||
}
|
||||
|
||||
if p != nil {
|
||||
p(src[:1], "%s, %d reps\n", op, nReps)
|
||||
}
|
||||
src = src[1:]
|
||||
|
||||
for i := 0; i < nReps; i++ {
|
||||
if p != nil && i != 0 {
|
||||
p(nil, "%s, implicit\n", op)
|
||||
}
|
||||
var largeArc, sweep bool
|
||||
if op[0] != 'A' && op[0] != 'a' {
|
||||
src, err = decodeCoordinates(coords[:nCoords], p, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
// We have an absolute or relative arcTo.
|
||||
src, err = decodeCoordinates(coords[:2], p, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
coords[2], src, err = decodeAngle(p, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
largeArc, sweep, src, err = decodeArcToFlags(p, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
src, err = decodeCoordinates(coords[4:6], p, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if dst == nil {
|
||||
continue
|
||||
}
|
||||
switch op[0] {
|
||||
case 'L':
|
||||
dst.AbsLineTo(coords[0], coords[1])
|
||||
case 'l':
|
||||
dst.RelLineTo(coords[0], coords[1])
|
||||
case 'T':
|
||||
dst.AbsSmoothQuadTo(coords[0], coords[1])
|
||||
case 't':
|
||||
dst.RelSmoothQuadTo(coords[0], coords[1])
|
||||
case 'Q':
|
||||
dst.AbsQuadTo(coords[0], coords[1], coords[2], coords[3])
|
||||
case 'q':
|
||||
dst.RelQuadTo(coords[0], coords[1], coords[2], coords[3])
|
||||
case 'S':
|
||||
dst.AbsSmoothCubeTo(coords[0], coords[1], coords[2], coords[3])
|
||||
case 's':
|
||||
dst.RelSmoothCubeTo(coords[0], coords[1], coords[2], coords[3])
|
||||
case 'C':
|
||||
dst.AbsCubeTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5])
|
||||
case 'c':
|
||||
dst.RelCubeTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5])
|
||||
case 'A':
|
||||
dst.AbsArcTo(coords[0], coords[1], coords[2], largeArc, sweep, coords[4], coords[5])
|
||||
case 'a':
|
||||
dst.RelArcTo(coords[0], coords[1], coords[2], largeArc, sweep, coords[4], coords[5])
|
||||
}
|
||||
}
|
||||
|
||||
case opcode == 0xe1:
|
||||
if p != nil {
|
||||
p(src[:1], "z (closePath); end path\n")
|
||||
}
|
||||
src = src[1:]
|
||||
if dst != nil {
|
||||
dst.ClosePathEndPath()
|
||||
}
|
||||
return decodeStyling, src, nil
|
||||
|
||||
case opcode == 0xe2:
|
||||
if p != nil {
|
||||
p(src[:1], "z (closePath); M (absolute moveTo)\n")
|
||||
}
|
||||
src = src[1:]
|
||||
src, err = decodeCoordinates(coords[:2], p, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if dst != nil {
|
||||
dst.ClosePathAbsMoveTo(coords[0], coords[1])
|
||||
}
|
||||
|
||||
case opcode == 0xe3:
|
||||
if p != nil {
|
||||
p(src[:1], "z (closePath); m (relative moveTo)\n")
|
||||
}
|
||||
src = src[1:]
|
||||
src, err = decodeCoordinates(coords[:2], p, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if dst != nil {
|
||||
dst.ClosePathRelMoveTo(coords[0], coords[1])
|
||||
}
|
||||
|
||||
case opcode == 0xe6:
|
||||
if p != nil {
|
||||
p(src[:1], "H (absolute horizontal lineTo)\n")
|
||||
}
|
||||
src = src[1:]
|
||||
src, err = decodeCoordinates(coords[:1], p, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if dst != nil {
|
||||
dst.AbsHLineTo(coords[0])
|
||||
}
|
||||
|
||||
case opcode == 0xe7:
|
||||
if p != nil {
|
||||
p(src[:1], "h (relative horizontal lineTo)\n")
|
||||
}
|
||||
src = src[1:]
|
||||
src, err = decodeCoordinates(coords[:1], p, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if dst != nil {
|
||||
dst.RelHLineTo(coords[0])
|
||||
}
|
||||
|
||||
case opcode == 0xe8:
|
||||
if p != nil {
|
||||
p(src[:1], "V (absolute vertical lineTo)\n")
|
||||
}
|
||||
src = src[1:]
|
||||
src, err = decodeCoordinates(coords[:1], p, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if dst != nil {
|
||||
dst.AbsVLineTo(coords[0])
|
||||
}
|
||||
|
||||
case opcode == 0xe9:
|
||||
if p != nil {
|
||||
p(src[:1], "v (relative vertical lineTo)\n")
|
||||
}
|
||||
src = src[1:]
|
||||
src, err = decodeCoordinates(coords[:1], p, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if dst != nil {
|
||||
dst.RelVLineTo(coords[0])
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, nil, errUnsupportedDrawingOpcode
|
||||
}
|
||||
return decodeDrawing, src, nil
|
||||
}
|
||||
|
||||
type decodeNumberFunc func(buffer) (float32, int)
|
||||
|
||||
func decodeNumber(p printer, src buffer, dnf decodeNumberFunc) (float32, buffer, error) {
|
||||
x, n := dnf(src)
|
||||
if n == 0 {
|
||||
return 0, nil, errInvalidNumber
|
||||
}
|
||||
if p != nil {
|
||||
p(src[:n], " %+g\n", x)
|
||||
}
|
||||
return x, src[n:], nil
|
||||
}
|
||||
|
||||
func decodeCoordinates(coords []float32, p printer, src buffer) (src1 buffer, err error) {
|
||||
for i := range coords {
|
||||
coords[i], src, err = decodeNumber(p, src, buffer.decodeCoordinate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func decodeCoordinatePairs(coords [][2]float32, p printer, src buffer) (src1 buffer, err error) {
|
||||
for i := range coords {
|
||||
coords[i][0], src, err = decodeNumber(p, src, buffer.decodeCoordinate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
coords[i][1], src, err = decodeNumber(p, src, buffer.decodeCoordinate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func decodeAngle(p printer, src buffer) (float32, buffer, error) {
|
||||
x, n := src.decodeZeroToOne()
|
||||
if n == 0 {
|
||||
return 0, nil, errInvalidNumber
|
||||
}
|
||||
if p != nil {
|
||||
p(src[:n], " %v × 360 degrees (%v degrees)\n", x, x*360)
|
||||
}
|
||||
return x, src[n:], nil
|
||||
}
|
||||
|
||||
func decodeArcToFlags(p printer, src buffer) (bool, bool, buffer, error) {
|
||||
x, n := src.decodeNatural()
|
||||
if n == 0 {
|
||||
return false, false, nil, errInvalidNumber
|
||||
}
|
||||
if p != nil {
|
||||
p(src[:n], " %#x (largeArc=%d, sweep=%d)\n", x, (x>>0)&0x01, (x>>1)&0x01)
|
||||
}
|
||||
return (x>>0)&0x01 != 0, (x>>1)&0x01 != 0, src[n:], nil
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package iconvg implements a compact, binary format for simple vector graphics:
|
||||
icons, logos, glyphs and emoji.
|
||||
|
||||
WARNING: THIS FORMAT IS EXPERIMENTAL AND SUBJECT TO INCOMPATIBLE CHANGES.
|
||||
|
||||
A longer overview is at
|
||||
https://github.com/google/iconvg
|
||||
|
||||
The file format is specified at
|
||||
https://github.com/google/iconvg/blob/main/spec/iconvg-spec.md
|
||||
|
||||
This package's encoder emits byte-identical output for the same input,
|
||||
independent of the platform (and specifically its floating-point hardware).
|
||||
*/
|
||||
package iconvg
|
||||
|
||||
// TODO: shapes (circles, rects) and strokes? Or can we assume that authoring
|
||||
// tools will convert shapes and strokes to paths?
|
||||
|
||||
// TODO: mark somehow that a graphic (such as a back arrow) should be flipped
|
||||
// horizontally or its paths otherwise varied when presented in a Right-To-Left
|
||||
// context, such as among Arabic and Hebrew text? Or should that be the
|
||||
// responsibility of higher layers, selecting different IconVG graphics based
|
||||
// on context, the way they would select different PNG graphics.
|
||||
|
||||
// TODO: hinting?
|
||||
+605
@@ -0,0 +1,605 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package iconvg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"golang.org/x/image/math/f32"
|
||||
)
|
||||
|
||||
var (
|
||||
errCSELUsedAsBothGradientAndStop = errors.New("iconvg: CSEL used as both gradient and stop")
|
||||
errDrawingOpsUsedInStylingMode = errors.New("iconvg: drawing ops used in styling mode")
|
||||
errInvalidSelectorAdjustment = errors.New("iconvg: invalid selector adjustment")
|
||||
errInvalidIncrementingAdjustment = errors.New("iconvg: invalid incrementing adjustment")
|
||||
errStylingOpsUsedInDrawingMode = errors.New("iconvg: styling ops used in drawing mode")
|
||||
errTooManyGradientStops = errors.New("iconvg: too many gradient stops")
|
||||
)
|
||||
|
||||
type mode uint8
|
||||
|
||||
const (
|
||||
modeInitial mode = iota
|
||||
modeStyling
|
||||
modeDrawing
|
||||
)
|
||||
|
||||
// Encoder is an IconVG encoder.
|
||||
//
|
||||
// The zero value is usable. Calling Reset, which is optional, sets the
|
||||
// Metadata for the subsequent encoded form. If Reset is not called before
|
||||
// other Encoder methods, the default metadata is implied.
|
||||
//
|
||||
// It aims to emit byte-identical Bytes output for the same input, independent
|
||||
// of the platform (and specifically its floating-point hardware).
|
||||
type Encoder struct {
|
||||
// HighResolutionCoordinates is whether the encoder should encode
|
||||
// coordinate numbers for subsequent paths at the best possible resolution
|
||||
// afforded by the underlying graphic format.
|
||||
//
|
||||
// By default (false), the encoder quantizes coordinates to 1/64th of a
|
||||
// unit if possible (the default graphic size is 64 by 64 units, so
|
||||
// 1/4096th of the default width or height). Each such coordinate can
|
||||
// therefore be encoded in either 1 or 2 bytes. If true, some coordinates
|
||||
// will be encoded in 4 bytes, giving greater accuracy but larger file
|
||||
// sizes. On the Material Design icon set, the 950 or so icons take up
|
||||
// around 40% more bytes (172K vs 123K) at high resolution.
|
||||
//
|
||||
// See the package documentation for more details on the coordinate number
|
||||
// encoding format.
|
||||
HighResolutionCoordinates bool
|
||||
|
||||
// highResolutionCoordinates is a local copy, copied during StartPath, to
|
||||
// avoid having to specify the semantics of modifying the exported field
|
||||
// while drawing.
|
||||
highResolutionCoordinates bool
|
||||
|
||||
buf buffer
|
||||
altBuf buffer
|
||||
metadata Metadata
|
||||
err error
|
||||
|
||||
lod0 float32
|
||||
lod1 float32
|
||||
cSel uint8
|
||||
nSel uint8
|
||||
|
||||
mode mode
|
||||
drawOp byte
|
||||
drawArgs []float32
|
||||
|
||||
scratch [12]byte
|
||||
}
|
||||
|
||||
// Bytes returns the encoded form.
|
||||
func (e *Encoder) Bytes() ([]byte, error) {
|
||||
if e.err != nil {
|
||||
return nil, e.err
|
||||
}
|
||||
if e.mode == modeInitial {
|
||||
e.appendDefaultMetadata()
|
||||
}
|
||||
return []byte(e.buf), nil
|
||||
}
|
||||
|
||||
// Reset resets the Encoder for the given Metadata.
|
||||
//
|
||||
// This includes setting e.HighResolutionCoordinates to false.
|
||||
func (e *Encoder) Reset(m Metadata) {
|
||||
*e = Encoder{
|
||||
buf: append(e.buf[:0], magic...),
|
||||
metadata: m,
|
||||
mode: modeStyling,
|
||||
lod1: positiveInfinity,
|
||||
}
|
||||
|
||||
nMetadataChunks := 0
|
||||
mcViewBox := m.ViewBox != DefaultViewBox
|
||||
if mcViewBox {
|
||||
nMetadataChunks++
|
||||
}
|
||||
mcSuggestedPalette := m.Palette != DefaultPalette
|
||||
if mcSuggestedPalette {
|
||||
nMetadataChunks++
|
||||
}
|
||||
e.buf.encodeNatural(uint32(nMetadataChunks))
|
||||
|
||||
if mcViewBox {
|
||||
e.altBuf = e.altBuf[:0]
|
||||
e.altBuf.encodeNatural(midViewBox)
|
||||
e.altBuf.encodeCoordinate(m.ViewBox.Min[0])
|
||||
e.altBuf.encodeCoordinate(m.ViewBox.Min[1])
|
||||
e.altBuf.encodeCoordinate(m.ViewBox.Max[0])
|
||||
e.altBuf.encodeCoordinate(m.ViewBox.Max[1])
|
||||
|
||||
e.buf.encodeNatural(uint32(len(e.altBuf)))
|
||||
e.buf = append(e.buf, e.altBuf...)
|
||||
}
|
||||
|
||||
if mcSuggestedPalette {
|
||||
n := 63
|
||||
for ; n >= 0 && m.Palette[n] == (color.RGBA{0x00, 0x00, 0x00, 0xff}); n-- {
|
||||
}
|
||||
|
||||
// Find the shortest encoding that can represent all of m.Palette's n+1
|
||||
// explicit colors.
|
||||
enc1, enc2, enc3 := true, true, true
|
||||
for _, c := range m.Palette[:n+1] {
|
||||
if enc1 && (!is1(c.R) || !is1(c.G) || !is1(c.B) || !is1(c.A)) {
|
||||
enc1 = false
|
||||
}
|
||||
if enc2 && (!is2(c.R) || !is2(c.G) || !is2(c.B) || !is2(c.A)) {
|
||||
enc2 = false
|
||||
}
|
||||
if enc3 && (c.A != 0xff) {
|
||||
enc3 = false
|
||||
}
|
||||
}
|
||||
|
||||
e.altBuf = e.altBuf[:0]
|
||||
e.altBuf.encodeNatural(midSuggestedPalette)
|
||||
if enc1 {
|
||||
e.altBuf = append(e.altBuf, byte(n)|0x00)
|
||||
for _, c := range m.Palette[:n+1] {
|
||||
x, _ := encodeColor1(RGBAColor(c))
|
||||
e.altBuf = append(e.altBuf, x)
|
||||
}
|
||||
} else if enc2 {
|
||||
e.altBuf = append(e.altBuf, byte(n)|0x40)
|
||||
for _, c := range m.Palette[:n+1] {
|
||||
x, _ := encodeColor2(RGBAColor(c))
|
||||
e.altBuf = append(e.altBuf, x[0], x[1])
|
||||
}
|
||||
} else if enc3 {
|
||||
e.altBuf = append(e.altBuf, byte(n)|0x80)
|
||||
for _, c := range m.Palette[:n+1] {
|
||||
e.altBuf = append(e.altBuf, c.R, c.G, c.B)
|
||||
}
|
||||
} else {
|
||||
e.altBuf = append(e.altBuf, byte(n)|0xc0)
|
||||
for _, c := range m.Palette[:n+1] {
|
||||
e.altBuf = append(e.altBuf, c.R, c.G, c.B, c.A)
|
||||
}
|
||||
}
|
||||
|
||||
e.buf.encodeNatural(uint32(len(e.altBuf)))
|
||||
e.buf = append(e.buf, e.altBuf...)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Encoder) appendDefaultMetadata() {
|
||||
e.buf = append(e.buf[:0], magic...)
|
||||
e.buf = append(e.buf, 0x00) // There are zero metadata chunks.
|
||||
e.mode = modeStyling
|
||||
}
|
||||
|
||||
func (e *Encoder) CSel() uint8 {
|
||||
if e.mode == modeInitial {
|
||||
e.appendDefaultMetadata()
|
||||
}
|
||||
return e.cSel
|
||||
}
|
||||
|
||||
func (e *Encoder) NSel() uint8 {
|
||||
if e.mode == modeInitial {
|
||||
e.appendDefaultMetadata()
|
||||
}
|
||||
return e.nSel
|
||||
}
|
||||
|
||||
func (e *Encoder) LOD() (lod0, lod1 float32) {
|
||||
if e.mode == modeInitial {
|
||||
e.appendDefaultMetadata()
|
||||
}
|
||||
return e.lod0, e.lod1
|
||||
}
|
||||
|
||||
func (e *Encoder) checkModeStyling() {
|
||||
if e.mode == modeStyling {
|
||||
return
|
||||
}
|
||||
if e.mode == modeInitial {
|
||||
e.appendDefaultMetadata()
|
||||
return
|
||||
}
|
||||
e.err = errStylingOpsUsedInDrawingMode
|
||||
}
|
||||
|
||||
func (e *Encoder) SetCSel(cSel uint8) {
|
||||
e.checkModeStyling()
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
e.cSel = cSel & 0x3f
|
||||
e.buf = append(e.buf, e.cSel)
|
||||
}
|
||||
|
||||
func (e *Encoder) SetNSel(nSel uint8) {
|
||||
e.checkModeStyling()
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
e.nSel = nSel & 0x3f
|
||||
e.buf = append(e.buf, e.nSel|0x40)
|
||||
}
|
||||
|
||||
func (e *Encoder) SetCReg(adj uint8, incr bool, c Color) {
|
||||
e.checkModeStyling()
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
if adj > 6 {
|
||||
e.err = errInvalidSelectorAdjustment
|
||||
return
|
||||
}
|
||||
if incr {
|
||||
if adj != 0 {
|
||||
e.err = errInvalidIncrementingAdjustment
|
||||
}
|
||||
adj = 7
|
||||
}
|
||||
|
||||
if x, ok := encodeColor1(c); ok {
|
||||
e.buf = append(e.buf, adj|0x80, x)
|
||||
return
|
||||
}
|
||||
if x, ok := encodeColor2(c); ok {
|
||||
e.buf = append(e.buf, adj|0x88, x[0], x[1])
|
||||
return
|
||||
}
|
||||
if x, ok := encodeColor3Direct(c); ok {
|
||||
e.buf = append(e.buf, adj|0x90, x[0], x[1], x[2])
|
||||
return
|
||||
}
|
||||
if x, ok := encodeColor4(c); ok {
|
||||
e.buf = append(e.buf, adj|0x98, x[0], x[1], x[2], x[3])
|
||||
return
|
||||
}
|
||||
if x, ok := encodeColor3Indirect(c); ok {
|
||||
e.buf = append(e.buf, adj|0xa0, x[0], x[1], x[2])
|
||||
return
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (e *Encoder) SetNReg(adj uint8, incr bool, f float32) {
|
||||
e.checkModeStyling()
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
if adj > 6 {
|
||||
e.err = errInvalidSelectorAdjustment
|
||||
return
|
||||
}
|
||||
if incr {
|
||||
if adj != 0 {
|
||||
e.err = errInvalidIncrementingAdjustment
|
||||
}
|
||||
adj = 7
|
||||
}
|
||||
|
||||
// Try three different encodings and pick the shortest.
|
||||
b := buffer(e.scratch[0:0])
|
||||
opcode, iBest, nBest := uint8(0xa8), 0, b.encodeReal(f)
|
||||
|
||||
b = buffer(e.scratch[4:4])
|
||||
if n := b.encodeCoordinate(f); n < nBest {
|
||||
opcode, iBest, nBest = 0xb0, 4, n
|
||||
}
|
||||
|
||||
b = buffer(e.scratch[8:8])
|
||||
if n := b.encodeZeroToOne(f); n < nBest {
|
||||
opcode, iBest, nBest = 0xb8, 8, n
|
||||
}
|
||||
|
||||
e.buf = append(e.buf, adj|opcode)
|
||||
e.buf = append(e.buf, e.scratch[iBest:iBest+nBest]...)
|
||||
}
|
||||
|
||||
func (e *Encoder) SetLOD(lod0, lod1 float32) {
|
||||
e.checkModeStyling()
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
e.lod0 = lod0
|
||||
e.lod1 = lod1
|
||||
e.buf = append(e.buf, 0xc7)
|
||||
e.buf.encodeReal(lod0)
|
||||
e.buf.encodeReal(lod1)
|
||||
}
|
||||
|
||||
// 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].
|
||||
//
|
||||
// The CSEL and NSEL selector registers maintain the same values after the
|
||||
// method returns as they had when the method was called.
|
||||
//
|
||||
// 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) > 64-len(transform) {
|
||||
e.err = errTooManyGradientStops
|
||||
return
|
||||
}
|
||||
if x, y := e.cSel, e.cSel+64; (cBase <= x && x < cBase+uint8(len(stops))) ||
|
||||
(cBase <= y && y < cBase+uint8(len(stops))) {
|
||||
e.err = errCSELUsedAsBothGradientAndStop
|
||||
return
|
||||
}
|
||||
|
||||
oldCSel := e.cSel
|
||||
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),
|
||||
B: nBase | bFlags,
|
||||
A: 0x00,
|
||||
}))
|
||||
e.SetCSel(cBase)
|
||||
e.SetNSel(nBase)
|
||||
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{
|
||||
R: uint8(r >> 8),
|
||||
G: uint8(g >> 8),
|
||||
B: uint8(b >> 8),
|
||||
A: uint8(a >> 8),
|
||||
}))
|
||||
e.SetNReg(0, true, s.Offset)
|
||||
}
|
||||
e.SetCSel(oldCSel)
|
||||
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) {
|
||||
// Explicitly disable FMA in the floating-point calculations below
|
||||
// to get consistent results on all platforms, and in turn produce
|
||||
// a byte-identical encoding.
|
||||
// See https://golang.org/ref/spec#Floating_point_operators and issue 43219.
|
||||
|
||||
// See the package documentation's appendix for a derivation of the
|
||||
// transformation matrix.
|
||||
invRSSR := 1 / (float32(rx*sy) - float32(sx*ry))
|
||||
|
||||
ma := +sy * invRSSR
|
||||
mb := -sx * invRSSR
|
||||
mc := -float32(ma*cx) - float32(mb*cy)
|
||||
md := -ry * invRSSR
|
||||
me := +rx * invRSSR
|
||||
mf := -float32(md*cx) - float32(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 {
|
||||
return
|
||||
}
|
||||
if adj > 6 {
|
||||
e.err = errInvalidSelectorAdjustment
|
||||
return
|
||||
}
|
||||
e.highResolutionCoordinates = e.HighResolutionCoordinates
|
||||
e.buf = append(e.buf, uint8(0xc0+adj))
|
||||
e.buf.encodeCoordinate(quantize(x, e.highResolutionCoordinates))
|
||||
e.buf.encodeCoordinate(quantize(y, e.highResolutionCoordinates))
|
||||
e.mode = modeDrawing
|
||||
}
|
||||
|
||||
func (e *Encoder) AbsHLineTo(x float32) { e.draw('H', x, 0, 0, 0, 0, 0) }
|
||||
func (e *Encoder) RelHLineTo(x float32) { e.draw('h', x, 0, 0, 0, 0, 0) }
|
||||
func (e *Encoder) AbsVLineTo(y float32) { e.draw('V', y, 0, 0, 0, 0, 0) }
|
||||
func (e *Encoder) RelVLineTo(y float32) { e.draw('v', y, 0, 0, 0, 0, 0) }
|
||||
func (e *Encoder) AbsLineTo(x, y float32) { e.draw('L', x, y, 0, 0, 0, 0) }
|
||||
func (e *Encoder) RelLineTo(x, y float32) { e.draw('l', x, y, 0, 0, 0, 0) }
|
||||
func (e *Encoder) AbsSmoothQuadTo(x, y float32) { e.draw('T', x, y, 0, 0, 0, 0) }
|
||||
func (e *Encoder) RelSmoothQuadTo(x, y float32) { e.draw('t', x, y, 0, 0, 0, 0) }
|
||||
func (e *Encoder) AbsQuadTo(x1, y1, x, y float32) { e.draw('Q', x1, y1, x, y, 0, 0) }
|
||||
func (e *Encoder) RelQuadTo(x1, y1, x, y float32) { e.draw('q', x1, y1, x, y, 0, 0) }
|
||||
func (e *Encoder) AbsSmoothCubeTo(x2, y2, x, y float32) { e.draw('S', x2, y2, x, y, 0, 0) }
|
||||
func (e *Encoder) RelSmoothCubeTo(x2, y2, x, y float32) { e.draw('s', x2, y2, x, y, 0, 0) }
|
||||
func (e *Encoder) AbsCubeTo(x1, y1, x2, y2, x, y float32) { e.draw('C', x1, y1, x2, y2, x, y) }
|
||||
func (e *Encoder) RelCubeTo(x1, y1, x2, y2, x, y float32) { e.draw('c', x1, y1, x2, y2, x, y) }
|
||||
func (e *Encoder) ClosePathEndPath() { e.draw('Z', 0, 0, 0, 0, 0, 0) }
|
||||
func (e *Encoder) ClosePathAbsMoveTo(x, y float32) { e.draw('Y', x, y, 0, 0, 0, 0) }
|
||||
func (e *Encoder) ClosePathRelMoveTo(x, y float32) { e.draw('y', x, y, 0, 0, 0, 0) }
|
||||
|
||||
func (e *Encoder) AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
|
||||
e.arcTo('A', rx, ry, xAxisRotation, largeArc, sweep, x, y)
|
||||
}
|
||||
|
||||
func (e *Encoder) RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
|
||||
e.arcTo('a', rx, ry, xAxisRotation, largeArc, sweep, x, y)
|
||||
}
|
||||
|
||||
func (e *Encoder) arcTo(drawOp byte, rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
|
||||
flags := uint32(0)
|
||||
if largeArc {
|
||||
flags |= 0x01
|
||||
}
|
||||
if sweep {
|
||||
flags |= 0x02
|
||||
}
|
||||
e.draw(drawOp, rx, ry, xAxisRotation, float32(flags), x, y)
|
||||
}
|
||||
|
||||
func (e *Encoder) draw(drawOp byte, arg0, arg1, arg2, arg3, arg4, arg5 float32) {
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
if e.mode != modeDrawing {
|
||||
e.err = errDrawingOpsUsedInStylingMode
|
||||
return
|
||||
}
|
||||
if e.drawOp != drawOp {
|
||||
e.flushDrawOps()
|
||||
}
|
||||
e.drawOp = drawOp
|
||||
switch drawOps[drawOp].nArgs {
|
||||
case 0:
|
||||
// No-op.
|
||||
case 1:
|
||||
e.drawArgs = append(e.drawArgs, arg0)
|
||||
case 2:
|
||||
e.drawArgs = append(e.drawArgs, arg0, arg1)
|
||||
case 4:
|
||||
e.drawArgs = append(e.drawArgs, arg0, arg1, arg2, arg3)
|
||||
case 6:
|
||||
e.drawArgs = append(e.drawArgs, arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
switch drawOp {
|
||||
case 'Z':
|
||||
e.mode = modeStyling
|
||||
fallthrough
|
||||
case 'Y', 'y':
|
||||
e.flushDrawOps()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Encoder) flushDrawOps() {
|
||||
if e.drawOp == 0x00 {
|
||||
return
|
||||
}
|
||||
|
||||
if op := drawOps[e.drawOp]; op.nArgs == 0 {
|
||||
e.buf = append(e.buf, op.opcodeBase)
|
||||
} else {
|
||||
n := len(e.drawArgs) / int(op.nArgs)
|
||||
for i := 0; n > 0; {
|
||||
m := n
|
||||
if m > int(op.maxRepCount) {
|
||||
m = int(op.maxRepCount)
|
||||
}
|
||||
e.buf = append(e.buf, op.opcodeBase+uint8(m)-1)
|
||||
|
||||
switch e.drawOp {
|
||||
default:
|
||||
for j := m * int(op.nArgs); j > 0; j-- {
|
||||
e.buf.encodeCoordinate(quantize(e.drawArgs[i], e.highResolutionCoordinates))
|
||||
i++
|
||||
}
|
||||
case 'A', 'a':
|
||||
for j := m; j > 0; j-- {
|
||||
e.buf.encodeCoordinate(quantize(e.drawArgs[i+0], e.highResolutionCoordinates))
|
||||
e.buf.encodeCoordinate(quantize(e.drawArgs[i+1], e.highResolutionCoordinates))
|
||||
e.buf.encodeAngle(e.drawArgs[i+2])
|
||||
e.buf.encodeNatural(uint32(e.drawArgs[i+3]))
|
||||
e.buf.encodeCoordinate(quantize(e.drawArgs[i+4], e.highResolutionCoordinates))
|
||||
e.buf.encodeCoordinate(quantize(e.drawArgs[i+5], e.highResolutionCoordinates))
|
||||
i += 6
|
||||
}
|
||||
}
|
||||
|
||||
n -= m
|
||||
}
|
||||
}
|
||||
|
||||
e.drawOp = 0x00
|
||||
e.drawArgs = e.drawArgs[:0]
|
||||
}
|
||||
|
||||
func quantize(coord float32, highResolutionCoordinates bool) float32 {
|
||||
if !highResolutionCoordinates && (-128 <= coord && coord < 128) {
|
||||
x := math.Floor(float64(coord*64 + 0.5))
|
||||
return float32(x) / 64
|
||||
}
|
||||
return coord
|
||||
}
|
||||
|
||||
var drawOps = [256]struct {
|
||||
opcodeBase byte
|
||||
maxRepCount uint8
|
||||
nArgs uint8
|
||||
}{
|
||||
'L': {0x00, 32, 2},
|
||||
'l': {0x20, 32, 2},
|
||||
'T': {0x40, 16, 2},
|
||||
't': {0x50, 16, 2},
|
||||
'Q': {0x60, 16, 4},
|
||||
'q': {0x70, 16, 4},
|
||||
'S': {0x80, 16, 4},
|
||||
's': {0x90, 16, 4},
|
||||
'C': {0xa0, 16, 6},
|
||||
'c': {0xb0, 16, 6},
|
||||
'A': {0xc0, 16, 6},
|
||||
'a': {0xd0, 16, 6},
|
||||
|
||||
// Z means close path and then end path.
|
||||
'Z': {0xe1, 1, 0},
|
||||
// Y/y means close path and then open a new path (with a MoveTo/moveTo).
|
||||
'Y': {0xe2, 1, 2},
|
||||
'y': {0xe3, 1, 2},
|
||||
|
||||
'H': {0xe6, 1, 1},
|
||||
'h': {0xe7, 1, 1},
|
||||
'V': {0xe8, 1, 1},
|
||||
'v': {0xe9, 1, 1},
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package iconvg
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"golang.org/x/image/math/f32"
|
||||
)
|
||||
|
||||
const magic = "\x89IVG"
|
||||
|
||||
var magicBytes = []byte(magic)
|
||||
|
||||
var (
|
||||
negativeInfinity = math.Float32frombits(0xff800000)
|
||||
positiveInfinity = math.Float32frombits(0x7f800000)
|
||||
)
|
||||
|
||||
func isNaNOrInfinity(f float32) bool {
|
||||
return math.Float32bits(f)&0x7f800000 == 0x7f800000
|
||||
}
|
||||
|
||||
const (
|
||||
// File Format Version 0.
|
||||
midViewBox = 0
|
||||
midSuggestedPalette = 1
|
||||
|
||||
// File Format Version 1.
|
||||
ffv1MIDViewBox = 8
|
||||
ffv1MIDSuggestedPalette = 16
|
||||
)
|
||||
|
||||
var gradientShapeNames = [2]string{
|
||||
"linear",
|
||||
"radial",
|
||||
}
|
||||
|
||||
var gradientSpreadNames = [4]string{
|
||||
"none",
|
||||
"pad",
|
||||
"reflect",
|
||||
"repeat",
|
||||
}
|
||||
|
||||
// GradientSpread is how to spread a gradient past its nominal bounds (from
|
||||
// offset being 0.0 to offset being 1.0).
|
||||
type GradientSpread uint8
|
||||
|
||||
const (
|
||||
GradientSpreadNone GradientSpread = 0
|
||||
GradientSpreadPad GradientSpread = 1
|
||||
GradientSpreadReflect GradientSpread = 2
|
||||
GradientSpreadRepeat GradientSpread = 3
|
||||
)
|
||||
|
||||
// GradientStop is a color/offset gradient stop.
|
||||
type GradientStop struct {
|
||||
Offset float32
|
||||
Color color.Color
|
||||
}
|
||||
|
||||
// Rectangle is defined by its minimum and maximum coordinates.
|
||||
type Rectangle struct {
|
||||
Min, Max f32.Vec2
|
||||
}
|
||||
|
||||
// AspectRatio returns the Rectangle's aspect ratio. An IconVG graphic is
|
||||
// scalable; these dimensions do not necessarily map 1:1 to pixels.
|
||||
func (r *Rectangle) AspectRatio() (dx, dy float32) {
|
||||
return r.Max[0] - r.Min[0], r.Max[1] - r.Min[1]
|
||||
}
|
||||
|
||||
// Palette is an IconVG palette.
|
||||
type Palette [64]color.RGBA
|
||||
|
||||
// Metadata is an IconVG's metadata.
|
||||
type Metadata struct {
|
||||
ViewBox Rectangle
|
||||
|
||||
// Palette is a 64 color palette. When encoding, it is the suggested
|
||||
// palette to place within the IconVG graphic. When decoding, it is either
|
||||
// the optional palette passed to Decode, or if no optional palette was
|
||||
// given, the suggested palette within the IconVG graphic.
|
||||
Palette Palette
|
||||
}
|
||||
|
||||
// DefaultViewBox is the default ViewBox. Its values should not be modified.
|
||||
var DefaultViewBox = Rectangle{
|
||||
Min: f32.Vec2{-32, -32},
|
||||
Max: f32.Vec2{+32, +32},
|
||||
}
|
||||
|
||||
// DefaultPalette is the default Palette. Its values should not be modified.
|
||||
var DefaultPalette = Palette{
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
color.RGBA{0x00, 0x00, 0x00, 0xff},
|
||||
}
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package gradient provides linear and radial gradient images.
|
||||
package gradient
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"golang.org/x/image/math/f64"
|
||||
)
|
||||
|
||||
// TODO: gamma correction / non-linear color interpolation?
|
||||
|
||||
// TODO: move this out of an internal directory, either under
|
||||
// golang.org/x/image or under the standard library's image, so that
|
||||
// golang.org/x/image/{draw,vector} and possibly image/draw can type switch on
|
||||
// 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. 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
|
||||
|
||||
const (
|
||||
ShapeLinear Shape = iota
|
||||
ShapeRadial
|
||||
)
|
||||
|
||||
// Spread is the gradient spread, or how to spread a gradient past its nominal
|
||||
// bounds (from offset being 0.0 to offset being 1.0).
|
||||
type Spread uint8
|
||||
|
||||
const (
|
||||
// SpreadNone means that offsets outside of the [0, 1] range map to
|
||||
// transparent black.
|
||||
SpreadNone Spread = iota
|
||||
// SpreadPad means that offsets below 0 and above 1 map to the colors that
|
||||
// 0 and 1 would map to.
|
||||
SpreadPad
|
||||
// SpreadReflect means that the offset mapping is reflected start-to-end,
|
||||
// end-to-start, start-to-end, etc.
|
||||
SpreadReflect
|
||||
// SpreadRepeat means that the offset mapping is repeated start-to-end,
|
||||
// start-to-end, start-to-end, etc.
|
||||
SpreadRepeat
|
||||
)
|
||||
|
||||
// Clamp clamps x to the range [0, 1]. If x is outside that range, it is
|
||||
// converted to a value in that range according to s's semantics. It returns -1
|
||||
// if s is SpreadNone and x is outside the range [0, 1].
|
||||
func (s Spread) Clamp(x float64) float64 {
|
||||
if x >= 0 {
|
||||
if x <= 1 {
|
||||
return x
|
||||
}
|
||||
switch s {
|
||||
case SpreadPad:
|
||||
return 1
|
||||
case SpreadReflect:
|
||||
if int(x)&1 == 0 {
|
||||
return x - math.Floor(x)
|
||||
}
|
||||
return math.Ceil(x) - x
|
||||
case SpreadRepeat:
|
||||
return x - math.Floor(x)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
switch s {
|
||||
case SpreadPad:
|
||||
return 0
|
||||
case SpreadReflect:
|
||||
x = -x
|
||||
if int(x)&1 == 0 {
|
||||
return x - math.Floor(x)
|
||||
}
|
||||
return math.Ceil(x) - x
|
||||
case SpreadRepeat:
|
||||
return x - math.Floor(x)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Stop is an offset and color.
|
||||
type Stop struct {
|
||||
Offset float64
|
||||
RGBA64 color.RGBA64
|
||||
}
|
||||
|
||||
// Range is the range between two stops.
|
||||
type Range struct {
|
||||
Offset0 float64
|
||||
Offset1 float64
|
||||
Width float64
|
||||
R0 float64
|
||||
R1 float64
|
||||
G0 float64
|
||||
G1 float64
|
||||
B0 float64
|
||||
B1 float64
|
||||
A0 float64
|
||||
A1 float64
|
||||
}
|
||||
|
||||
// MakeRange returns the range between two stops.
|
||||
func MakeRange(s0, s1 Stop) Range {
|
||||
return Range{
|
||||
Offset0: s0.Offset,
|
||||
Offset1: s1.Offset,
|
||||
Width: s1.Offset - s0.Offset,
|
||||
R0: float64(s0.RGBA64.R),
|
||||
R1: float64(s1.RGBA64.R),
|
||||
G0: float64(s0.RGBA64.G),
|
||||
G1: float64(s1.RGBA64.G),
|
||||
B0: float64(s0.RGBA64.B),
|
||||
B1: float64(s1.RGBA64.B),
|
||||
A0: float64(s0.RGBA64.A),
|
||||
A1: float64(s1.RGBA64.A),
|
||||
}
|
||||
}
|
||||
|
||||
// AppendRanges appends to a the ranges defined by a's implicit final stop (if
|
||||
// any exist) and stops.
|
||||
func AppendRanges(a []Range, stops []Stop) []Range {
|
||||
if len(stops) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(a) != 0 {
|
||||
z := a[len(a)-1]
|
||||
a = append(a, MakeRange(Stop{
|
||||
Offset: z.Offset1,
|
||||
RGBA64: color.RGBA64{
|
||||
R: uint16(z.R1),
|
||||
G: uint16(z.G1),
|
||||
B: uint16(z.B1),
|
||||
A: uint16(z.A1),
|
||||
},
|
||||
}, stops[0]))
|
||||
}
|
||||
for i := 0; i < len(stops)-1; i++ {
|
||||
a = append(a, MakeRange(stops[i], stops[i+1]))
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Gradient is a very large image.Image (the same size as an image.Uniform)
|
||||
// whose colors form a gradient.
|
||||
type Gradient struct {
|
||||
Shape Shape
|
||||
Spread Spread
|
||||
|
||||
// Pix2Grad transforms coordinates from pixel space (the arguments to the
|
||||
// Image.At method) to gradient space. Gradient space is where a linear
|
||||
// gradient ranges from x == 0 to x == 1, and a radial gradient has center
|
||||
// (0, 0) and radius 1.
|
||||
//
|
||||
// This is an affine transform, so it can represent elliptical gradients in
|
||||
// pixel space, including non-axis-aligned ellipses.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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{}
|
||||
g.Last = color.RGBA64{}
|
||||
} else {
|
||||
g.First = stops[0].RGBA64
|
||||
g.Last = stops[len(stops)-1].RGBA64
|
||||
}
|
||||
}
|
||||
|
||||
// ColorModel satisfies the image.Image interface.
|
||||
func (g *Gradient) ColorModel() color.Model {
|
||||
return color.RGBA64Model
|
||||
}
|
||||
|
||||
// Bounds satisfies the image.Image interface.
|
||||
func (g *Gradient) Bounds() image.Rectangle {
|
||||
return image.Rectangle{
|
||||
Min: image.Point{-1e9, -1e9},
|
||||
Max: image.Point{+1e9, +1e9},
|
||||
}
|
||||
}
|
||||
|
||||
// At satisfies the image.Image interface.
|
||||
func (g *Gradient) At(x, y int) color.Color {
|
||||
if len(g.Ranges) == 0 {
|
||||
return color.RGBA64{}
|
||||
}
|
||||
|
||||
px := float64(x) + 0.5
|
||||
py := float64(y) + 0.5
|
||||
|
||||
offset := 0.0
|
||||
if g.Shape == ShapeLinear {
|
||||
offset = g.Spread.Clamp(g.Pix2Grad[0]*px + g.Pix2Grad[1]*py + g.Pix2Grad[2])
|
||||
} else {
|
||||
gx := g.Pix2Grad[0]*px + g.Pix2Grad[1]*py + g.Pix2Grad[2]
|
||||
gy := g.Pix2Grad[3]*px + g.Pix2Grad[4]*py + g.Pix2Grad[5]
|
||||
offset = g.Spread.Clamp(math.Sqrt(gx*gx + gy*gy))
|
||||
}
|
||||
if !(offset >= 0) {
|
||||
return color.RGBA64{}
|
||||
}
|
||||
|
||||
if offset < g.Ranges[0].Offset0 {
|
||||
return g.First
|
||||
}
|
||||
for _, r := range g.Ranges {
|
||||
if r.Offset0 <= offset && offset <= r.Offset1 {
|
||||
t := (offset - r.Offset0) / r.Width
|
||||
s := 1 - t
|
||||
return color.RGBA64{
|
||||
uint16(s*r.R0 + t*r.R1),
|
||||
uint16(s*r.G0 + t*r.G1),
|
||||
uint16(s*r.B0 + t*r.B1),
|
||||
uint16(s*r.A0 + t*r.A1),
|
||||
}
|
||||
}
|
||||
}
|
||||
return g.Last
|
||||
}
|
||||
+595
@@ -0,0 +1,595 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package iconvg
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"math"
|
||||
|
||||
"golang.org/x/exp/shiny/iconvg/internal/gradient"
|
||||
"golang.org/x/image/math/f64"
|
||||
"golang.org/x/image/vector"
|
||||
)
|
||||
|
||||
const (
|
||||
smoothTypeNone = iota
|
||||
smoothTypeQuad
|
||||
smoothTypeCube
|
||||
)
|
||||
|
||||
// Rasterizer is a Destination that draws an IconVG graphic onto a raster
|
||||
// image.
|
||||
//
|
||||
// The zero value is usable, in that it has no raster image to draw onto, so
|
||||
// that calling Decode with this Destination is a no-op (other than checking
|
||||
// the encoded form for errors in the byte code). Call SetDstImage to change
|
||||
// the raster image, before calling Decode or between calls to Decode.
|
||||
type Rasterizer struct {
|
||||
z vector.Rasterizer
|
||||
|
||||
dst draw.Image
|
||||
r image.Rectangle
|
||||
drawOp draw.Op
|
||||
|
||||
// scale and bias transforms the metadata.ViewBox rectangle to the (0, 0) -
|
||||
// (r.Dx(), r.Dy()) rectangle.
|
||||
scaleX float32
|
||||
biasX float32
|
||||
scaleY float32
|
||||
biasY float32
|
||||
|
||||
metadata Metadata
|
||||
|
||||
lod0 float32
|
||||
lod1 float32
|
||||
cSel uint8
|
||||
nSel uint8
|
||||
|
||||
disabled bool
|
||||
|
||||
firstStartPath bool
|
||||
prevSmoothType uint8
|
||||
prevSmoothPointX float32
|
||||
prevSmoothPointY float32
|
||||
|
||||
fill image.Image
|
||||
flatColor color.RGBA
|
||||
flatImage image.Uniform
|
||||
gradient gradient.Gradient
|
||||
|
||||
cReg [64]color.RGBA
|
||||
nReg [64]float32
|
||||
stops [64]gradient.Stop
|
||||
}
|
||||
|
||||
// SetDstImage sets the Rasterizer to draw onto a destination image, given by
|
||||
// dst and r, with the given compositing operator.
|
||||
//
|
||||
// The IconVG graphic (which does not have a fixed size in pixels) will be
|
||||
// scaled in the X and Y dimensions to fit the rectangle r. The scaling factors
|
||||
// may differ in the two dimensions.
|
||||
func (z *Rasterizer) SetDstImage(dst draw.Image, r image.Rectangle, drawOp draw.Op) {
|
||||
z.dst = dst
|
||||
if r.Empty() {
|
||||
r = image.Rectangle{}
|
||||
}
|
||||
z.r = r
|
||||
z.drawOp = drawOp
|
||||
z.recalcTransform()
|
||||
}
|
||||
|
||||
// Reset resets the Rasterizer for the given Metadata.
|
||||
func (z *Rasterizer) Reset(m Metadata) {
|
||||
z.metadata = m
|
||||
z.lod0 = 0
|
||||
z.lod1 = positiveInfinity
|
||||
z.cSel = 0
|
||||
z.nSel = 0
|
||||
z.firstStartPath = true
|
||||
z.prevSmoothType = smoothTypeNone
|
||||
z.prevSmoothPointX = 0
|
||||
z.prevSmoothPointY = 0
|
||||
z.cReg = m.Palette
|
||||
z.nReg = [64]float32{}
|
||||
z.recalcTransform()
|
||||
}
|
||||
|
||||
func (z *Rasterizer) recalcTransform() {
|
||||
z.scaleX = float32(z.r.Dx()) / (z.metadata.ViewBox.Max[0] - z.metadata.ViewBox.Min[0])
|
||||
z.biasX = -z.metadata.ViewBox.Min[0]
|
||||
z.scaleY = float32(z.r.Dy()) / (z.metadata.ViewBox.Max[1] - z.metadata.ViewBox.Min[1])
|
||||
z.biasY = -z.metadata.ViewBox.Min[1]
|
||||
}
|
||||
|
||||
func (z *Rasterizer) SetCSel(cSel uint8) { z.cSel = cSel & 0x3f }
|
||||
func (z *Rasterizer) SetNSel(nSel uint8) { z.nSel = nSel & 0x3f }
|
||||
|
||||
func (z *Rasterizer) SetCReg(adj uint8, incr bool, c Color) {
|
||||
z.cReg[(z.cSel-adj)&0x3f] = c.Resolve(&z.metadata.Palette, &z.cReg)
|
||||
if incr {
|
||||
z.cSel++
|
||||
}
|
||||
}
|
||||
|
||||
func (z *Rasterizer) SetNReg(adj uint8, incr bool, f float32) {
|
||||
z.nReg[(z.nSel-adj)&0x3f] = f
|
||||
if incr {
|
||||
z.nSel++
|
||||
}
|
||||
}
|
||||
|
||||
func (z *Rasterizer) SetLOD(lod0, lod1 float32) {
|
||||
z.lod0, z.lod1 = lod0, lod1
|
||||
}
|
||||
|
||||
func (z *Rasterizer) unabsX(x float32) float32 { return x/z.scaleX - z.biasX }
|
||||
func (z *Rasterizer) unabsY(y float32) float32 { return y/z.scaleY - z.biasY }
|
||||
|
||||
func (z *Rasterizer) absX(x float32) float32 { return z.scaleX * (x + z.biasX) }
|
||||
func (z *Rasterizer) absY(y float32) float32 { return z.scaleY * (y + z.biasY) }
|
||||
func (z *Rasterizer) relX(x float32) float32 { return z.scaleX * x }
|
||||
func (z *Rasterizer) relY(y float32) float32 { return z.scaleY * y }
|
||||
|
||||
func (z *Rasterizer) absVec2(x, y float32) (zx, zy float32) {
|
||||
return z.absX(x), z.absY(y)
|
||||
}
|
||||
|
||||
func (z *Rasterizer) relVec2(x, y float32) (zx, zy float32) {
|
||||
px, py := z.z.Pen()
|
||||
return px + z.relX(x), py + z.relY(y)
|
||||
}
|
||||
|
||||
// implicitSmoothPoint returns the implicit control point for smooth-quadratic
|
||||
// and smooth-cubic Bézier curves.
|
||||
//
|
||||
// https://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands says, "The first
|
||||
// control point is assumed to be the reflection of the second control point on
|
||||
// the previous command relative to the current point. (If there is no previous
|
||||
// command or if the previous command was not [a quadratic or cubic command],
|
||||
// assume the first control point is coincident with the current point.)"
|
||||
func (z *Rasterizer) implicitSmoothPoint(thisSmoothType uint8) (zx, zy float32) {
|
||||
px, py := z.z.Pen()
|
||||
if z.prevSmoothType != thisSmoothType {
|
||||
return px, py
|
||||
}
|
||||
return 2*px - z.prevSmoothPointX, 2*py - z.prevSmoothPointY
|
||||
}
|
||||
|
||||
func (z *Rasterizer) initGradient(rgba color.RGBA) (ok bool) {
|
||||
nStops := int(rgba.R & 0x3f)
|
||||
cBase := int(rgba.G & 0x3f)
|
||||
nBase := int(rgba.B & 0x3f)
|
||||
prevN := negativeInfinity
|
||||
for i := 0; i < nStops; i++ {
|
||||
c := z.cReg[(cBase+i)&0x3f]
|
||||
if !validAlphaPremulColor(c) {
|
||||
return false
|
||||
}
|
||||
n := z.nReg[(nBase+i)&0x3f]
|
||||
if !(0 <= n && n <= 1) || !(n > prevN) {
|
||||
return false
|
||||
}
|
||||
prevN = n
|
||||
z.stops[i] = gradient.Stop{
|
||||
Offset: float64(n),
|
||||
RGBA64: color.RGBA64{
|
||||
R: uint16(c.R) * 0x101,
|
||||
G: uint16(c.G) * 0x101,
|
||||
B: uint16(c.B) * 0x101,
|
||||
A: uint16(c.A) * 0x101,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (z *Rasterizer) StartPath(adj uint8, x, y float32) {
|
||||
z.flatColor = z.cReg[(z.cSel-adj)&0x3f]
|
||||
if validAlphaPremulColor(z.flatColor) {
|
||||
z.flatImage.C = &z.flatColor
|
||||
z.fill = &z.flatImage
|
||||
z.disabled = z.flatColor.A == 0
|
||||
} else if z.flatColor.A == 0x00 && z.flatColor.B&0x80 != 0 {
|
||||
z.fill = &z.gradient
|
||||
z.disabled = !z.initGradient(z.flatColor)
|
||||
} else {
|
||||
z.fill = nil
|
||||
z.disabled = true
|
||||
}
|
||||
|
||||
width, height := z.r.Dx(), z.r.Dy()
|
||||
h := float32(height)
|
||||
z.disabled = z.disabled || !(z.lod0 <= h && h < z.lod1)
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
|
||||
z.z.Reset(width, height)
|
||||
if z.firstStartPath {
|
||||
z.firstStartPath = false
|
||||
z.z.DrawOp = z.drawOp
|
||||
}
|
||||
z.prevSmoothType = smoothTypeNone
|
||||
z.z.MoveTo(z.absVec2(x, y))
|
||||
}
|
||||
|
||||
func (z *Rasterizer) ClosePathEndPath() {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
z.z.ClosePath()
|
||||
if z.dst == nil {
|
||||
return
|
||||
}
|
||||
z.z.Draw(z.dst, z.r, z.fill, image.Point{})
|
||||
}
|
||||
|
||||
func (z *Rasterizer) ClosePathAbsMoveTo(x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
z.prevSmoothType = smoothTypeNone
|
||||
z.z.ClosePath()
|
||||
z.z.MoveTo(z.absVec2(x, y))
|
||||
}
|
||||
|
||||
func (z *Rasterizer) ClosePathRelMoveTo(x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
z.prevSmoothType = smoothTypeNone
|
||||
z.z.ClosePath()
|
||||
z.z.MoveTo(z.relVec2(x, y))
|
||||
}
|
||||
|
||||
func (z *Rasterizer) AbsHLineTo(x float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
_, py := z.z.Pen()
|
||||
z.prevSmoothType = smoothTypeNone
|
||||
z.z.LineTo(z.absX(x), py)
|
||||
}
|
||||
|
||||
func (z *Rasterizer) RelHLineTo(x float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
px, py := z.z.Pen()
|
||||
z.prevSmoothType = smoothTypeNone
|
||||
z.z.LineTo(px+z.relX(x), py)
|
||||
}
|
||||
|
||||
func (z *Rasterizer) AbsVLineTo(y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
px, _ := z.z.Pen()
|
||||
z.prevSmoothType = smoothTypeNone
|
||||
z.z.LineTo(px, z.absY(y))
|
||||
}
|
||||
|
||||
func (z *Rasterizer) RelVLineTo(y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
px, py := z.z.Pen()
|
||||
z.prevSmoothType = smoothTypeNone
|
||||
z.z.LineTo(px, py+z.relY(y))
|
||||
}
|
||||
|
||||
func (z *Rasterizer) AbsLineTo(x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
z.prevSmoothType = smoothTypeNone
|
||||
z.z.LineTo(z.absVec2(x, y))
|
||||
}
|
||||
|
||||
func (z *Rasterizer) RelLineTo(x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
z.prevSmoothType = smoothTypeNone
|
||||
z.z.LineTo(z.relVec2(x, y))
|
||||
}
|
||||
|
||||
func (z *Rasterizer) AbsSmoothQuadTo(x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
x1, y1 := z.implicitSmoothPoint(smoothTypeQuad)
|
||||
x, y = z.absVec2(x, y)
|
||||
z.prevSmoothType = smoothTypeQuad
|
||||
z.prevSmoothPointX, z.prevSmoothPointY = x1, y1
|
||||
z.z.QuadTo(x1, y1, x, y)
|
||||
}
|
||||
|
||||
func (z *Rasterizer) RelSmoothQuadTo(x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
x1, y1 := z.implicitSmoothPoint(smoothTypeQuad)
|
||||
x, y = z.relVec2(x, y)
|
||||
z.prevSmoothType = smoothTypeQuad
|
||||
z.prevSmoothPointX, z.prevSmoothPointY = x1, y1
|
||||
z.z.QuadTo(x1, y1, x, y)
|
||||
}
|
||||
|
||||
func (z *Rasterizer) AbsQuadTo(x1, y1, x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
x1, y1 = z.absVec2(x1, y1)
|
||||
x, y = z.absVec2(x, y)
|
||||
z.prevSmoothType = smoothTypeQuad
|
||||
z.prevSmoothPointX, z.prevSmoothPointY = x1, y1
|
||||
z.z.QuadTo(x1, y1, x, y)
|
||||
}
|
||||
|
||||
func (z *Rasterizer) RelQuadTo(x1, y1, x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
x1, y1 = z.relVec2(x1, y1)
|
||||
x, y = z.relVec2(x, y)
|
||||
z.prevSmoothType = smoothTypeQuad
|
||||
z.prevSmoothPointX, z.prevSmoothPointY = x1, y1
|
||||
z.z.QuadTo(x1, y1, x, y)
|
||||
}
|
||||
|
||||
func (z *Rasterizer) AbsSmoothCubeTo(x2, y2, x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
x1, y1 := z.implicitSmoothPoint(smoothTypeCube)
|
||||
x2, y2 = z.absVec2(x2, y2)
|
||||
x, y = z.absVec2(x, y)
|
||||
z.prevSmoothType = smoothTypeCube
|
||||
z.prevSmoothPointX, z.prevSmoothPointY = x2, y2
|
||||
z.z.CubeTo(x1, y1, x2, y2, x, y)
|
||||
}
|
||||
|
||||
func (z *Rasterizer) RelSmoothCubeTo(x2, y2, x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
x1, y1 := z.implicitSmoothPoint(smoothTypeCube)
|
||||
x2, y2 = z.relVec2(x2, y2)
|
||||
x, y = z.relVec2(x, y)
|
||||
z.prevSmoothType = smoothTypeCube
|
||||
z.prevSmoothPointX, z.prevSmoothPointY = x2, y2
|
||||
z.z.CubeTo(x1, y1, x2, y2, x, y)
|
||||
}
|
||||
|
||||
func (z *Rasterizer) AbsCubeTo(x1, y1, x2, y2, x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
x1, y1 = z.absVec2(x1, y1)
|
||||
x2, y2 = z.absVec2(x2, y2)
|
||||
x, y = z.absVec2(x, y)
|
||||
z.prevSmoothType = smoothTypeCube
|
||||
z.prevSmoothPointX, z.prevSmoothPointY = x2, y2
|
||||
z.z.CubeTo(x1, y1, x2, y2, x, y)
|
||||
}
|
||||
|
||||
func (z *Rasterizer) RelCubeTo(x1, y1, x2, y2, x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
x1, y1 = z.relVec2(x1, y1)
|
||||
x2, y2 = z.relVec2(x2, y2)
|
||||
x, y = z.relVec2(x, y)
|
||||
z.prevSmoothType = smoothTypeCube
|
||||
z.prevSmoothPointX, z.prevSmoothPointY = x2, y2
|
||||
z.z.CubeTo(x1, y1, x2, y2, x, y)
|
||||
}
|
||||
|
||||
func (z *Rasterizer) AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
|
||||
if z.disabled {
|
||||
return
|
||||
}
|
||||
z.prevSmoothType = smoothTypeNone
|
||||
|
||||
// We follow the "Conversion from endpoint to center parameterization"
|
||||
// algorithm as per
|
||||
// https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
|
||||
|
||||
// There seems to be a bug in the spec's "implementation notes".
|
||||
//
|
||||
// Actual implementations, such as
|
||||
// - https://git.gnome.org/browse/librsvg/tree/rsvg-path.c
|
||||
// - http://svn.apache.org/repos/asf/xmlgraphics/batik/branches/svg11/sources/org/apache/batik/ext/awt/geom/ExtendedGeneralPath.java
|
||||
// - https://java.net/projects/svgsalamander/sources/svn/content/trunk/svg-core/src/main/java/com/kitfox/svg/pathcmd/Arc.java
|
||||
// - https://github.com/millermedeiros/SVGParser/blob/master/com/millermedeiros/geom/SVGArc.as
|
||||
// do something slightly different (marked with a †).
|
||||
|
||||
// (†) The Abs isn't part of the spec. Neither is checking that Rx and Ry
|
||||
// are non-zero (and non-NaN).
|
||||
Rx := math.Abs(float64(rx))
|
||||
Ry := math.Abs(float64(ry))
|
||||
if !(Rx > 0 && Ry > 0) {
|
||||
z.z.LineTo(x, y)
|
||||
return
|
||||
}
|
||||
|
||||
// We work in IconVG coordinates (e.g. from -32 to +32 by default), rather
|
||||
// than destination image coordinates (e.g. the width of the dst image),
|
||||
// since the rx and ry radii also need to be scaled, but their scaling
|
||||
// factors can be different, and aren't trivial to calculate due to
|
||||
// xAxisRotation.
|
||||
//
|
||||
// We convert back to destination image coordinates via absX and absY calls
|
||||
// later, during arcSegmentTo.
|
||||
penX, penY := z.z.Pen()
|
||||
x1 := float64(z.unabsX(penX))
|
||||
y1 := float64(z.unabsY(penY))
|
||||
x2 := float64(x)
|
||||
y2 := float64(y)
|
||||
|
||||
phi := 2 * math.Pi * float64(xAxisRotation)
|
||||
|
||||
// Step 1: Compute (x1′, y1′)
|
||||
halfDx := (x1 - x2) / 2
|
||||
halfDy := (y1 - y2) / 2
|
||||
cosPhi := math.Cos(phi)
|
||||
sinPhi := math.Sin(phi)
|
||||
x1Prime := +cosPhi*halfDx + sinPhi*halfDy
|
||||
y1Prime := -sinPhi*halfDx + cosPhi*halfDy
|
||||
|
||||
// Step 2: Compute (cx′, cy′)
|
||||
rxSq := Rx * Rx
|
||||
rySq := Ry * Ry
|
||||
x1PrimeSq := x1Prime * x1Prime
|
||||
y1PrimeSq := y1Prime * y1Prime
|
||||
|
||||
// (†) Check that the radii are large enough.
|
||||
radiiCheck := x1PrimeSq/rxSq + y1PrimeSq/rySq
|
||||
if radiiCheck > 1 {
|
||||
c := math.Sqrt(radiiCheck)
|
||||
Rx *= c
|
||||
Ry *= c
|
||||
rxSq = Rx * Rx
|
||||
rySq = Ry * Ry
|
||||
}
|
||||
|
||||
denom := rxSq*y1PrimeSq + rySq*x1PrimeSq
|
||||
step2 := 0.0
|
||||
if a := rxSq*rySq/denom - 1; a > 0 {
|
||||
step2 = math.Sqrt(a)
|
||||
}
|
||||
if largeArc == sweep {
|
||||
step2 = -step2
|
||||
}
|
||||
cxPrime := +step2 * Rx * y1Prime / Ry
|
||||
cyPrime := -step2 * Ry * x1Prime / Rx
|
||||
|
||||
// Step 3: Compute (cx, cy) from (cx′, cy′)
|
||||
cx := +cosPhi*cxPrime - sinPhi*cyPrime + (x1+x2)/2
|
||||
cy := +sinPhi*cxPrime + cosPhi*cyPrime + (y1+y2)/2
|
||||
|
||||
// Step 4: Compute θ1 and Δθ
|
||||
ax := (+x1Prime - cxPrime) / Rx
|
||||
ay := (+y1Prime - cyPrime) / Ry
|
||||
bx := (-x1Prime - cxPrime) / Rx
|
||||
by := (-y1Prime - cyPrime) / Ry
|
||||
theta1 := angle(1, 0, ax, ay)
|
||||
deltaTheta := angle(ax, ay, bx, by)
|
||||
if sweep {
|
||||
if deltaTheta < 0 {
|
||||
deltaTheta += 2 * math.Pi
|
||||
}
|
||||
} else {
|
||||
if deltaTheta > 0 {
|
||||
deltaTheta -= 2 * math.Pi
|
||||
}
|
||||
}
|
||||
|
||||
// This ends the
|
||||
// https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
|
||||
// algorithm. What follows below is specific to this implementation.
|
||||
|
||||
// We approximate an arc by one or more cubic Bézier curves.
|
||||
n := int(math.Ceil(math.Abs(deltaTheta) / (math.Pi/2 + 0.001)))
|
||||
for i := 0; i < n; i++ {
|
||||
z.arcSegmentTo(cx, cy,
|
||||
theta1+deltaTheta*float64(i+0)/float64(n),
|
||||
theta1+deltaTheta*float64(i+1)/float64(n),
|
||||
Rx, Ry, cosPhi, sinPhi,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// arcSegmentTo approximates an arc by a cubic Bézier curve. The mathematical
|
||||
// formulae for the control points are the same as that used by librsvg.
|
||||
func (z *Rasterizer) arcSegmentTo(cx, cy, theta1, theta2, rx, ry, cosPhi, sinPhi float64) {
|
||||
halfDeltaTheta := (theta2 - theta1) * 0.5
|
||||
q := math.Sin(halfDeltaTheta * 0.5)
|
||||
t := (8 * q * q) / (3 * math.Sin(halfDeltaTheta))
|
||||
cos1 := math.Cos(theta1)
|
||||
sin1 := math.Sin(theta1)
|
||||
cos2 := math.Cos(theta2)
|
||||
sin2 := math.Sin(theta2)
|
||||
x1 := rx * (+cos1 - t*sin1)
|
||||
y1 := ry * (+sin1 + t*cos1)
|
||||
x2 := rx * (+cos2 + t*sin2)
|
||||
y2 := ry * (+sin2 - t*cos2)
|
||||
x3 := rx * (+cos2)
|
||||
y3 := ry * (+sin2)
|
||||
z.z.CubeTo(
|
||||
z.absX(float32(cx+cosPhi*x1-sinPhi*y1)),
|
||||
z.absY(float32(cy+sinPhi*x1+cosPhi*y1)),
|
||||
z.absX(float32(cx+cosPhi*x2-sinPhi*y2)),
|
||||
z.absY(float32(cy+sinPhi*x2+cosPhi*y2)),
|
||||
z.absX(float32(cx+cosPhi*x3-sinPhi*y3)),
|
||||
z.absY(float32(cy+sinPhi*x3+cosPhi*y3)),
|
||||
)
|
||||
}
|
||||
|
||||
func (z *Rasterizer) RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
|
||||
ax, ay := z.relVec2(x, y)
|
||||
z.AbsArcTo(rx, ry, xAxisRotation, largeArc, sweep, z.unabsX(ax), z.unabsY(ay))
|
||||
}
|
||||
|
||||
// angle returns the angle between the u and v vectors.
|
||||
func angle(ux, uy, vx, vy float64) float64 {
|
||||
uNorm := math.Sqrt(ux*ux + uy*uy)
|
||||
vNorm := math.Sqrt(vx*vx + vy*vy)
|
||||
norm := uNorm * vNorm
|
||||
cos := (ux*vx + uy*vy) / norm
|
||||
ret := 0.0
|
||||
if cos <= -1 {
|
||||
ret = math.Pi
|
||||
} else if cos >= +1 {
|
||||
ret = 0
|
||||
} else {
|
||||
ret = math.Acos(cos)
|
||||
}
|
||||
if ux*vy < uy*vx {
|
||||
return -ret
|
||||
}
|
||||
return +ret
|
||||
}
|
||||
+1100
File diff suppressed because it is too large
Load Diff
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+10969
File diff suppressed because it is too large
Load Diff
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:generate go run gen.go -mdicons=/path/to/the/material-design-icons
|
||||
|
||||
// Package icons contains the Material Design icon set, in the IconVG vector
|
||||
// graphic format.
|
||||
//
|
||||
// See https://design.google.com/icons/ and
|
||||
// https://godoc.org/golang.org/x/exp/shiny/iconvg
|
||||
package icons
|
||||
Reference in New Issue
Block a user