Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add sixel to ansi package #380

Merged
merged 43 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
ed43f81
feat: Add sixels to ansi package
CannibalVox Jan 30, 2025
d44fac5
scanSize tests
CannibalVox Feb 1, 2025
16b7248
Update ansi/sixel/encoder.go
CannibalVox Feb 1, 2025
f672bee
Update ansi/graphics.go
CannibalVox Feb 1, 2025
9734d1f
pr comments
CannibalVox Feb 1, 2025
41a4558
raster/parseraster
CannibalVox Feb 2, 2025
8705df7
repeat/parserepeat
CannibalVox Feb 2, 2025
01e9042
remove sixel.Options and address pX conditional
raphamorim Feb 19, 2025
59b7744
remove WriteSixelGraphics func
raphamorim Feb 19, 2025
693be27
ignore p3
raphamorim Feb 19, 2025
0f4f102
move back to ;q
raphamorim Feb 19, 2025
2a70985
use byte instead of rune
raphamorim Feb 19, 2025
ce91bcb
remove WriteString
raphamorim Feb 19, 2025
af13a23
remove slices
raphamorim Feb 19, 2025
d68cbad
use Compare and isNan from 1.24
raphamorim Feb 19, 2025
e3b8780
fixup! use Compare and isNan from 1.24
raphamorim Feb 19, 2025
394b145
fixup! fixup! use Compare and isNan from 1.24
raphamorim Feb 19, 2025
39755ba
fixup! fixup! fixup! use Compare and isNan from 1.24
raphamorim Feb 19, 2025
d53c02c
remove code
raphamorim Feb 19, 2025
38f4f5a
chore: merge branch 'main' into feat-sixel-support
aymanbagabas Feb 19, 2025
226b4c2
add bench
raphamorim Feb 20, 2025
3653b43
rename file
raphamorim Feb 20, 2025
f1a551f
add palette_sort
raphamorim Feb 20, 2025
49ef044
remove stableCmpFunc
raphamorim Feb 20, 2025
eb42d4e
remove unnecessary stuff
raphamorim Feb 20, 2025
53d8da5
remove comments
raphamorim Feb 20, 2025
a68a7ba
feat(ansi): sixel: implement types and use io.Reader
aymanbagabas Feb 20, 2025
4cece45
Update decoder and encoder
raphamorim Feb 21, 2025
78fe515
remove unused and expose default color palette
raphamorim Feb 21, 2025
cb8b527
fixup! remove unused and expose default color palette
raphamorim Feb 21, 2025
6748926
fixup! fixup! remove unused and expose default color palette
raphamorim Feb 21, 2025
b1ce404
fixup! fixup! fixup! remove unused and expose default color palette
raphamorim Feb 21, 2025
1274bd6
uncommenting code
raphamorim Feb 21, 2025
86eb1f8
add .gitattributes lfs to png folder
raphamorim Feb 21, 2025
833523d
remove quant
raphamorim Feb 21, 2025
595a567
remove go-sixel
raphamorim Feb 21, 2025
405e40c
comment go-sixel
raphamorim Feb 21, 2025
5ea93ef
Update ansi/sixel/decoder.go
raphamorim Feb 21, 2025
c8efdb7
remove ununsed stuff
raphamorim Feb 21, 2025
243a2e8
add example
raphamorim Feb 21, 2025
e1664ec
Add example img2term
raphamorim Feb 21, 2025
a642787
proper format
raphamorim Feb 21, 2025
17ea3f3
Merge branch 'main' into feat-sixel-support
raphamorim Feb 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading