1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
// Package sixel provides sixel graphics format functionality.
package sixel
import (
"fmt"
"image/color"
"io"
"github.com/lucasb-eyer/go-colorful"
)
// ErrInvalidColor is returned when a Sixel color is invalid.
var ErrInvalidColor = fmt.Errorf("invalid color")
// WriteColor writes a Sixel color to a writer. If pu is 0, the rest of the
// parameters are ignored.
func WriteColor(w io.Writer, pc, pu, px, py, pz int) (int, error) {
if pu <= 0 || pu > 2 {
return fmt.Fprintf(w, "#%d", pc) //nolint:wrapcheck
}
return fmt.Fprintf(w, "#%d;%d;%d;%d;%d", pc, pu, px, py, pz) //nolint:wrapcheck
}
// ConvertChannel converts a color channel from color.Color 0xffff to 0-100
// Sixel RGB format.
func ConvertChannel(c uint32) uint32 {
// We add 328 because that is about 0.5 in the sixel 0-100 color range, we're trying to
// round to the nearest value
return (c + 328) * 100 / 0xffff
}
// FromColor returns a Sixel color from a color.Color. It converts the color
// channels to the 0-100 range.
func FromColor(c color.Color) Color {
if c == nil {
return Color{}
}
r, g, b, _ := c.RGBA()
return Color{
Pu: 2, // Always use RGB format "2"
Px: int(ConvertChannel(r)),
Py: int(ConvertChannel(g)),
Pz: int(ConvertChannel(b)),
}
}
// DecodeColor decodes a Sixel color from a byte slice. It returns the Color and
// the number of bytes read.
func DecodeColor(data []byte) (c Color, n int) {
if len(data) == 0 || data[0] != ColorIntroducer {
return c, n
}
if len(data) < 2 { // The minimum length is 2: the introducer and a digit.
return c, n
}
// Parse the color number and optional color system.
pc := &c.Pc
for n = 1; n < len(data); n++ {
if data[n] == ';' {
if pc == &c.Pc {
pc = &c.Pu
} else {
n++
break
}
} else if data[n] >= '0' && data[n] <= '9' {
*pc = (*pc)*10 + int(data[n]-'0')
} else {
break
}
}
// Parse the color components.
ptr := &c.Px
for ; n < len(data); n++ {
if data[n] == ';' { //nolint:nestif
if ptr == &c.Px {
ptr = &c.Py
} else if ptr == &c.Py {
ptr = &c.Pz
} else {
n++
break
}
} else if data[n] >= '0' && data[n] <= '9' {
*ptr = (*ptr)*10 + int(data[n]-'0')
} else {
break
}
}
return c, n
}
// Color represents a Sixel color.
type Color struct {
// Pc is the color number (0-255).
Pc int
// Pu is an optional color system
// - 0: default color map
// - 1: HLS
// - 2: RGB
Pu int
// Color components range from 0-100 for RGB values. For HLS format, the Px
// (Hue) component ranges from 0-360 degrees while L (Lightness) and S
// (Saturation) are 0-100.
Px, Py, Pz int
}
// RGBA implements the color.Color interface.
func (c Color) RGBA() (r, g, b, a uint32) {
switch c.Pu {
case 1:
return sixelHLS(c.Px, c.Py, c.Pz).RGBA()
case 2:
return sixelRGB(c.Px, c.Py, c.Pz).RGBA()
default:
return colorPalette[c.Pc].RGBA()
}
}
// #define PALVAL(n,a,m) (((n) * (a) + ((m) / 2)) / (m))
func palval(n, a, m int) int {
return (n*a + m/2) / m
}
func sixelRGB(r, g, b int) color.Color {
return color.NRGBA{uint8(palval(r, 0xff, 100)), uint8(palval(g, 0xff, 100)), uint8(palval(b, 0xff, 100)), 0xFF} //nolint:gosec
}
func sixelHLS(h, l, s int) color.Color {
return colorful.Hsl(float64(h), float64(s)/100.0, float64(l)/100.0).Clamped()
}
|