Code to serialize/unserialize loaded images

This commit is contained in:
Kovid Goyal
2025-10-07 17:25:47 +05:30
parent 8f91fcefbe
commit a76f32df2d
3 changed files with 115 additions and 0 deletions

View File

@@ -8,6 +8,9 @@ import (
var _ = fmt.Print
//go:embed logo/kitty.png
var KittyLogoAsPNGData []byte
//go:embed kitty_tests/GraphemeBreakTest.json
var grapheme_break_test_data []byte

View File

@@ -53,6 +53,23 @@ type ImageFrame struct {
Img image.Image
}
type SerializableImageFrame struct {
Width, Height, Left, Top int
Number int // 1-based number
Compose_onto int // number of frame to compose onto
Delay_ms int // negative for gapless frame, zero ignored, positive is number of ms
Is_opaque bool
Size int
}
func (s *ImageFrame) Serialize() SerializableImageFrame {
return SerializableImageFrame{
Width: s.Width, Height: s.Height, Left: s.Left, Top: s.Top,
Number: s.Number, Compose_onto: s.Compose_onto, Delay_ms: int(s.Delay_ms),
Is_opaque: s.Is_opaque,
}
}
func (self *ImageFrame) DataAsSHM(pattern string) (ans shm.MMap, err error) {
bytes_per_pixel := 4
if self.Is_opaque {
@@ -122,12 +139,73 @@ func (self *ImageFrame) Data() (ans []byte) {
return
}
func ImageFrameFromSerialized(s SerializableImageFrame, data []byte) (*ImageFrame, error) {
ans := ImageFrame{
Width: s.Width, Height: s.Height, Left: s.Left, Top: s.Top,
Number: s.Number, Compose_onto: s.Compose_onto, Delay_ms: int32(s.Delay_ms),
Is_opaque: s.Is_opaque,
}
r := image.Rect(0, 0, s.Width, s.Height)
if s.Is_opaque {
if len(data) != 3*r.Dx()*r.Dy() {
return nil, fmt.Errorf("serialized image data has size: %d != %d", len(data), 3*r.Dy()*r.Dx())
}
ans.Img = &NRGB{Pix: data, Stride: 3 * r.Dx(), Rect: r}
} else {
if len(data) != 4*r.Dx()*r.Dy() {
return nil, fmt.Errorf("serialized image data has size: %d != %d", len(data), 4*r.Dy()*r.Dx())
}
ans.Img = &image.NRGBA{Pix: data, Stride: 4 * r.Dx(), Rect: r}
}
return &ans, nil
}
type ImageData struct {
Width, Height int
Format_uppercase string
Frames []*ImageFrame
}
type SerializableImageMetadata struct {
Version int
Width, Height int
Format_uppercase string
Frames []SerializableImageFrame
}
const SERIALIZE_VERSION = 1
func (self *ImageData) Serialize() (SerializableImageMetadata, [][]byte) {
m := SerializableImageMetadata{Version: SERIALIZE_VERSION, Width: self.Width, Height: self.Height, Format_uppercase: self.Format_uppercase}
data := make([][]byte, len(self.Frames))
for i, f := range self.Frames {
m.Frames = append(m.Frames, f.Serialize())
data[i] = f.Data()
m.Frames[len(m.Frames)-1].Size = len(data[i])
}
return m, data
}
func ImageFromSerialized(m SerializableImageMetadata, data [][]byte) (*ImageData, error) {
if m.Version > SERIALIZE_VERSION {
return nil, fmt.Errorf("serialized image data has unsupported version: %d", m.Version)
}
if len(m.Frames) != len(data) {
return nil, fmt.Errorf("serialized image data has %d frames in metadata but have data for: %d", len(m.Frames), len(data))
}
ans := ImageData{
Width: m.Width, Height: m.Height, Format_uppercase: m.Format_uppercase,
}
for i, f := range m.Frames {
if ff, err := ImageFrameFromSerialized(f, data[i]); err != nil {
return nil, err
} else {
ans.Frames = append(ans.Frames, ff)
}
}
return &ans, nil
}
func (self *ImageFrame) Resize(x_frac, y_frac float64) *ImageFrame {
b := self.Img.Bounds()
left, top, width, height := b.Min.X, b.Min.Y, b.Dx(), b.Dy()
@@ -266,6 +344,7 @@ func OpenNativeImageFromReader(f io.ReadSeeker) (ans *ImageData, err error) {
return
}
// ImageMagick {{{
var MagickExe = sync.OnceValue(func() string {
return utils.FindExe("magick")
})
@@ -610,6 +689,8 @@ func OpenImageFromPathWithMagick(path string) (ans *ImageData, err error) {
return ans, nil
}
// }}}
func OpenImageFromPath(path string) (ans *ImageData, err error) {
mt := utils.GuessMimeType(path)
if DecodableImageTypes[mt] {

View File

@@ -0,0 +1,31 @@
package images
import (
"bytes"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/kovidgoyal/kitty"
)
var _ = fmt.Print
func TestImageSerialize(t *testing.T) {
img, err := OpenNativeImageFromReader(bytes.NewReader(kitty.KittyLogoAsPNGData))
if err != nil {
t.Fatal(err)
}
m, data := img.Serialize()
img2, err := ImageFromSerialized(m, data)
if err != nil {
t.Fatal(err)
}
m2, data2 := img2.Serialize()
if diff := cmp.Diff(m, m2); diff != "" {
t.Fatalf("Image metadata failed to roundtrip:\n%s", diff)
}
if diff := cmp.Diff(data, data2); diff != "" {
t.Fatalf("Image data failed to roundtrip:\n%s", diff)
}
}