iconvg

package
v0.0.0-...-8b1c86b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 14, 2023 License: BSD-3-Clause Imports: 10 Imported by: 0

Documentation

Overview

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.

Example
package main

import (
	"image"
	"image/draw"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"

	"golang.org/x/exp/shiny/iconvg"
)

func main() {
	ivgData, err := ioutil.ReadFile(filepath.FromSlash("testdata/action-info.lores.ivg"))
	if err != nil {
		log.Fatal(err)
	}

	const width = 24
	dst := image.NewAlpha(image.Rect(0, 0, width, width))
	var z iconvg.Rasterizer
	z.SetDstImage(dst, dst.Bounds(), draw.Src)
	if err := iconvg.Decode(&z, ivgData, nil); err != nil {
		log.Fatal(err)
	}

	const asciiArt = ".++8"
	buf := make([]byte, 0, width*(width+1))
	for y := 0; y < width; y++ {
		for x := 0; x < width; x++ {
			a := dst.AlphaAt(x, y).A
			buf = append(buf, asciiArt[a>>6])
		}
		buf = append(buf, '\n')
	}
	os.Stdout.Write(buf)

}
Output:

........................
........................
........++8888++........
......+8888888888+......
.....+888888888888+.....
....+88888888888888+....
...+8888888888888888+...
...88888888..88888888...
..+88888888..88888888+..
..+888888888888888888+..
..88888888888888888888..
..888888888..888888888..
..888888888..888888888..
..888888888..888888888..
..+88888888..88888888+..
..+88888888..88888888+..
...88888888..88888888...
...+8888888888888888+...
....+88888888888888+....
.....+888888888888+.....
......+8888888888+......
........++8888++........
........................
........................

Index

Examples

Constants

This section is empty.

Variables

View Source
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},
}

DefaultPalette is the default Palette. Its values should not be modified.

View Source
var DefaultViewBox = Rectangle{
	Min: f32.Vec2{-32, -32},
	Max: f32.Vec2{+32, +32},
}

DefaultViewBox is the default ViewBox. Its values should not be modified.

Functions

func Decode

func Decode(dst Destination, src []byte, opts *DecodeOptions) error

Decode decodes an IconVG graphic.

Types

type Color

type Color struct {
	// contains filtered or unexported fields
}

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.

func BlendColor

func BlendColor(t, c0, c1 uint8) Color

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 CRegColor

func CRegColor(i uint8) Color

CRegColor returns an indirect Color referring to a color register of the decoder virtual machine.

func PaletteIndexColor

func PaletteIndexColor(i uint8) Color

PaletteIndexColor returns an indirect Color referring to an index of the custom palette.

func RGBAColor

func RGBAColor(c color.RGBA) Color

RGBAColor returns a direct Color.

func (Color) Resolve

func (c Color) Resolve(pal *Palette, cReg *[64]color.RGBA) color.RGBA

Resolve resolves the Color's RGBA value, given its context: the custom palette and the color registers of the decoder virtual machine.

type ColorType

type ColorType uint8

ColorType distinguishes types of Colors.

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
)

type DecodeOptions

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
}

DecodeOptions are the optional parameters to the Decode function.

type Destination

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)
}

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 Encoder

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
	// contains filtered or unexported fields
}

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.

func (*Encoder) AbsArcTo

func (e *Encoder) AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)

func (*Encoder) AbsCubeTo

func (e *Encoder) AbsCubeTo(x1, y1, x2, y2, x, y float32)

func (*Encoder) AbsHLineTo

func (e *Encoder) AbsHLineTo(x float32)

func (*Encoder) AbsLineTo

func (e *Encoder) AbsLineTo(x, y float32)

func (*Encoder) AbsQuadTo

func (e *Encoder) AbsQuadTo(x1, y1, x, y float32)

func (*Encoder) AbsSmoothCubeTo

func (e *Encoder) AbsSmoothCubeTo(x2, y2, x, y float32)

func (*Encoder) AbsSmoothQuadTo

func (e *Encoder) AbsSmoothQuadTo(x, y float32)

