kitty/tools/utils/images/convert.go

140 lines
3.5 KiB
Go

package images
import (
"bytes"
"encoding/binary"
"fmt"
"image"
"image/color"
"io"
"os"
"slices"
"strings"
"github.com/kovidgoyal/kitty/tools/cli"
"github.com/kovidgoyal/kitty/tools/utils"
)
var _ = fmt.Print
func encode_rgba(output io.Writer, img image.Image) (err error) {
var final_img *image.NRGBA
switch ti := img.(type) {
case *image.NRGBA:
final_img = ti
default:
b := img.Bounds()
final_img = image.NewNRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
ctx := Context{}
ctx.PasteCenter(final_img, img, nil)
}
b := final_img.Bounds()
header := make([]byte, 8)
var width = utils.Abs(b.Dx())
var height = utils.Abs(b.Dy())
binary.LittleEndian.PutUint32(header, uint32(width))
binary.LittleEndian.PutUint32(header[4:], uint32(height))
readers := []io.Reader{bytes.NewReader(header)}
stride := 4 * width
if final_img.Stride == stride {
readers = append(readers, bytes.NewReader(final_img.Pix))
} else {
p := final_img.Pix
for y := 0; y < b.Dy(); y++ {
readers = append(readers, bytes.NewReader(p[:min(stride, len(p))]))
p = p[final_img.Stride:]
}
}
_, err = io.Copy(output, io.MultiReader(readers...))
return
}
func convert_image(input io.ReadSeeker, output io.Writer, format string) (err error) {
image_data, err := OpenNativeImageFromReader(input)
if err != nil {
return err
}
if len(image_data.Frames) == 0 {
return fmt.Errorf("Image has no frames")
}
img := image_data.Frames[0].Img
q := strings.ToLower(format)
if q == "rgba" {
return encode_rgba(output, img)
}
mt := utils.GuessMimeType("file." + q)
if mt == "" {
return fmt.Errorf("Unknown image output format: %s", format)
}
return Encode(output, img, mt)
}
func PalettedToNRGBA(paletted *image.Paletted) *image.NRGBA {
bounds := paletted.Bounds()
nrgba := image.NewNRGBA(bounds)
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
c := color.NRGBAModel.Convert(paletted.At(x, y))
nrgba.Set(x, y, c)
}
}
return nrgba
}
func develop_serialize(input_data []byte) (err error) {
img, err := OpenNativeImageFromReader(bytes.NewReader(input_data))
if err != nil {
return err
}
m, b := img.Serialize()
rimg, err := ImageFromSerialized(m, b)
if err != nil {
return err
}
for i := range img.Frames {
a, b := img.Frames[i], rimg.Frames[i]
if a.Img.Bounds() != b.Img.Bounds() {
return fmt.Errorf("bounds of frame %d not equal: %v != %v", i, a.Img.Bounds(), b.Img.Bounds())
}
for y := a.Img.Bounds().Min.Y; y < a.Img.Bounds().Max.Y; y++ {
for x := a.Img.Bounds().Min.X; x < a.Img.Bounds().Max.X; x++ {
or, og, ob, oa := a.Img.At(x, y).RGBA()
nr, ng, nb, na := b.Img.At(x, y).RGBA()
a, b := []uint32{or, og, ob, oa}, []uint32{nr, ng, nb, na}
if !slices.Equal(a, b) {
return fmt.Errorf("pixel at %dx%d differs: %v != %v", x, y, a, b)
}
}
}
}
return
}
func ConvertEntryPoint(root *cli.Command) {
root.AddSubCommand(&cli.Command{
Name: "__convert_image__",
Hidden: true,
OnlyArgsAllowed: true,
Run: func(cmd *cli.Command, args []string) (rc int, err error) {
if len(args) != 1 {
return 1, fmt.Errorf("Usage: __convert_image__ OUTPUT_FORMAT")
}
format := args[0]
buf := bytes.NewBuffer(make([]byte, 0, 1024*1024))
if _, err = io.Copy(buf, os.Stdin); err != nil {
return 1, err
}
if format == "develop-serialize" {
err = develop_serialize(buf.Bytes())
rc = utils.IfElse(err == nil, 0, 1)
return
}
if err = convert_image(bytes.NewReader(buf.Bytes()), os.Stdout, format); err != nil {
rc = 1
}
return
},
})
}