mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 14:18:26 +02:00
Code to serialize/unserialize loaded images
This commit is contained in:
@@ -8,6 +8,9 @@ import (
|
|||||||
|
|
||||||
var _ = fmt.Print
|
var _ = fmt.Print
|
||||||
|
|
||||||
|
//go:embed logo/kitty.png
|
||||||
|
var KittyLogoAsPNGData []byte
|
||||||
|
|
||||||
//go:embed kitty_tests/GraphemeBreakTest.json
|
//go:embed kitty_tests/GraphemeBreakTest.json
|
||||||
var grapheme_break_test_data []byte
|
var grapheme_break_test_data []byte
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,23 @@ type ImageFrame struct {
|
|||||||
Img image.Image
|
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) {
|
func (self *ImageFrame) DataAsSHM(pattern string) (ans shm.MMap, err error) {
|
||||||
bytes_per_pixel := 4
|
bytes_per_pixel := 4
|
||||||
if self.Is_opaque {
|
if self.Is_opaque {
|
||||||
@@ -122,12 +139,73 @@ func (self *ImageFrame) Data() (ans []byte) {
|
|||||||
return
|
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 {
|
type ImageData struct {
|
||||||
Width, Height int
|
Width, Height int
|
||||||
Format_uppercase string
|
Format_uppercase string
|
||||||
Frames []*ImageFrame
|
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 {
|
func (self *ImageFrame) Resize(x_frac, y_frac float64) *ImageFrame {
|
||||||
b := self.Img.Bounds()
|
b := self.Img.Bounds()
|
||||||
left, top, width, height := b.Min.X, b.Min.Y, b.Dx(), b.Dy()
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImageMagick {{{
|
||||||
var MagickExe = sync.OnceValue(func() string {
|
var MagickExe = sync.OnceValue(func() string {
|
||||||
return utils.FindExe("magick")
|
return utils.FindExe("magick")
|
||||||
})
|
})
|
||||||
@@ -610,6 +689,8 @@ func OpenImageFromPathWithMagick(path string) (ans *ImageData, err error) {
|
|||||||
return ans, nil
|
return ans, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
|
||||||
func OpenImageFromPath(path string) (ans *ImageData, err error) {
|
func OpenImageFromPath(path string) (ans *ImageData, err error) {
|
||||||
mt := utils.GuessMimeType(path)
|
mt := utils.GuessMimeType(path)
|
||||||
if DecodableImageTypes[mt] {
|
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