mirror of
https://github.com/kovidgoyal/kitty.git
synced 2025-12-13 20:36:22 +01:00
Code to serialize/unserialize loaded images
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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] {
|
||||
|
||||
31
tools/utils/images/serialize_test.go
Normal file
31
tools/utils/images/serialize_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user