mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 14:18:26 +02:00
Switch over to the new imaging backend for icat
Greatly simplifies a whole bunch of code. The new backend takes care of falling back to ImageMagick efficiently itself.
This commit is contained in:
@@ -1,64 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package icat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kovidgoyal/go-parallel"
|
||||
"github.com/kovidgoyal/kitty/tools/tui/graphics"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/images"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func render(path, original_file_path string, ro *images.RenderOptions, frames []images.IdentifyRecord) (ans []*image_frame, err error) {
|
||||
ro.TempfilenameTemplate = shm_template
|
||||
image_frames, filenames, err := images.RenderWithMagick(path, original_file_path, ro, frames)
|
||||
if err == nil {
|
||||
ans = make([]*image_frame, len(image_frames))
|
||||
for i, x := range image_frames {
|
||||
ans[i] = &image_frame{
|
||||
filename: filenames[x.Number], filename_is_temporary: true,
|
||||
number: x.Number, width: x.Width, height: x.Height, left: x.Left, top: x.Top,
|
||||
transmission_format: graphics.GRT_format_rgba, delay_ms: int(x.Delay_ms), compose_onto: x.Compose_onto,
|
||||
}
|
||||
if x.Is_opaque {
|
||||
ans[i].transmission_format = graphics.GRT_format_rgb
|
||||
}
|
||||
}
|
||||
}
|
||||
return ans, err
|
||||
}
|
||||
|
||||
func render_image_with_magick(imgd *image_data, src *opened_input) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = parallel.Format_stacktrace_on_panic(r, 1)
|
||||
}
|
||||
}()
|
||||
err = src.PutOnFilesystem()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
frames, err := images.IdentifyWithMagick(src.FileSystemName(), imgd.source_name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imgd.format_uppercase = frames[0].Fmt_uppercase
|
||||
imgd.canvas_width, imgd.canvas_height = frames[0].Canvas.Width, frames[0].Canvas.Height
|
||||
set_basic_metadata(imgd)
|
||||
if !imgd.needs_conversion {
|
||||
make_output_from_input(imgd, src)
|
||||
return nil
|
||||
}
|
||||
ro := images.RenderOptions{RemoveAlpha: remove_alpha, Flip: flip, Flop: flop}
|
||||
if scale_image(imgd) {
|
||||
ro.ResizeTo.X, ro.ResizeTo.Y = imgd.canvas_width, imgd.canvas_height
|
||||
}
|
||||
imgd.frames, err = render(src.FileSystemName(), imgd.source_name, &ro, frames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package icat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/kovidgoyal/go-parallel"
|
||||
"github.com/kovidgoyal/imaging/nrgb"
|
||||
"github.com/kovidgoyal/kitty/tools/tty"
|
||||
"github.com/kovidgoyal/kitty/tools/tui/graphics"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/images"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shm"
|
||||
|
||||
"github.com/kovidgoyal/imaging"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func resize_frame(imgd *image_data, img image.Image) (image.Image, image.Rectangle) {
|
||||
b := img.Bounds()
|
||||
left, top, width, height := b.Min.X, b.Min.Y, b.Dx(), b.Dy()
|
||||
new_width := int(imgd.scaled_frac.x * float64(width))
|
||||
new_height := int(imgd.scaled_frac.y * float64(height))
|
||||
img = imaging.Resize(img, new_width, new_height, imaging.Lanczos)
|
||||
newleft := int(imgd.scaled_frac.x * float64(left))
|
||||
newtop := int(imgd.scaled_frac.y * float64(top))
|
||||
return img, image.Rect(newleft, newtop, newleft+new_width, newtop+new_height)
|
||||
}
|
||||
|
||||
const shm_template = "kitty-icat-*"
|
||||
|
||||
func add_frame(ctx *images.Context, imgd *image_data, img image.Image, left, top int) *image_frame {
|
||||
is_opaque := imaging.IsOpaque(img)
|
||||
b := img.Bounds()
|
||||
if imgd.scaled_frac.x != 0 {
|
||||
img, b = resize_frame(imgd, img)
|
||||
}
|
||||
f := image_frame{width: b.Dx(), height: b.Dy(), number: len(imgd.frames) + 1, left: left, top: top}
|
||||
dest_rect := image.Rect(0, 0, f.width, f.height)
|
||||
var final_img image.Image
|
||||
bytes_per_pixel := 4
|
||||
|
||||
if is_opaque || remove_alpha != nil {
|
||||
var rgb *imaging.NRGB
|
||||
bytes_per_pixel = 3
|
||||
m, err := shm.CreateTemp(shm_template, uint64(f.width*f.height*bytes_per_pixel))
|
||||
if err != nil {
|
||||
rgb = nrgb.NewNRGB(dest_rect)
|
||||
} else {
|
||||
rgb = &imaging.NRGB{Pix: m.Slice(), Stride: bytes_per_pixel * f.width, Rect: dest_rect}
|
||||
f.shm = m
|
||||
}
|
||||
f.transmission_format = graphics.GRT_format_rgb
|
||||
f.in_memory_bytes = rgb.Pix
|
||||
final_img = rgb
|
||||
} else {
|
||||
var rgba *image.NRGBA
|
||||
m, err := shm.CreateTemp(shm_template, uint64(f.width*f.height*bytes_per_pixel))
|
||||
if err != nil {
|
||||
rgba = image.NewNRGBA(dest_rect)
|
||||
} else {
|
||||
rgba = &image.NRGBA{Pix: m.Slice(), Stride: bytes_per_pixel * f.width, Rect: dest_rect}
|
||||
f.shm = m
|
||||
}
|
||||
f.transmission_format = graphics.GRT_format_rgba
|
||||
f.in_memory_bytes = rgba.Pix
|
||||
final_img = rgba
|
||||
}
|
||||
ctx.PasteCenter(final_img, img, remove_alpha)
|
||||
imgd.frames = append(imgd.frames, &f)
|
||||
if flip {
|
||||
ctx.FlipPixelsV(bytes_per_pixel, f.width, f.height, f.in_memory_bytes)
|
||||
if f.height < imgd.canvas_height {
|
||||
f.top = (2*imgd.canvas_height - f.height - f.top) % imgd.canvas_height
|
||||
}
|
||||
}
|
||||
if flop {
|
||||
ctx.FlipPixelsH(bytes_per_pixel, f.width, f.height, f.in_memory_bytes)
|
||||
if f.width < imgd.canvas_width {
|
||||
f.left = (2*imgd.canvas_width - f.width - f.left) % imgd.canvas_width
|
||||
}
|
||||
}
|
||||
return &f
|
||||
}
|
||||
|
||||
func scale_up(width, height, maxWidth, maxHeight int) (newWidth, newHeight int) {
|
||||
if width == 0 || height == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Calculate the ratio to scale the width and the ratio to scale the height.
|
||||
// We use floating-point division for precision.
|
||||
widthRatio := float64(maxWidth) / float64(width)
|
||||
heightRatio := float64(maxHeight) / float64(height)
|
||||
|
||||
// To preserve the aspect ratio and fit within the limits, we must use the
|
||||
// smaller of the two scaling ratios.
|
||||
var ratio float64
|
||||
if widthRatio < heightRatio {
|
||||
ratio = widthRatio
|
||||
} else {
|
||||
ratio = heightRatio
|
||||
}
|
||||
|
||||
// Calculate the new dimensions and convert them back to uints.
|
||||
newWidth = int(float64(width) * ratio)
|
||||
newHeight = int(float64(height) * ratio)
|
||||
|
||||
return newWidth, newHeight
|
||||
}
|
||||
|
||||
func scale_image(imgd *image_data) bool {
|
||||
if imgd.needs_scaling {
|
||||
width, height := imgd.canvas_width, imgd.canvas_height
|
||||
if opts.ScaleUp && (imgd.canvas_width < imgd.available_width || imgd.canvas_height < imgd.available_height) && (imgd.available_height != inf || imgd.available_width != inf) {
|
||||
imgd.canvas_width, imgd.canvas_height = scale_up(imgd.canvas_width, imgd.canvas_height, imgd.available_width, imgd.available_height)
|
||||
}
|
||||
neww, newh := images.FitImage(imgd.canvas_width, imgd.canvas_height, imgd.available_width, imgd.available_height)
|
||||
imgd.needs_scaling = false
|
||||
imgd.scaled_frac.x = float64(neww) / float64(width)
|
||||
imgd.scaled_frac.y = float64(newh) / float64(height)
|
||||
imgd.canvas_width = int(imgd.scaled_frac.x * float64(width))
|
||||
imgd.canvas_height = int(imgd.scaled_frac.y * float64(height))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var debugprintln = tty.DebugPrintln
|
||||
var _ = debugprintln
|
||||
|
||||
func add_frames(ctx *images.Context, imgd *image_data, gf *imaging.Image) {
|
||||
for _, f := range gf.Frames {
|
||||
frame := add_frame(ctx, imgd, f.Image, f.TopLeft.X, f.TopLeft.Y)
|
||||
frame.number, frame.compose_onto = int(f.Number), int(f.ComposeOnto)
|
||||
frame.replace = f.Replace
|
||||
frame.delay_ms = int(f.Delay.Milliseconds())
|
||||
if frame.delay_ms <= 0 {
|
||||
frame.delay_ms = -1 // -1 is gapless in graphics protocol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func render_image_with_go(imgd *image_data, src *opened_input) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = parallel.Format_stacktrace_on_panic(r, 1)
|
||||
}
|
||||
}()
|
||||
ctx := images.Context{}
|
||||
imgs, _, err := imaging.DecodeAll(src.file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if imgs == nil {
|
||||
return fmt.Errorf("unknown image format")
|
||||
}
|
||||
imgd.format_uppercase = imgs.Metadata.Format.String()
|
||||
// Loading could auto orient and therefore change width/height, so
|
||||
// re-calculate
|
||||
b := imgs.Bounds()
|
||||
imgd.canvas_width, imgd.canvas_height = b.Dx(), b.Dy()
|
||||
set_basic_metadata(imgd)
|
||||
scale_image(imgd)
|
||||
add_frames(&ctx, imgd, imgs)
|
||||
return nil
|
||||
}
|
||||
@@ -15,52 +15,15 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kovidgoyal/imaging"
|
||||
"github.com/kovidgoyal/kitty/tools/tty"
|
||||
"github.com/kovidgoyal/kitty/tools/tui/graphics"
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/images"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shm"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
type BytesBuf struct {
|
||||
data []byte
|
||||
pos int64
|
||||
}
|
||||
|
||||
func (self *BytesBuf) Seek(offset int64, whence int) (int64, error) {
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
self.pos = offset
|
||||
case io.SeekCurrent:
|
||||
self.pos += offset
|
||||
case io.SeekEnd:
|
||||
self.pos = int64(len(self.data)) + offset
|
||||
default:
|
||||
return self.pos, fmt.Errorf("Unknown value for whence: %#v", whence)
|
||||
}
|
||||
self.pos = utils.Max(0, utils.Min(self.pos, int64(len(self.data))))
|
||||
return self.pos, nil
|
||||
}
|
||||
|
||||
func (self *BytesBuf) Read(p []byte) (n int, err error) {
|
||||
nb := utils.Min(int64(len(p)), int64(len(self.data))-self.pos)
|
||||
if nb == 0 {
|
||||
err = io.EOF
|
||||
} else {
|
||||
n = copy(p, self.data[self.pos:self.pos+nb])
|
||||
self.pos += nb
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *BytesBuf) Close() error {
|
||||
self.data = nil
|
||||
self.pos = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
type input_arg struct {
|
||||
arg string
|
||||
value string
|
||||
@@ -120,54 +83,14 @@ func process_dirs(args ...string) (results []input_arg, err error) {
|
||||
}
|
||||
|
||||
type opened_input struct {
|
||||
file io.ReadSeekCloser
|
||||
name_to_unlink string
|
||||
file io.Reader
|
||||
bytes []byte
|
||||
path string
|
||||
}
|
||||
|
||||
func (self *opened_input) Rewind() {
|
||||
if self.file != nil {
|
||||
_, _ = self.file.Seek(0, io.SeekStart)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *opened_input) Release() {
|
||||
if self.file != nil {
|
||||
self.file.Close()
|
||||
self.file = nil
|
||||
}
|
||||
if self.name_to_unlink != "" {
|
||||
os.Remove(self.name_to_unlink)
|
||||
self.name_to_unlink = ""
|
||||
}
|
||||
}
|
||||
|
||||
func (self *opened_input) PutOnFilesystem() (err error) {
|
||||
if self.name_to_unlink != "" {
|
||||
return
|
||||
}
|
||||
f, err := images.CreateTempInRAM()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create a temporary file to store input data with error: %w", err)
|
||||
}
|
||||
self.Rewind()
|
||||
_, err = io.Copy(f, self.file)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return fmt.Errorf("Failed to copy input data to temporary file with error: %w", err)
|
||||
}
|
||||
self.Release()
|
||||
self.file = f
|
||||
self.name_to_unlink = f.Name()
|
||||
return
|
||||
}
|
||||
|
||||
func (self *opened_input) FileSystemName() string { return self.name_to_unlink }
|
||||
|
||||
type image_frame struct {
|
||||
filename string
|
||||
shm shm.MMap
|
||||
in_memory_bytes []byte
|
||||
filename_is_temporary bool
|
||||
width, height, left, top int
|
||||
transmission_format graphics.GRT_f
|
||||
compose_onto int
|
||||
@@ -180,8 +103,7 @@ type image_data struct {
|
||||
canvas_width, canvas_height int
|
||||
format_uppercase string
|
||||
available_width, available_height int
|
||||
needs_scaling, needs_conversion bool
|
||||
scaled_frac struct{ x, y float64 }
|
||||
needs_scaling bool
|
||||
frames []*image_frame
|
||||
image_number uint32
|
||||
image_id uint32
|
||||
@@ -222,7 +144,6 @@ func set_basic_metadata(imgd *image_data) {
|
||||
}
|
||||
}
|
||||
imgd.needs_scaling = imgd.canvas_width > imgd.available_width || imgd.canvas_height > imgd.available_height || opts.ScaleUp
|
||||
imgd.needs_conversion = imgd.needs_scaling || remove_alpha != nil || flip || flop || imgd.format_uppercase != "PNG"
|
||||
}
|
||||
|
||||
func report_error(source_name, msg string, err error) {
|
||||
@@ -231,7 +152,6 @@ func report_error(source_name, msg string, err error) {
|
||||
}
|
||||
|
||||
func make_output_from_input(imgd *image_data, f *opened_input) {
|
||||
bb, ok := f.file.(*BytesBuf)
|
||||
frame := image_frame{}
|
||||
imgd.frames = append(imgd.frames, &frame)
|
||||
frame.width = imgd.canvas_width
|
||||
@@ -240,17 +160,71 @@ func make_output_from_input(imgd *image_data, f *opened_input) {
|
||||
panic(fmt.Sprintf("Unknown transmission format: %s", imgd.format_uppercase))
|
||||
}
|
||||
frame.transmission_format = graphics.GRT_format_png
|
||||
if ok {
|
||||
frame.in_memory_bytes = bb.data
|
||||
if f.bytes != nil {
|
||||
frame.in_memory_bytes = f.bytes
|
||||
} else if f.path != "" {
|
||||
frame.filename = f.path
|
||||
} else {
|
||||
frame.filename = f.file.(*os.File).Name()
|
||||
if f.name_to_unlink != "" {
|
||||
frame.filename_is_temporary = true
|
||||
f.name_to_unlink = ""
|
||||
var err error
|
||||
if frame.in_memory_bytes, err = io.ReadAll(f.file); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scale_up(width, height, maxWidth, maxHeight int) (newWidth, newHeight int) {
|
||||
if width == 0 || height == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
// Calculate the ratio to scale the width and the ratio to scale the height.
|
||||
// We use floating-point division for precision.
|
||||
widthRatio := float64(maxWidth) / float64(width)
|
||||
heightRatio := float64(maxHeight) / float64(height)
|
||||
|
||||
// To preserve the aspect ratio and fit within the limits, we must use the
|
||||
// smaller of the two scaling ratios.
|
||||
var ratio float64
|
||||
if widthRatio < heightRatio {
|
||||
ratio = widthRatio
|
||||
} else {
|
||||
ratio = heightRatio
|
||||
}
|
||||
|
||||
// Calculate the new dimensions and convert them back to uints.
|
||||
newWidth = int(float64(width) * ratio)
|
||||
newHeight = int(float64(height) * ratio)
|
||||
|
||||
return newWidth, newHeight
|
||||
}
|
||||
|
||||
func scale_image(imgd *image_data) bool {
|
||||
if imgd.needs_scaling {
|
||||
width, height := imgd.canvas_width, imgd.canvas_height
|
||||
if opts.ScaleUp && (imgd.canvas_width < imgd.available_width || imgd.canvas_height < imgd.available_height) && (imgd.available_height != inf || imgd.available_width != inf) {
|
||||
imgd.canvas_width, imgd.canvas_height = scale_up(imgd.canvas_width, imgd.canvas_height, imgd.available_width, imgd.available_height)
|
||||
}
|
||||
neww, newh := images.FitImage(imgd.canvas_width, imgd.canvas_height, imgd.available_width, imgd.available_height)
|
||||
imgd.needs_scaling = false
|
||||
x := float64(neww) / float64(width)
|
||||
y := float64(newh) / float64(height)
|
||||
imgd.canvas_width = int(x * float64(width))
|
||||
imgd.canvas_height = int(y * float64(height))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func add_frame(imgd *image_data, img image.Image, left, top int) *image_frame {
|
||||
const shm_template = "kitty-icat-*"
|
||||
num_channels, pix := imaging.AsRGBData8(img)
|
||||
b := img.Bounds()
|
||||
f := image_frame{width: b.Dx(), height: b.Dy(), number: len(imgd.frames) + 1, left: left, top: top}
|
||||
f.transmission_format = utils.IfElse(num_channels == 3, graphics.GRT_format_rgb, graphics.GRT_format_rgba)
|
||||
f.in_memory_bytes = pix
|
||||
imgd.frames = append(imgd.frames, &f)
|
||||
return &f
|
||||
}
|
||||
|
||||
func process_arg(arg input_arg) {
|
||||
var f opened_input
|
||||
if arg.is_http_url {
|
||||
@@ -271,14 +245,16 @@ func process_arg(arg input_arg) {
|
||||
report_error(arg.value, "Could not download", err)
|
||||
return
|
||||
}
|
||||
f.file = &BytesBuf{data: dest.Bytes()}
|
||||
f.bytes = dest.Bytes()
|
||||
f.file = bytes.NewReader(f.bytes)
|
||||
} else if arg.value == "" {
|
||||
stdin, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
report_error("<stdin>", "Could not read from", err)
|
||||
return
|
||||
}
|
||||
f.file = &BytesBuf{data: stdin}
|
||||
f.bytes = stdin
|
||||
f.file = bytes.NewReader(f.bytes)
|
||||
} else {
|
||||
q, err := os.Open(arg.value)
|
||||
if err != nil {
|
||||
@@ -286,56 +262,70 @@ func process_arg(arg input_arg) {
|
||||
return
|
||||
}
|
||||
f.file = q
|
||||
f.path = q.Name()
|
||||
defer q.Close()
|
||||
}
|
||||
|
||||
var img *images.ImageData
|
||||
var dopts []imaging.DecodeOption
|
||||
needs_conversion := false
|
||||
if flip {
|
||||
dopts = append(dopts, imaging.Transform(imaging.FlipVTransform))
|
||||
needs_conversion = true
|
||||
}
|
||||
if flop {
|
||||
dopts = append(dopts, imaging.Transform(imaging.FlipHTransform))
|
||||
needs_conversion = true
|
||||
}
|
||||
if remove_alpha != nil {
|
||||
dopts = append(dopts, imaging.Background(*remove_alpha))
|
||||
needs_conversion = true
|
||||
}
|
||||
switch opts.Engine {
|
||||
case "native", "builtin":
|
||||
dopts = append(dopts, imaging.Backends(imaging.GO_IMAGE))
|
||||
case "magick":
|
||||
dopts = append(dopts, imaging.Backends(imaging.MAGICK_IMAGE))
|
||||
}
|
||||
defer f.Release()
|
||||
can_use_go := false
|
||||
var c image.Config
|
||||
var format string
|
||||
var err error
|
||||
imgd := image_data{source_name: arg.value}
|
||||
if opts.Engine == "auto" || opts.Engine == "builtin" {
|
||||
c, format, err = image.DecodeConfig(f.file)
|
||||
f.Rewind()
|
||||
can_use_go = err == nil
|
||||
dopts = append(dopts, imaging.ResizeCallback(func(w, h int) (int, int) {
|
||||
imgd.canvas_width, imgd.canvas_height = w, h
|
||||
set_basic_metadata(&imgd)
|
||||
if scale_image(&imgd) {
|
||||
needs_conversion = true
|
||||
w, h = imgd.canvas_width, imgd.canvas_height
|
||||
}
|
||||
return w, h
|
||||
}))
|
||||
var err error
|
||||
if f.path != "" {
|
||||
img, err = images.OpenImageFromPath(f.path, dopts...)
|
||||
} else {
|
||||
img, f.file, err = images.OpenImageFromReader(f.file, dopts...)
|
||||
}
|
||||
if err != nil {
|
||||
report_error(arg.value, "Could not render image to RGB", err)
|
||||
return
|
||||
}
|
||||
if !keep_going.Load() {
|
||||
return
|
||||
}
|
||||
if can_use_go {
|
||||
imgd.canvas_width = c.Width
|
||||
imgd.canvas_height = c.Height
|
||||
imgd.format_uppercase = strings.ToUpper(format)
|
||||
set_basic_metadata(&imgd)
|
||||
if !imgd.needs_conversion {
|
||||
make_output_from_input(&imgd, &f)
|
||||
send_output(&imgd)
|
||||
return
|
||||
}
|
||||
err = render_image_with_go(&imgd, &f)
|
||||
if err != nil {
|
||||
if opts.Engine != "builtin" {
|
||||
merr := render_image_with_magick(&imgd, &f)
|
||||
if merr != nil {
|
||||
report_error(arg.value, "Could not render image to RGB", err)
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
report_error(arg.value, "could not render", err)
|
||||
return
|
||||
}
|
||||
imgd.format_uppercase = img.Format_uppercase
|
||||
imgd.canvas_width, imgd.canvas_height = img.Width, img.Height
|
||||
if !needs_conversion && imgd.format_uppercase == "PNG" && len(img.Frames) == 1 {
|
||||
make_output_from_input(&imgd, &f)
|
||||
} else {
|
||||
err = render_image_with_magick(&imgd, &f)
|
||||
if err != nil {
|
||||
report_error(arg.value, "ImageMagick failed", err)
|
||||
return
|
||||
for _, f := range img.Frames {
|
||||
frame := add_frame(&imgd, f.Img, f.Left, f.Top)
|
||||
frame.number, frame.compose_onto = int(f.Number), int(f.Compose_onto)
|
||||
frame.replace = f.Replace
|
||||
frame.delay_ms = int(f.Delay_ms)
|
||||
}
|
||||
}
|
||||
if !keep_going.Load() {
|
||||
return
|
||||
}
|
||||
send_output(&imgd)
|
||||
|
||||
}
|
||||
|
||||
func run_worker() {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/kovidgoyal/kitty"
|
||||
"io"
|
||||
@@ -92,43 +91,32 @@ func transmit_shm(imgd *image_data, frame_num int, frame *image_frame) (err erro
|
||||
return fmt.Errorf("Failed to open image data output file: %s with error: %w", frame.filename, err)
|
||||
}
|
||||
defer f.Close()
|
||||
data_size, _ = f.Seek(0, io.SeekEnd)
|
||||
_, _ = f.Seek(0, io.SeekStart)
|
||||
mmap, err = shm.CreateTemp("icat-*", uint64(data_size))
|
||||
if err != nil {
|
||||
if data_size, err = f.Seek(0, io.SeekEnd); err != nil {
|
||||
return fmt.Errorf("Failed to seek in image data output file: %s with error: %w", frame.filename, err)
|
||||
}
|
||||
if _, err = f.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("Failed to seek in image data output file: %s with error: %w", frame.filename, err)
|
||||
}
|
||||
if mmap, err = shm.CreateTemp("icat-*", uint64(data_size)); err != nil {
|
||||
return fmt.Errorf("Failed to create a SHM file for transmission: %w", err)
|
||||
}
|
||||
dest := mmap.Slice()
|
||||
for len(dest) > 0 {
|
||||
n, err := f.Read(dest)
|
||||
dest = dest[n:]
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
_ = mmap.Unlink()
|
||||
return fmt.Errorf("Failed to read data from image output data file: %w", err)
|
||||
}
|
||||
if _, err = io.ReadFull(f, mmap.Slice()); err != nil {
|
||||
mmap.Close()
|
||||
mmap.Unlink()
|
||||
return fmt.Errorf("Failed to read data from image output data file: %w", err)
|
||||
}
|
||||
} else {
|
||||
if frame.shm == nil {
|
||||
data_size = int64(len(frame.in_memory_bytes))
|
||||
mmap, err = shm.CreateTemp("icat-*", uint64(data_size))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create a SHM file for transmission: %w", err)
|
||||
}
|
||||
copy(mmap.Slice(), frame.in_memory_bytes)
|
||||
} else {
|
||||
mmap = frame.shm
|
||||
frame.shm = nil
|
||||
data_size = int64(len(frame.in_memory_bytes))
|
||||
if mmap, err = shm.CreateTemp("icat-*", uint64(data_size)); err != nil {
|
||||
return fmt.Errorf("Failed to create a SHM file for transmission: %w", err)
|
||||
}
|
||||
copy(mmap.Slice(), frame.in_memory_bytes)
|
||||
}
|
||||
defer mmap.Close() // terminal is responsible for unlink
|
||||
gc := gc_for_image(imgd, frame_num, frame)
|
||||
gc.SetTransmission(graphics.GRT_transmission_sharedmem)
|
||||
gc.SetDataSize(uint64(data_size))
|
||||
err = gc.WriteWithPayloadTo(os.Stdout, utils.UnsafeStringToBytes(mmap.Name()))
|
||||
mmap.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -137,7 +125,6 @@ func transmit_file(imgd *image_data, frame_num int, frame *image_frame) (err err
|
||||
fname := ""
|
||||
var data_size int
|
||||
if frame.in_memory_bytes == nil {
|
||||
is_temp = frame.filename_is_temporary
|
||||
fname, err = filepath.Abs(frame.filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to convert image data output file: %s to absolute path with error: %w", frame.filename, err)
|
||||
@@ -145,30 +132,21 @@ func transmit_file(imgd *image_data, frame_num int, frame *image_frame) (err err
|
||||
frame.filename = "" // so it isn't deleted in cleanup
|
||||
} else {
|
||||
is_temp = true
|
||||
if frame.shm != nil && frame.shm.FileSystemName() != "" {
|
||||
fname = frame.shm.FileSystemName()
|
||||
frame.shm.Close()
|
||||
frame.shm = nil
|
||||
} else {
|
||||
f, err := images.CreateTempInRAM()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create a temp file for image data transmission: %w", err)
|
||||
}
|
||||
data_size = len(frame.in_memory_bytes)
|
||||
_, err = bytes.NewBuffer(frame.in_memory_bytes).WriteTo(f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to write image data to temp file for transmission: %w", err)
|
||||
}
|
||||
fname = f.Name()
|
||||
f, err := images.CreateTempInRAM()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create a temp file for image data transmission: %w", err)
|
||||
}
|
||||
data_size = len(frame.in_memory_bytes)
|
||||
_, err = bytes.NewBuffer(frame.in_memory_bytes).WriteTo(f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
os.Remove(f.Name())
|
||||
return fmt.Errorf("Failed to write image data to temp file for transmission: %w", err)
|
||||
}
|
||||
fname = f.Name()
|
||||
}
|
||||
gc := gc_for_image(imgd, frame_num, frame)
|
||||
if is_temp {
|
||||
gc.SetTransmission(graphics.GRT_transmission_tempfile)
|
||||
} else {
|
||||
gc.SetTransmission(graphics.GRT_transmission_file)
|
||||
}
|
||||
gc.SetTransmission(utils.IfElse(is_temp, graphics.GRT_transmission_tempfile, graphics.GRT_transmission_file))
|
||||
if data_size > 0 {
|
||||
gc.SetDataSize(uint64(data_size))
|
||||
}
|
||||
@@ -178,14 +156,9 @@ func transmit_file(imgd *image_data, frame_num int, frame *image_frame) (err err
|
||||
func transmit_stream(imgd *image_data, frame_num int, frame *image_frame) (err error) {
|
||||
data := frame.in_memory_bytes
|
||||
if data == nil {
|
||||
f, err := os.Open(frame.filename)
|
||||
data, err = os.ReadFile(frame.filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to open image data output file: %s with error: %w", frame.filename, err)
|
||||
}
|
||||
data, err = io.ReadAll(f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to read data from image output data file: %w", err)
|
||||
return fmt.Errorf("Failed to read image data output file: %s with error: %w", frame.filename, err)
|
||||
}
|
||||
}
|
||||
gc := gc_for_image(imgd, frame_num, frame)
|
||||
@@ -282,20 +255,6 @@ func transmit_image(imgd *image_data, no_trailing_newline bool) {
|
||||
if seen_image_ids == nil {
|
||||
seen_image_ids = utils.NewSet[uint32](32)
|
||||
}
|
||||
defer func() {
|
||||
for _, frame := range imgd.frames {
|
||||
if frame.filename_is_temporary && frame.filename != "" {
|
||||
os.Remove(frame.filename)
|
||||
frame.filename = ""
|
||||
}
|
||||
if frame.shm != nil {
|
||||
_ = frame.shm.Unlink()
|
||||
frame.shm.Close()
|
||||
frame.shm = nil
|
||||
}
|
||||
frame.in_memory_bytes = nil
|
||||
}
|
||||
}()
|
||||
var f func(*image_data, int, *image_frame) error
|
||||
if opts.TransferMode != "detect" {
|
||||
switch opts.TransferMode {
|
||||
|
||||
Reference in New Issue
Block a user