func (*Encoder) AbsVLineTo

func (e *Encoder) AbsVLineTo(y float32)

func (*Encoder) Bytes

func (e *Encoder) Bytes() ([]byte, error)

Bytes returns the encoded form.

func (*Encoder) CSel

func (e *Encoder) CSel() uint8

func (*Encoder) ClosePathAbsMoveTo

func (e *Encoder) ClosePathAbsMoveTo(x, y float32)

func (*Encoder) ClosePathEndPath

func (e *Encoder) ClosePathEndPath()

func (*Encoder) ClosePathRelMoveTo

func (e *Encoder) ClosePathRelMoveTo(x, y float32)

func (*Encoder) LOD

func (e *Encoder) LOD() (lod0, lod1 float32)

func (*Encoder) NSel

func (e *Encoder) NSel() uint8

func (*Encoder) RelArcTo

func (e *Encoder) RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)

func (*Encoder) RelCubeTo

func (e *Encoder) RelCubeTo(x1, y1, x2, y2, x, y float32)

func (*Encoder) RelHLineTo

func (e *Encoder) RelHLineTo(x float32)

func (*Encoder) RelLineTo

func (e *Encoder) RelLineTo(x, y float32)

func (*Encoder) RelQuadTo

func (e *Encoder) RelQuadTo(x1, y1, x, y float32)

func (*Encoder) RelSmoothCubeTo

func (e *Encoder) RelSmoothCubeTo(x2, y2, x, y float32)

func (*Encoder) RelSmoothQuadTo

func (e *Encoder) RelSmoothQuadTo(x, y float32)

func (*Encoder) RelVLineTo

func (e *Encoder) RelVLineTo(y float32)

func (*Encoder) Reset

func (e *Encoder) Reset(m Metadata)

Reset resets the Encoder for the given Metadata.

This includes setting e.HighResolutionCoordinates to false.

func (*Encoder) SetCReg

func (e *Encoder) SetCReg(adj uint8, incr bool, c Color)

func (*Encoder) SetCSel

func (e *Encoder) SetCSel(cSel uint8)

func (*Encoder) SetCircularGradient

func (e *Encoder) SetCircularGradient(cBase, nBase uint8, cx, cy, rx, ry float32, spread GradientSpread, stops []GradientStop)

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 (*Encoder) SetEllipticalGradient

func (e *Encoder) SetEllipticalGradient(cBase, nBase uint8, cx, cy, rx, ry, sx, sy float32, spread GradientSpread, stops []GradientStop)

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 (*Encoder) SetGradient

func (e *Encoder) SetGradient(cBase, nBase uint8, radial bool, transform f32.Aff3, spread GradientSpread, stops []GradientStop)

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 (*Encoder) SetLOD

func (e *Encoder) SetLOD(lod0, lod1 float32)

func (*Encoder) SetLinearGradient

func (e *Encoder) SetLinearGradient(cBase, nBase uint8, x1, y1, x2, y2 float32, spread GradientSpread, stops []GradientStop)

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 (*Encoder) SetNReg

func (e *Encoder) SetNReg(adj uint8, incr bool, f float32)

func (*Encoder) SetNSel

func (e *Encoder) SetNSel(nSel uint8)

func (*Encoder) StartPath

func (e *Encoder) StartPath(adj uint8, x, y float32)

type GradientSpread

type GradientSpread uint8

GradientSpread is how to spread a gradient past its nominal bounds (from offset being 0.0 to offset being 1.0).

const (
	GradientSpreadNone    GradientSpread = 0
	GradientSpreadPad     GradientSpread = 1
	GradientSpreadReflect GradientSpread = 2
	GradientSpreadRepeat  GradientSpread = 3
)

type GradientStop

type GradientStop struct {
	Offset float32
	Color  color.Color
}

GradientStop is a color/offset gradient stop.

type 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
}

Metadata is an IconVG's metadata.

func DecodeMetadata

func DecodeMetadata(src []byte) (m Metadata, err error)

DecodeMetadata decodes only the metadata in an IconVG graphic.

