-
Notifications
You must be signed in to change notification settings - Fork 20
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
Sixel Graphics Implementation #352
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Impressive work here!
Initial review here with few comments to abstract and generalize the implementation. I wonder if we need a sixel.Options
here, instead, we can place encoder/decoder customizations and options directly in the structure definitions.
"strconv" | ||
"strings" | ||
|
||
"github.com/bits-and-blooms/bitset" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anyway we can get rid of this dependency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only way I see it working is writing our own bitset implementation- is that something you want to pursue?
// sixelColor is a flat struct that contains a single color: all channels are 0-100 | ||
// instead of anything sensible | ||
type sixelColor struct { | ||
Red uint32 | ||
Green uint32 | ||
Blue uint32 | ||
Alpha uint32 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// sixelColor is a flat struct that contains a single color: all channels are 0-100 | |
// instead of anything sensible | |
type sixelColor struct { | |
Red uint32 | |
Green uint32 | |
Blue uint32 | |
Alpha uint32 | |
} | |
// sixelColor is a flat struct that contains a single color: all channels are 0-100 | |
// instead of anything sensible | |
type RGBAColor color.RGBA | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we good with the fact that RGBA colors sourced from outside this package will not work properly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RGBA colors that are not sixel.RGBAColor
can be converted using sixelConvertColor
// - If a single color sits on a cut line, all pixels of that color are assigned to one of the subcubes | ||
// rather than try to split them up between the subcubes. This allows us to use a slice of unique colors | ||
// and a map of pixel counts rather than try to represent each pixel individually. | ||
type sixelPalette struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we expose this and be able to pass a custom Palette
in *Options
?
ansi/sixel/encoder.go
Outdated
func (e *Encoder) encodePaletteColor(w io.Writer, paletteIndex int, c sixelColor) { | ||
// Initializing palette entries | ||
// #<a>;<b>;<c>;<d>;<e> | ||
// a = palette index | ||
// b = color type, 2 is RGB | ||
// c = R | ||
// d = G | ||
// e = B | ||
|
||
w.Write([]byte{sixelUseColor}) //nolint:errcheck | ||
io.WriteString(w, strconv.Itoa(paletteIndex)) //nolint:errcheck | ||
io.WriteString(w, ";2;") | ||
io.WriteString(w, strconv.Itoa(int(c.Red))) //nolint:errcheck | ||
w.Write([]byte{';'}) //nolint:errcheck | ||
io.WriteString(w, strconv.Itoa(int(c.Green))) //nolint:errcheck | ||
w.Write([]byte{';'}) | ||
io.WriteString(w, strconv.Itoa(int(c.Blue))) //nolint:errcheck | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's define sixel.BasicColor
, sixel.RGBAColor
, sixel.HLSColor
types that implement color.Color
and fmt.Stringer
. We can also have a sixel.ParseColor([]byte) (color.Color, error)
to decode a color from bytes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is a basiccolor in this case, is it just getting a palette index with no color information?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct
type Decoder struct { | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we make the palette customizable?
type Decoder struct { | |
} | |
type Decoder struct { | |
Palette *Palette | |
} | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this just cover default colors, or should we also ignore color definitions when a palette is provided?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use default colors if Palette
is nil
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but do the passed in palette colors get overwritten by defined colors, or do they only replace the default colors?
Tests are failing because the |
Co-authored-by: Ayman Bagabas <[email protected]>
Co-authored-by: Ayman Bagabas <[email protected]>
I think I should probably mention: the encoder previously used a single stringbuilder in order to minimize allocations. These Raster/Repeat/Color/etc. methods will each add an allocation every time they're called, which will impact performance. |
Fair point. Let's change these methods to be writers |
This PR adds support for sixels to x/ansi. Sixels are a protocol for writing images to the terminal by writing a large blob of ANSI-escaped data. They function by encoding columns of 6 pixels into a single character (in much the same way base64 encodes data 6 bits at a time). Sixel images are paletted, with a palette established at the beginning of the image blob and pixels identifying palette entries by index while writing the pixel data.
Sixels are written one 6-pixel-tall band at a time, one color at a time. For each band, a single color's pixels are written, then a carriage return is written to bring the "cursor" back to the beginning of a band where a new color is selected and pixels written. This continues until the entire band has been drawn, at which time a line break is written to begin the next band.
Sixel writing and reading take the form of the sixel.Encoder and sixel.Decoder types, in order to match the existing kitty graphics implementation as closely as we reasonably can. The only issue is that because there is no way to know the size of a sixel blob in advance (unlike the kitty graphics protocol, we can't determine the size of the payload from the width and height), we can only determine the boundaries of the payload by detecting the ST sequence (or BEL or whatever we're using to close the payload). As a result, the sixel decoder accepts a byte slice that contains only the payload and relies on the caller to determine the boundaries.
I included an options type in the parameters for WriteSixelGraphics, but there are not yet any options. I'd like to include a dithering implementation in the future, but I wanted to get the actual sixel implementation checked in first.