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