type Palette

type Palette [64]color.RGBA

Palette is an IconVG palette.

type Rasterizer

type Rasterizer struct {
	// contains filtered or unexported fields
}

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.

func (*Rasterizer) AbsArcTo

func (z *Rasterizer) AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)

func (*Rasterizer) AbsCubeTo

func (z *Rasterizer) AbsCubeTo(x1, y1, x2, y2, x, y float32)

func (*Rasterizer) AbsHLineTo

func (z *Rasterizer) AbsHLineTo(x float32)

func (*Rasterizer) AbsLineTo

func (z *Rasterizer) AbsLineTo(x, y float32)

func (*Rasterizer) AbsQuadTo

func (z *Rasterizer) AbsQuadTo(x1, y1, x, y float32)

func (*Rasterizer) AbsSmoothCubeTo

func (z *Rasterizer) AbsSmoothCubeTo(x2, y2, x, y float32)

func (*Rasterizer) AbsSmoothQuadTo

func (z *Rasterizer) AbsSmoothQuadTo(x, y float32)

func (*Rasterizer) AbsVLineTo

func (z *Rasterizer) AbsVLineTo(y float32)

func (*Rasterizer) ClosePathAbsMoveTo

func (z *Rasterizer) ClosePathAbsMoveTo(x, y float32)

func (*Rasterizer) ClosePathEndPath

func (z *Rasterizer) ClosePathEndPath()

func (*Rasterizer) ClosePathRelMoveTo

func (z *Rasterizer) ClosePathRelMoveTo(x, y float32)

func (*Rasterizer) RelArcTo

func (z *Rasterizer) RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)

func (*Rasterizer) RelCubeTo

func (z *Rasterizer) RelCubeTo(x1, y1, x2, y2, x, y float32)

func (*Rasterizer) RelHLineTo

func (z *Rasterizer) RelHLineTo(x float32)

func (*Rasterizer) RelLineTo

func (z *Rasterizer) RelLineTo(x, y float32)

func (*Rasterizer) RelQuadTo

func (z *Rasterizer) RelQuadTo(x1, y1, x, y float32)

func (*Rasterizer) RelSmoothCubeTo

func (z *Rasterizer) RelSmoothCubeTo(x2, y2, x, y float32)

func (*Rasterizer) RelSmoothQuadTo

func (z *Rasterizer) RelSmoothQuadTo(x, y float32)

func (*Rasterizer) RelVLineTo

func (z *Rasterizer) RelVLineTo(y float32)

func (*Rasterizer) Reset

func (z *Rasterizer) Reset(m Metadata)

Reset resets the Rasterizer for the given Metadata.

func (*Rasterizer) SetCReg

func (z *Rasterizer) SetCReg(adj uint8, incr bool, c Color)

func (*Rasterizer) SetCSel

func (z *Rasterizer) SetCSel(cSel uint8)

func (*Rasterizer) SetDstImage

func (z *Rasterizer) SetDstImage(dst draw.Image, r image.Rectangle, drawOp draw.Op)

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 (*Rasterizer) SetLOD

func (z *Rasterizer) SetLOD(lod0, lod1 float32)

func (*Rasterizer) SetNReg

func (z *Rasterizer) SetNReg(adj uint8, incr bool, f float32)

func (*Rasterizer) SetNSel

func (z *Rasterizer) SetNSel(nSel uint8)

func (*Rasterizer) StartPath

func (z *Rasterizer) StartPath(adj uint8, x, y float32)

type Rectangle

type Rectangle struct {
	Min, Max f32.Vec2
}

Rectangle is defined by its minimum and maximum coordinates.

func (*Rectangle) AspectRatio

func (r *Rectangle) AspectRatio() (dx, dy float32)

AspectRatio returns the Rectangle's aspect ratio. An IconVG graphic is scalable; these dimensions do not necessarily map 1:1 to pixels.

Directories

Path Synopsis
internal
gradient
Package gradient provides linear and radial gradient images.
Package gradient provides linear and radial gradient images.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL