-
Notifications
You must be signed in to change notification settings - Fork 22
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
Implement Sixel encoder/decoder support #381
Open
aymanbagabas
wants to merge
37
commits into
main
Choose a base branch
from
feat-sixel-support-pp
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 33 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
ed43f81
feat: Add sixels to ansi package
CannibalVox d44fac5
scanSize tests
CannibalVox 16b7248
Update ansi/sixel/encoder.go
CannibalVox f672bee
Update ansi/graphics.go
CannibalVox 9734d1f
pr comments
CannibalVox 41a4558
raster/parseraster
CannibalVox 8705df7
repeat/parserepeat
CannibalVox 01e9042
remove sixel.Options and address pX conditional
raphamorim 59b7744
remove WriteSixelGraphics func
raphamorim 693be27
ignore p3
raphamorim 0f4f102
move back to ;q
raphamorim 2a70985
use byte instead of rune
raphamorim ce91bcb
remove WriteString
raphamorim af13a23
remove slices
raphamorim d68cbad
use Compare and isNan from 1.24
raphamorim e3b8780
fixup! use Compare and isNan from 1.24
raphamorim 394b145
fixup! fixup! use Compare and isNan from 1.24
raphamorim 39755ba
fixup! fixup! fixup! use Compare and isNan from 1.24
raphamorim d53c02c
remove code
raphamorim 38f4f5a
chore: merge branch 'main' into feat-sixel-support
aymanbagabas 226b4c2
add bench
raphamorim 3653b43
rename file
raphamorim f1a551f
add palette_sort
raphamorim 49ef044
remove stableCmpFunc
raphamorim eb42d4e
remove unnecessary stuff
raphamorim 53d8da5
remove comments
raphamorim 83cc248
feat(ansi): sixel: implement types and use io.Reader
aymanbagabas 5735ce9
refactor(ansi): sixel: clean up and refactor, use quant
aymanbagabas 92f58dc
feat: use aymanbagabas/quant instead of soniakeys/quant for sort
aymanbagabas eec3c2c
feat(ansi): sixel: support transparent color in encoder
aymanbagabas bd0387e
feat(ansi): sixel: add dithering support
aymanbagabas 1271e57
chore(ansi): sixel: use unix line endings
aymanbagabas db2c579
fix(ansi): sixel: rename AddTransparent to NoTransparency
aymanbagabas d226daa
chore(ansi): sixel: remove unused and expose default color palette
aymanbagabas 73138de
fix(ansi): sixel: lint errors
aymanbagabas e46a2b7
feat(ansi): sixel: add Palette field to Decoder
aymanbagabas fdb1448
feat(examples): add img2term example
aymanbagabas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,3 +18,6 @@ | |
|
||
testdata | ||
*.png | ||
|
||
# Allow graphics used for bench test | ||
!ansi/fixtures/graphics/*.png | ||
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,12 @@ module github.com/charmbracelet/x/ansi | |
go 1.18 | ||
|
||
require ( | ||
github.com/bits-and-blooms/bitset v1.20.0 | ||
github.com/lucasb-eyer/go-colorful v1.2.0 | ||
github.com/mattn/go-runewidth v0.0.16 | ||
github.com/mattn/go-sixel v0.0.5 //go:build benchthis | ||
github.com/rivo/uniseg v0.4.7 | ||
github.com/soniakeys/quant v1.0.0 // indirect; go:build benchthis | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not github.com/aymanbagabas/quant? this is just for the bench? |
||
) | ||
|
||
require github.com/aymanbagabas/quant v0.0.0-20250220224823-9dea6ec382b5 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would be good to move to the first require no? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,15 @@ | ||
github.com/aymanbagabas/quant v0.0.0-20250220224823-9dea6ec382b5 h1:BQwkJD8W8jgJpvJgA3klNG5W4pyrU4wJ7LynWik+uYc= | ||
github.com/aymanbagabas/quant v0.0.0-20250220224823-9dea6ec382b5/go.mod h1:LZcbdMgqlAs77pcs8WZnQxNflVHNtzWWDDUJoBFC24A= | ||
github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= | ||
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= | ||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | ||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | ||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||
github.com/mattn/go-sixel v0.0.5 h1:55w2FR5ncuhKhXrM5ly1eiqMQfZsnAHIpYNGZX03Cv8= | ||
github.com/mattn/go-sixel v0.0.5/go.mod h1:h2Sss+DiUEHy0pUqcIB6PFXo5Cy8sTQEFr3a9/5ZLNw= | ||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | ||
github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y= | ||
github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
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) | ||
} | ||
|
||
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 { | ||
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 | ||
} | ||
|
||
if len(data) < 2 { // The minimum length is 2: the introducer and a digit. | ||
return | ||
} | ||
|
||
// 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] == ';' { | ||
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 | ||
} | ||
|
||
// 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 colors[c.Pc].RGBA() | ||
} | ||
} | ||
|
||
var colors = map[int]color.Color{ | ||
// 16 predefined color registers of VT340 | ||
0: sixelRGB(0, 0, 0), | ||
1: sixelRGB(20, 20, 80), | ||
2: sixelRGB(80, 13, 13), | ||
3: sixelRGB(20, 80, 20), | ||
4: sixelRGB(80, 20, 80), | ||
5: sixelRGB(20, 80, 80), | ||
6: sixelRGB(80, 80, 20), | ||
7: sixelRGB(53, 53, 53), | ||
8: sixelRGB(26, 26, 26), | ||
9: sixelRGB(33, 33, 60), | ||
10: sixelRGB(60, 26, 26), | ||
11: sixelRGB(33, 60, 33), | ||
12: sixelRGB(60, 33, 60), | ||
13: sixelRGB(33, 60, 60), | ||
14: sixelRGB(60, 60, 33), | ||
15: sixelRGB(80, 80, 80), | ||
} | ||
|
||
// #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() | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I added
ansi/fixtures/graphics/JigokudaniMonkeyPark.png
originally but i have a concern if it would come along when someone would install x/ansi, i assume not right?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.
I think they wont as long as we use git-lfs