blob: bfa7310c4ef1865966f39f18852a0616ac78a771 [file] [log] [blame]
// 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.
It is similar in concept to SVG (Scalable Vector Graphics) but much simpler.
Compared to "SVG Tiny", it does not have features for text, multimedia,
interactivity, linking, scripting, animation, XSLT, DOM, combination with
raster graphics such as JPEG formatted textures, etc.
It is a format for efficient presentation, not an authoring format. For
example, it does not provide grouping individual paths into higher level
objects. Instead, the anticipated workflow is that artists use other tools and
authoring formats like Inkscape and SVG, or commercial equivalents, and export
IconVG versions of their assets, the same way that they would produce PNG
versions of their vector art. It is not a goal to be able to recover the
original SVG from a derived IconVG.
It is not a pixel-exact format. Different implementations may produce slightly
different renderings, due to implementation-specific rounding errors in the
mathematical computations when rasterizing vector paths to pixels. Artifacts
may appear when scaling up to extreme sizes, say 1 million by 1 million pixels.
Nonetheless, at typical scales, e.g. up to 4096 × 4096, such differences are
not expected to be perceptible to the naked eye.
Structure
An IconVG graphic consists of a magic identifier, one or more metadata bytes
then a sequence of variable length instructions for a virtual machine.
Those instructions encode a sequence of filled paths, similar to SVG path data
(https://www.w3.org/TR/SVG/paths.html#PathData). Rendering involves switching
between two modes: a styling mode where color registers are set, and a drawing
mode where a path's geometry is defined. The virtual machine starts in the
styling mode.
In both modes, rendering proceeds by reading a one byte opcode followed by a
variable number of data bytes for that opcode. The mapping from byte values to
opcodes depends on whether the renderer is in the styling or drawing mode. A
0x07 byte value means setting the color selector register in the styling mode,
and means adding multiple lineto segments in the drawing mode.
Level of Detail
The machine state includes 2 level of detail registers, denoted LOD0 and LOD1,
both float32 values, initialized to +0 and +infinity. Drawing mode opcodes have
no effect (other than leaving drawing mode) unless the height H in pixels of
the rasterization satisfies LOD0 <= H and H < LOD1.
This allows the format to provide a simpler version for small rasterizations
(e.g. below 32 pixels) and a more complex version for large rasterizations
(e.g. 32 and above pixels).
Registers
The machine state includes 64 color registers (denoted CREG[0], CREG[1], ...,
CREG[63]) and 64 number registers (denoted NREG[0], NREG[1], ..., NREG[63]).
Register indexing is done modulo 64, so CREG[70] is the same as CREG[6], and
CREG[-1] is the same as CREG[63].
Each CREG and NREG register is 32 bits wide. The CREG registers are initialized
to the custom palette (see below); the NREG registers are initialized to 0. The
machine state also includes two selector registers, denoted CSEL and NSEL. They
are effectively 6 bit integers, as they index CREG and NREG, and are also
initialized to 0.
Color registers are four uint8 values: red, green, blue and alpha.
Number registers are float32 values.
Colors and Gradients
IconVG graphics work in 32 bit alpha-premultiplied color, with 8 bits for red,
green, blue and alpha. Alpha-premultiplication means that c00000c0 represents a
75%-opaque, fully saturated red.
It also means that some RGBA combinations (where e.g. the red value is greater
than the alpha value) are nonsensical. The virtual machine re-purposes some of
those values to represent gradients instead of flat colors. Any color register
whose alpha value is zero but whose blue value is at least 128 is a gradient.
Its remaining bits are reinterpreted such that:
The low 6 bits of the red value is the number of color/offset stops, NSTOPS.
The high 2 bits of the red value are reserved.
The low 6 bits of the green value is the color register base, CBASE.
The high 2 bits of the green value is how to spread the gradient past its
nominal bounds (from offset being 0.0 to offset being 1.0). The high two bits
being 0, 1, 2 or 3 mean none, pad, reflect and repeat respectively. None means
that offsets outside of the [0.0, 1.0] range map to transparent black. Pad
means that offsets below 0.0 and above 1.0 map to the colors that 0.0 and 1.0
would map to. Reflect means that the offset mapping is reflected start-to-end,
end-to-start, start-to-end, etc. Repeat means that the offset mapping is
repeated start-to-end, start-to-end, start-to-end, etc.
The low 6 bits of the blue value is the number register base, NBASE.
The remaining bit (the 0x40 bit) of the blue value denotes the gradient shape:
0 means a linear gradient and 1 means a radial gradient.
The gradient has NSTOPS color/offset stops. The first stop has color
CREG[CBASE+0] and offset NREG[NBASE+0], the second stop has color CREG[CBASE+1]
and offset NREG[NBASE+1], and so on.
The gradient also uses the six numbers from NREG[NBASE-6] to NREG[NBASE-1],
which form an affine transformation matrix [a, b, c; d, e, f] such that
a=NREG[NBASE-6], b=NREG[NBASE-5], c=NREG[NBASE-4], etc. This matrix maps from
graphic coordinate space (defined by the metadata's viewBox) to gradient
coordinate space. Gradient coordinate space is where a linear gradient ranges
from x=0 to x=1, and a radial gradient has center (0, 0) and radius 1.
The graphic coordinate (px, py) maps to the gradient coordinate (dx, dy) by:
dx = a*px + b*py + c
dy = d*px + e*py + f
The appendix below gives explicit formulae for the [a, b, c; d, e, f] affine
transformation matrix for common gradient geometry, such as a linear gradient
defined by two points.
At the time a gradient is used to fill a path, it is invalid for any of the
stop colors to itself be a gradient, or for any stop offset to be less than or
equal to a previous offset, or outside the range [0, 1].
Colors
Color register values are always 32 bits, or 4 bytes. Colors in the instruction
byte stream can be encoded more compactly, and are encoded in either 1, 2, 3 or
4 bytes, depending on context. For example, some opcodes are followed by a 1
byte color, others by a 2 byte color. There are two forms of 3 byte colors:
direct and indirect.
For a 1 byte encoding, byte values in the range [0, 125) encode the RGBA color
where the red, green and blue values come from the base-5 encoding of that byte
value such that 0, 1, 2, 3 and 4 map to 0x00, 0x40, 0x80, 0xc0, 0xff. The alpha
value is 0xff. For example, the color 40ffc0ff can be encoded as 0x30, as
decimal 48 equals 1*25 + 4*5 + 3. A byte value of 125, 126 or 127 mean the
colors c0c0c0c0, 80808080 and 00000000 respectively. Byte values in the range
[128, 192) mean a color from the custom palette (indexed by that byte value
minus 128). Byte values in the range [192, 256) mean the value of a CREG color
register (with CREG indexed by that byte value minus 192).
For a 2 byte encoding, the red, green, blue and alpha values are all 4 bit
values. For example, the color 338800ff can be encoded as 0x38 0x0f.
For a 3 byte direct encoding, the red, green and blue values are all 8 bit
values. The alpha value is implicitly 255. For example, the color 306607ff can
be encoded as 0x30 0x66 0x07.
For a 4 byte encoding, the red, green, blue and alpha values are all 8 bit
values. For example, the color 30660780 is simply 0x30 0x66 0x07 0x80.
For a 3 byte indirect encoding, the first byte is an integer value in the range
[0, 255] (denoted T) and the second and third bytes are each a 1 byte encoded
color (denoted C0 and C1). The resultant color's red channel value is given by:
RESULTANT.RED = (((255-T) * C0.RED) + (T * C1.RED) + 128) / 255
rounded down to an integer value (the mathematical floor function), and
likewise for the green, blue and alpha channels. For example, if the custom
palette's third entry is a fully opaque orange, then 0x40 0x7f 0x82 encodes a
25% percent opaque orange: a blend of 75% times fully transparent (1 byte color
0x7f) and 25% times a fully opaque orange (1 byte color 0x82).
It is valid for some encodings to yield a color value where the red, green or
blue value is greater than the alpha value, as this may be a gradient. If it
isn't a gradient, the subsequent rendering is undefined.
Palettes
Rendering an IconVG graphic can be varied by a 64 color palette. For example,
an emoji graphic may be rendered with palette color 0 for skin and palette
color 1 for hair. Decorative variations, such as different clothing, can be
implemented by palette colors possibly set to completely transparent to switch
paths off.
Rendering software should allow users to pass an optional 64 color palette. If
one isn't provided, the suggested palette (either given in the metadata or the
default consisting entirely of opaque black) should be used. Whichever palette
ends up being used is designated the custom palette.
Some user-given colors may be nonsensical as alpha-premultiplied colors, where
e.g. the red value is greater than the alpha value. Such colors are replaced by
opaque black and not re-interpreted as gradients.
Assigning names such as "skin", "hair" or "bow_tie" to the integer indexes of
that 64 color palette is out of scope of the IconVG format per se.
Numbers
Like colors, numbers are encoded in the instruction stream in either 1, 2 or 4
bytes. Unlike colors, the encoding length is not determined by context.
Instead, the low two bits of the first byte indicate the encoding length.
If the least significant bit (the 0x01 bit) of the first byte is 0, the number
is encoded in 1 byte. Otherwise, it is encoded in 2 or 4 bytes depending on the
second least significant bit (the 0x02 bit) of the first byte being 0 or 1.
Natural Numbers
For a 1 byte encoding, the remaining 7 bits form an integer value in the range
[0, 1<<7). For example, 0x28 encodes the value 0x14 or, in decimal, 20.
For a 2 byte encoding, the remaining 14 bits, interpreted as little endian,
form an integer in the range [0, 1<<14). For example, 0x59 0x83 encodes the
value 0x20d6 or, in decimal, 8406.
For a 4 byte encoding, the remaining 30 bits, interpreted as little endian,
form an integer in the range [0, 1<<30). For example, 0x07 0x00 0x80 0x3f
encodes the value 0xfe00001 or, in decimal, 266338305.
Real Numbers
The encoding of a real number resembles the encoding of a natural number. For 1
and 2 byte encodings, the decoded real number equals the decoded natural
number. For example, 0x28 encodes the real number 20.0, just as it encodes the
natural number 20.
For a 4 byte encoding, the decoded natural number, in the range [0, 1<<30), is
shifted left by 2, to make a uint32 value that is a multiple of 4. The decoded
real number is the floating point number corresponding to the IEEE 754 binary
representation of that uint32 (i.e. a reinterpretation as a float32). For
example, 0x07 0x00 0x80 0x3f encodes the value 1.000000476837158203125.
It is valid for the 4 byte encoding to represent infinities and NaNs, but if
not loaded into LOD0 or LOD1, the subsequent rendering is undefined.
Coordinate Numbers
The encoding of a coordinate number resembles the encoding of a real number.
For 1 and 2 byte encodings, the decoded coordinate number equals (R*scale -
bias), where R is the decoded real number as above. The scale and bias depends
on the number of bytes in the encoding.
For a 1 byte encoding, the scale is 1 and the bias is 64, so that a 1 byte
coordinate ranges in [-64, +64) at integer granularity. For example, the
coordinate 7 can be encoded as 0x8e.
For a 2 byte encoding, the scale is 1/64 and the bias is 128, so that a 2 byte
coordinate ranges in [-128, +128) at 1/64 granularity. For example, the
coordinate 7.5 can be encoded as 0x81 0x87.
For a 4 byte encoding, the decoded coordinate number simply equals R. For
example, the coordinate 7.5 can also be encoded as 0x03 0x00 0xf0 0x40.
Zero-to-One Numbers
A zero-to-one number is a real number that is typically in the range [0, 1],
although it is valid for a value to be outside of that range. For example,
angles are expressed as a zero-to-one number: a fraction of a complete
revolution (360 degrees). Gradient stop offsets are another example.
The encoding of a zero-to-one number resembles the encoding of a real number.
For 1 and 2 byte encodings, the decoded number equals R*scale, where R is the
decoded real number as above. The scale depends on the number of bytes in the
encoding.
For a 1 byte encoding, the real number (ranging up to 128) is scaled by 1/120.
The denominator is 2*2*2 * 3 * 5, so that 15 degrees (2*π/24 radians) can be
encoded as 0x0a.
For a 2 byte encoding, the real number (ranging up to 16384) is scaled by
1/15120. The denominator is 2*2*2*2 * 3*3*3 * 5 * 7. For example, 40 degrees
(2*π/9 radians) can be encoded as 0x41 0x1a.
For a 4 byte encoding, the decoded zero-to-one number simply equals R. For
example, 1 degree (2*π/360 radians), or 0.002777777..., can be approximated by
the encoding 0x63 0x0b 0x36 0x3b.
Magic Identifier
An IconVG graphic starts with the four bytes 0x89 0x49 0x56 0x47 ("\x89IVG").
Metadata
The encoded metadata starts with a natural number (see encoding above) of the
number of metadata chunks in the metadata, followed by that many chunks. Each
chunk starts with the length remaining in the chunk (again, encoded as a
natural number), not including the chunk length itself. After that is a MID
(Metadata Identifier) natural number, then MID-specific data. Chunks must be
presented in increasing MID order. MIDs cannot be repeated. All MIDs are
optional.
MID 0 - ViewBox
Metadata Identifier 0 means that the MID-specific data contains four coordinate
values (see above for the coordinate encoding). These are the minX, minY, maxX,
maxY of the graphic's viewBox: its bounding rectangle in (scalable) vector
space. Note that these are abstract units, and not necessarily 1:1 with pixels.
If this MID is not present, the viewBox defaults to (-32, -32, +32, +32). A
viewBox is invalid if minX > maxX or if minY > maxY or if at least one of those
four values are a NaN or an infinity.
MID 1 - Suggested Palette
Metadata Identifier 1 means that the MID-specific data contains a suggested
palette, e.g. to provide a default rendering of variable colors such as an
emoji's skin and hair. The suggested palette is encoded in at least one byte.
The low 6 bits of that byte form a number N. The high 2 bits denote the palette
color format: those high 2 bits being 0, 1, 2 or 3 mean 1, 2, 3 (direct) or 4
byte colors (see above for the color encoding). The chunk then contains N+1
explicit colors, in that 1, 2, 3 or 4 byte encoding. A palette has exactly 64
colors, the 63-N implicit colors of the suggested palette are set to opaque
black. A 1 byte color that refers to the custom palette or a CREG color
register resolves to opaque black. If this MID is not present, the suggested
palette consists entirely of opaque black, as black is always fashionable.
Styling Opcodes
Some opcode descriptions refer to an adjustment value, ADJ. That value is the
low three bits of the opcode, nominally in the range [0, 8), although in
practice the range is [0, 7) as no ADJ-using opcode has the low three bits set.
Opcodes 0x00 to 0x3f sets CSEL to the low 6 bits of the opcode.
Opcodes 0x40 to 0x7f sets NSEL to the low 6 bits of the opcode.
Opcodes 0x80 to 0x86 sets CREG[CSEL-ADJ] to the 1 byte encoded color following
the opcode.
Opcodes 0x88 to 0x8e sets CREG[CSEL-ADJ] to the 2 byte encoded color following
the opcode.
Opcodes 0x90 to 0x96 sets CREG[CSEL-ADJ] to the 3 byte direct encoded color
following the opcode.
Opcodes 0x98 to 0x9e sets CREG[CSEL-ADJ] to the 4 byte encoded color following
the opcode.
Opcodes 0xa0 to 0xa6 sets CREG[CSEL-ADJ] to the 3 byte indirect encoded color
following the opcode.
Opcodes 0x87, 0x8f, 0x97, 0x9f and 0xa7 sets CREG[CSEL] to the 1, 2, 3
(direct), 4 and 3 (indirect) byte encoded color, following the opcode, and then
increments CSEL by 1.
Opcodes 0xa8 to 0xae sets NREG[NSEL-ADJ] to the real number following the
opcode.
Opcodes 0xb0 to 0xb6 sets NREG[NSEL-ADJ] to the coordinate number following the
opcode.
Opcodes 0xb8 to 0xbe sets NREG[NSEL-ADJ] to the zero-to-one number following
the opcode.
Opcode 0xaf, 0xb7 and 0xbf sets NREG[NSEL] to the real, coordinate and
zero-to-one number following the opcode, and then increments NSEL by 1.
Opcodes 0xc0 to 0xc6 switches to the drawing mode, and is followed by two
coordinates that is the path's starting location. In effect, there is an
implicit M (absolute moveto) op. CREG[CSEL-ADJ], either a flat color or a
gradient, will fill the path once it is complete.
Opcode 0xc7 sets the Level of Detail bounds LOD0 and LOD1 to the two real
numbers following the opcode.
All other opcodes are reserved.
Drawing Opcodes
The drawing model is based on SVG path data
(https://www.w3.org/TR/SVG/paths.html#PathData) and this description re-uses
SVG's one-letter mnemonics: M means absolute moveto, m means relative moveto, L
means absolute lineto, l means relative lineto, H means absolute horizontal
lineto, etc. Upper and lower case mean absolute and relative coordinates. The
upper case mnemonics of the SVG operations used in IconVG's drawing mode are:
M, Z, L, H, V, C, S, Q, T, A.
IconVG differs from SVG with multiple consecutive moveto ops. SVG treats all
but the first one as lineto ops. IconVG treats them all as moveto ops.
Almost all opcodes, i.e. those in the range [0x00, 0xdf], come in contiguous
groups of 16 or 32. For example, there are 16 Q (absolute quadratic Bézier
curveto) opcodes, from 0x60 to 0x6f. Those opcodes' meaning differ only in
their repeat count RC: how often that drawing operation is repeated. The lowest
valued opcode has a repeat count of 1, the next lowest has a repeat count of 2
and so on. For example, the opcode 0x68 means 9 consecutive Q drawing ops.
Opcodes 0x00 to 0x1f means RC consecutive L ops, for RC in [1, 32]. The opcode
is followed by 2*RC coordinates, RC sets of (x, y).
Opcodes 0x20 to 0x3f are like the previous paragraph, except L (absolute)
becomes l (relative).
Opcodes 0x40 to 0x4f means RC consecutive T ops, for RC in [1, 16]. The opcode
is followed by 2*RC coordinates, RC sets of (x, y).
Opcodes 0x50 to 0x5f are like the previous paragraph, except T (absolute)
becomes t (relative).
Opcodes 0x60 to 0x6f means RC consecutive Q ops, for RC in [1, 16]. The opcode
is followed by 4*RC coordinates, RC sets of (x1, y1, x, y).
Opcodes 0x70 to 0x7f are like the previous paragraph, except Q (absolute)
becomes q (relative).
Opcodes 0x80 to 0x8f means RC consecutive S ops, for RC in [1, 16]. The opcode
is followed by 4*RC coordinates, RC sets of (x2, y2, x, y).
Opcodes 0x90 to 0x9f are like the previous paragraph, except S (absolute)
becomes s (relative).
Opcodes 0xa0 to 0xaf means RC consecutive C ops, for RC in [1, 16]. The opcode
is followed by 6*RC coordinates, RC sets of (x1, y1, x2, y2, x, y).
Opcodes 0xb0 to 0xbf are like the previous paragraph, except C (absolute)
becomes c (relative).
Opcodes 0xc0 to 0xcf means RC consecutive A ops, for RC in [1, 16]. The opcode
is followed by 6*RC numbers, RC sets of (rx, ry, xAxisRotation, flags, x, y).
The rx, ry, x and y numbers are coordinates. The xAxisRotation number is an
angle (a zero-to-one number being a fraction of 360 degrees). The flags are
encoded as a natural number. The 0x01 bit of the decoded natural number is the
large-arc-flag and the 0x02 bit is the sweep-flag.
Opcodes 0xd0 to 0xdf are like the previous paragraph, except A (absolute)
becomes a (relative).
Opcode 0xe0 is reserved. (A future version of IconVG may use this opcode to
mean the same as 0xe1 without the one z op, which will matter for stroked
paths).
Opcode 0xe1 means one z op and then end the path: fill the path with the color
chosen when we switched to the drawing mode, and switch back to the styling
mode. (The Z and z ops are equivalent).
Opcode 0xe2 means one z op and then an implicit M op to the (x, y) coordinates
after the opcode.
Opcode 0xe3 means one z op and then an implicit m op to the (x, y) coordinates
after the opcode.
Opcodes 0xe4 and 0xe5 are reserved. (A future version of IconVG may use these
for M and m ops, if we allow stroked paths).
Opcode 0xe6 means one H op to the x coordinate after the opcode.
Opcode 0xe7 means one h op to the x coordinate after the opcode.
Opcode 0xe8 means one V op to the y coordinate after the opcode.
Opcode 0xe9 means one v op to the y coordinate after the opcode.
All other opcodes are reserved.
These opcode descriptions all assume that the Level of Detail constraint (see
above) is satisfied. If not, the opcodes and their variable length data are
consumed, but no further action is taken (other than leaving drawing mode).
Example
The production version of the "action/info" icon from the Material Design icon
set is defined by the following SVG, also available at
https://github.com/google/material-design-icons/blob/master/action/svg/production/ic_info_48px.svg:
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
<path d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4z
m2 30h-4V22h4v12zm0-16h-4v-4h4v4z"/></svg>
This is 202 bytes, or 174 bytes after "gzip --best". The PNG renderings at
various sizes:
18x18: 207 bytes
24x24: 222 bytes
36x36: 321 bytes
48x48: 412 bytes
The corresponding IconVG is 73 bytes:
89 49 56 47 02 0a 00 50 50 b0 b0 c0 80 58 a0 cf
cc 30 c1 58 58 cf cc 30 c1 58 80 91 37 33 0f 41
a8 a8 a8 a8 37 33 0f c1 a8 58 80 cf cc 30 41 58
80 58 e3 84 bc e7 78 e8 7c e7 88 e9 98 e3 80 60
e7 78 e9 78 e7 88 e9 88 e1
The annotated version is below. Note that the IconVG viewBox ranges from -24 to
+24 while the SVG viewBox ranges from 0 to 48.
89 49 56 47 IconVG Magic identifier
02 Number of metadata chunks: 1
0a Metadata chunk length: 5
00 Metadata Identifier: 0 (viewBox)
50 -24
50 -24
b0 +24
b0 +24
c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
80 +0
58 -20
a0 C (absolute cubeTo), 1 reps
cf cc 30 c1 -11.049999
58 -20
58 -20
cf cc 30 c1 -11.049999
58 -20
80 +0
91 s (relative smooth cubeTo), 2 reps
37 33 0f 41 +8.950001
a8 +20
a8 +20
a8 +20
s (relative smooth cubeTo), implicit
a8 +20
37 33 0f c1 -8.950001
a8 +20
58 -20
80 S (absolute smooth cubeTo), 1 reps
cf cc 30 41 +11.049999
58 -20
80 +0
58 -20
e3 z (closePath); m (relative moveTo)
84 +2
bc +30
e7 h (relative horizontal lineTo)
78 -4
e8 V (absolute vertical lineTo)
7c -2
e7 h (relative horizontal lineTo)
88 +4
e9 v (relative vertical lineTo)
98 +12
e3 z (closePath); m (relative moveTo)
80 +0
60 -16
e7 h (relative horizontal lineTo)
78 -4
e9 v (relative vertical lineTo)
78 -4
e7 h (relative horizontal lineTo)
88 +4
e9 v (relative vertical lineTo)
88 +4
e1 z (closePath); end path
There are more examples in the ./testdata directory.
Appendix - Gradient Transformation Matrices
This appendix derives the affine transformation matrices [a, b, c; d, e, f] for
linear, circular and elliptical gradients.
Linear Gradients
For a linear gradient from (x1, y1) to (x2, y2), let dx, dy = x2-x1, y2-y1. In
gradient coordinate space, the y-coordinate is ignored, so the transformation
matrix simplifies to [a, b, c; 0, 0, 0]. It satisfies the three simultaneous
equations:
a*(x1 ) + b*(y1 ) + c = 0 (eq L.0)
a*(x1+dy) + b*(y1-dx) + c = 0 (eq L.1)
a*(x1+dx) + b*(y1+dy) + c = 1 (eq L.2)
Subtracting equation L.0 from equations L.1 and L.2 yields:
a*dy - b*dx = 0
a*dx + b*dy = 1
So that
a*dy*dy - b*dx*dy = 0
a*dx*dx + b*dx*dy = dx
Overall:
a = dx / (dx*dx + dy*dy)
b = dy / (dx*dx + dy*dy)
c = -a*x1 - b*y1
d = 0
e = 0
f = 0
Circular Gradients
For a circular gradient with center (cx, cy) and radius vector (rx, ry), such
that (cx+rx, cy+ry) is on the circle, let
r = math.Sqrt(rx*rx + ry*ry)
The transformation matrix maps (cx, cy) to (0, 0), maps (cx+r, cy) to (1, 0)
and maps (cx, cy+r) to (0, 1). Solving those six simultaneous equations give:
a = +1 / r
b = +0 / r
c = -cx / r
d = +0 / r
e = +1 / r
f = -cy / r
Elliptical Gradients
For an elliptical gradient with center (cx, cy) and axis vectors (rx, ry) and
(sx, sy), such that (cx+rx, cx+ry) and (cx+sx, cx+sy) are on the ellipse, the
transformation matrix satisfies the six simultaneous equations:
a*(cx ) + b*(cy ) + c = 0 (eq E.0)
a*(cx+rx) + b*(cy+ry) + c = 1 (eq E.1)
a*(cx+sx) + b*(cy+sy) + c = 0 (eq E.2)
d*(cx ) + e*(cy ) + f = 0 (eq E.3)
d*(cx+rx) + e*(cy+ry) + f = 0 (eq E.4)
d*(cx+sx) + e*(cy+sy) + f = 1 (eq E.5)
Subtracting equation E.0 from equations E.1 and E.2 yields:
a*rx + b*ry = 1
a*sx + b*sy = 0
Solving these two simultaneous equations yields:
a = +sy / (rx*sy - sx*ry)
b = -sx / (rx*sy - sx*ry)
Re-arranging E.0 yields:
c = -a*cx - b*cy
Similarly for d, e and f so that, overall:
a = +sy / (rx*sy - sx*ry)
b = -sx / (rx*sy - sx*ry)
c = -a*cx - b*cy
d = -ry / (rx*sy - sx*ry)
e = +rx / (rx*sy - sx*ry)
f = -d*cx - e*cy
Note that if rx = r, ry = 0, sx = 0 and sy = r then this simplifies to the
circular gradient transformation matrix formula, above.
*/
package iconvg
// TODO: 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?