Skip to content

Commit

Permalink
Update decoder and encoder
Browse files Browse the repository at this point in the history
Co-authored-by: Ayman Bagabas <ayman.bagabas@gmail.com>
  • Loading branch information
raphamorim and aymanbagabas committed Feb 21, 2025
1 parent a68a7ba commit 4cece45
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 29 deletions.
50 changes: 38 additions & 12 deletions ansi/sixel/color.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,37 @@ 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 uint) (int, error) {
func WriteColor(w io.Writer, pc, pu, px, py, pz int) (int, error) {
if pu <= 0 || pu > 2 {
return fmt.Fprintf(w, "#%d", pc)
}

return fmt.Fprintf(w, "#%d;%d;%d;%d;%d", pc, pu, px, py, pz)
}

// 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 {
r, g, b, a := c.RGBA()
if a == 0 {
return Color{Pu: 3} // Transparent color
}
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) {
Expand All @@ -43,7 +66,7 @@ func DecodeColor(data []byte) (c Color, n int) {
break
}
} else if data[n] >= '0' && data[n] <= '9' {
*pc = (*pc)*10 + data[n] - '0'
*pc = (*pc)*10 + int(data[n]-'0')
} else {
break
}
Expand All @@ -62,7 +85,7 @@ func DecodeColor(data []byte) (c Color, n int) {
break
}
} else if data[n] >= '0' && data[n] <= '9' {
*ptr = (*ptr)*10 + uint(data[n]-'0')
*ptr = (*ptr)*10 + int(data[n]-'0')
} else {
break
}
Expand All @@ -74,13 +97,16 @@ func DecodeColor(data []byte) (c Color, n int) {
// Color represents a Sixel color.
type Color struct {
// Pc is the color number (0-255).
Pc uint8
// Pu is an optional color system (1: HLS, 2: RGB, 0: default color map).
Pu uint8
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 uint
Px, Py, Pz int
}

// RGBA implements the color.Color interface.
Expand All @@ -95,7 +121,7 @@ func (c Color) RGBA() (r, g, b, a uint32) {
}
}

var colors = map[uint8]color.Color{
var colors = map[int]color.Color{
// 16 predefined color registers of VT340
0: sixelRGB(0, 0, 0),
1: sixelRGB(20, 20, 80),
Expand All @@ -116,14 +142,14 @@ var colors = map[uint8]color.Color{
}

// #define PALVAL(n,a,m) (((n) * (a) + ((m) / 2)) / (m))
func palval(n, a, m uint) uint {
func palval(n, a, m int) int {
return (n*a + m/2) / m
}

func sixelRGB(r, g, b uint) color.Color {
return color.RGBA{uint8(palval(r, 0xff, 100)), uint8(palval(g, 0xff, 100)), uint8(palval(b, 0xff, 100)), 0xFF} //nolint:gosec
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 uint) color.Color {
func sixelHLS(h, l, s int) color.Color {
return colorful.Hsl(float64(h), float64(s)/100.0, float64(l)/100.0).Clamped()
}
22 changes: 11 additions & 11 deletions ansi/sixel/color_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (
func TestWriteColor(t *testing.T) {
tests := []struct {
name string
pc uint
pu uint
px uint
py uint
pz uint
pc int
pu int
px int
py int
pz int
expected string
}{
{
Expand Down Expand Up @@ -181,9 +181,9 @@ func TestColor_RGBA(t *testing.T) {
func TestSixelRGB(t *testing.T) {
tests := []struct {
name string
r uint
g uint
b uint
r int
g int
b int
want color.Color
}{
{
Expand Down Expand Up @@ -231,9 +231,9 @@ func TestSixelRGB(t *testing.T) {
func TestSixelHLS(t *testing.T) {
tests := []struct {
name string
h uint
l uint
s uint
h int
l int
s int
want color.Color
}{
{
Expand Down
14 changes: 9 additions & 5 deletions ansi/sixel/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ func (d *Decoder) Decode(r io.Reader) (image.Image, error) {
var currentX, currentBandY, currentPaletteIndex int

// data buffer used to decode Sixel commands
data := make([]byte, 0, 16) // arbitrary number of bytes to read
data := make([]byte, 0, 6) // arbitrary number of bytes to read
// i := 0 // keeps track of the data buffer index
for {
b, err := rd.ReadByte()
if err != nil {
Expand All @@ -364,7 +365,7 @@ func (d *Decoder) Decode(r io.Reader) (image.Image, error) {
case b == CarriageReturn: // CR
currentX = 0
case b == ColorIntroducer: // #
data = data[0:]
data = data[:0]
data = append(data, b)
for {
b, err = rd.ReadByte()
Expand All @@ -387,16 +388,19 @@ func (d *Decoder) Decode(r io.Reader) (image.Image, error) {
return img, ErrInvalidColor
}

currentPaletteIndex = int(c.Pc)
palette[currentPaletteIndex] = c
currentPaletteIndex = c.Pc
if c.Pu > 0 {
// Non-zero Pu means we have a color definition to set.
palette[currentPaletteIndex] = c
}
// palette[currentPaletteIndex] = color.RGBA64{
// R: uint16(imageConvertChannel(uint32(c.Px))),
// G: uint16(imageConvertChannel(uint32(c.Py))),
// B: uint16(imageConvertChannel(uint32(c.Pz))),
// A: 65525,
// }
case b == RepeatIntroducer: // !
data = data[0:]
data = data[:0]
data = append(data, b)
for {
b, err = rd.ReadByte()
Expand Down
2 changes: 1 addition & 1 deletion ansi/sixel/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (s *sixelBuilder) SetColor(x int, y int, color color.Color) {
paletteIndex := s.SixelPalette.ColorIndex(sixelConvertColor(color))

bit := s.BandHeight()*s.imageWidth*6*paletteIndex + bandY*s.imageWidth*6 + (x * 6) + (y % 6)
s.pixelBands.Set(uint(bit))
s.pixelBands.Set(uint(bit)) //nolint:gosec
}

// GeneratePixels is used to write the pixel data to the internal imageData string builder.
Expand Down

0 comments on commit 4cece45

Please sign in to comment.