mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-09-20 02:57:19 +03:00
More work on ImageMagick
This commit is contained in:
parent
4d21be9eb5
commit
c317c934f3
@ -120,7 +120,17 @@
|
||||
|
||||
--silent
|
||||
type=bool-set
|
||||
Do not print out anything to STDOUT during operation.
|
||||
Not used, present for legacy compatibility.
|
||||
|
||||
|
||||
--engine
|
||||
type=choices
|
||||
choices=auto,builtin,magick
|
||||
default=auto
|
||||
The engine used for decoding and processing of images. The default is to use
|
||||
the most appropriate engine. The :code:`builtin` engine uses Go's native
|
||||
imaging libraries. The :code:`magick` engine uses ImageMagick which requires
|
||||
it to be installed on the system.
|
||||
|
||||
|
||||
--z-index -z
|
||||
|
@ -3,11 +3,344 @@
|
||||
package icat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/gif"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"kitty/tools/tui/graphics"
|
||||
"kitty/tools/utils"
|
||||
"kitty/tools/utils/images"
|
||||
"kitty/tools/utils/shm"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func render_image_with_magick(imgd *image_data, src *opened_input) error {
|
||||
var find_exe_lock sync.Once
|
||||
var magick_exe string = ""
|
||||
|
||||
func find_magick_exe() {
|
||||
magick_exe = utils.Which("magick")
|
||||
}
|
||||
|
||||
func run_magick(path string, cmd []string) ([]byte, error) {
|
||||
c := exec.Command(cmd[0], cmd[1:]...)
|
||||
output, err := c.Output()
|
||||
if err != nil {
|
||||
var exit_err *exec.ExitError
|
||||
if errors.As(err, &exit_err) {
|
||||
return nil, fmt.Errorf("Running the command: %s\nFailed with error:\n%s", strings.Join(cmd, " "), string(exit_err.Stderr))
|
||||
}
|
||||
return nil, fmt.Errorf("Could not find the program: %#v. Is ImageMagick installed and in your PATH?", cmd[0])
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
type IdentifyOutput struct {
|
||||
Fmt, Canvas, Transparency, Gap, Index, Size, Dpi, Dispose, Orientation string
|
||||
}
|
||||
|
||||
type IdentifyRecord struct {
|
||||
FmtUppercase string
|
||||
Gap int
|
||||
Canvas struct{ Width, Height, Left, Top int }
|
||||
Width, Height int
|
||||
Dpi struct{ X, Y float64 }
|
||||
Index int
|
||||
Mode graphics.GRT_f
|
||||
NeedsBlend bool
|
||||
Disposal int
|
||||
DimensionsSwapped bool
|
||||
}
|
||||
|
||||
func parse_identify_record(ans *IdentifyRecord, raw *IdentifyOutput) (err error) {
|
||||
ans.FmtUppercase = strings.ToUpper(raw.Fmt)
|
||||
if raw.Gap != "" {
|
||||
ans.Gap, err = strconv.Atoi(raw.Gap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid gap value in identify output: %s", raw.Gap)
|
||||
}
|
||||
ans.Gap = utils.Max(0, ans.Gap)
|
||||
}
|
||||
area, pos, found := utils.Cut(raw.Canvas, "+")
|
||||
ok := false
|
||||
if found {
|
||||
w, h, found := utils.Cut(area, "x")
|
||||
if found {
|
||||
ans.Canvas.Width, err = strconv.Atoi(w)
|
||||
if err == nil {
|
||||
ans.Canvas.Height, err = strconv.Atoi(h)
|
||||
if err == nil {
|
||||
x, y, found := utils.Cut(pos, "+")
|
||||
if found {
|
||||
ans.Canvas.Left, err = strconv.Atoi(x)
|
||||
if err == nil {
|
||||
ans.Canvas.Top, err = strconv.Atoi(y)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("Invalid canvas value in identify output: %s", raw.Canvas)
|
||||
}
|
||||
w, h, found := utils.Cut(raw.Size, "x")
|
||||
ok = false
|
||||
if found {
|
||||
ans.Width, err = strconv.Atoi(w)
|
||||
if err == nil {
|
||||
ans.Height, err = strconv.Atoi(h)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("Invalid size value in identify output: %s", raw.Size)
|
||||
}
|
||||
x, y, found := utils.Cut(raw.Dpi, "x")
|
||||
ok = false
|
||||
if found {
|
||||
ans.Dpi.X, err = strconv.ParseFloat(x, 64)
|
||||
if err == nil {
|
||||
ans.Dpi.Y, err = strconv.ParseFloat(y, 64)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("Invalid dpi value in identify output: %s", raw.Dpi)
|
||||
}
|
||||
ans.Index, err = strconv.Atoi(raw.Index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid index value in identify output: %s", raw.Index)
|
||||
}
|
||||
q := strings.ToLower(raw.Transparency)
|
||||
if q == "blend" || q == "true" {
|
||||
ans.Mode = graphics.GRT_format_rgba
|
||||
} else {
|
||||
ans.Mode = graphics.GRT_format_rgb
|
||||
}
|
||||
ans.NeedsBlend = q == "blend"
|
||||
switch strings.ToLower(raw.Dispose) {
|
||||
case "undefined":
|
||||
ans.Disposal = 0
|
||||
case "none":
|
||||
ans.Disposal = gif.DisposalNone
|
||||
case "background":
|
||||
ans.Disposal = gif.DisposalBackground
|
||||
case "previous":
|
||||
ans.Disposal = gif.DisposalPrevious
|
||||
default:
|
||||
return fmt.Errorf("Invalid value for dispose: %s", raw.Dispose)
|
||||
}
|
||||
switch raw.Orientation {
|
||||
case "5", "6", "7", "8":
|
||||
ans.DimensionsSwapped = true
|
||||
}
|
||||
if ans.DimensionsSwapped {
|
||||
ans.Canvas.Width, ans.Canvas.Height = ans.Canvas.Height, ans.Canvas.Width
|
||||
ans.Width, ans.Height = ans.Height, ans.Width
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Identify(path string) (ans []IdentifyRecord, err error) {
|
||||
find_exe_lock.Do(find_magick_exe)
|
||||
cmd := []string{"identify"}
|
||||
if magick_exe != "" {
|
||||
cmd = []string{magick_exe, cmd[0]}
|
||||
}
|
||||
q := `{"fmt":"%m","canvas":"%g","transparency":"%A","gap":"%T","index":"%p","size":"%wx%h",` +
|
||||
`"dpi":"%xx%y","dispose":"%D","orientation":"%[EXIF:Orientation]"},`
|
||||
cmd = append(cmd, "-format", q, "--", path)
|
||||
output, err := run_magick(path, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = bytes.TrimRight(bytes.TrimSpace(output), ",")
|
||||
raw_json := make([]byte, 0, len(output)+2)
|
||||
raw_json = append(raw_json, '[')
|
||||
raw_json = append(raw_json, output...)
|
||||
raw_json = append(raw_json, ']')
|
||||
var records []IdentifyOutput
|
||||
err = json.Unmarshal(raw_json, &records)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("The ImageMagick identify program returned malformed output, with error: %w", err)
|
||||
}
|
||||
ans = make([]IdentifyRecord, len(records))
|
||||
for i, rec := range records {
|
||||
err = parse_identify_record(&ans[i], &rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ans, nil
|
||||
}
|
||||
|
||||
type RenderOptions struct {
|
||||
RemoveAlpha *images.NRGBColor
|
||||
Flip, Flop bool
|
||||
ResizeTo image.Point
|
||||
OnlyFirstFrame bool
|
||||
}
|
||||
|
||||
func make_temp_dir() (ans string, err error) {
|
||||
if shm.SHM_DIR != "" {
|
||||
ans, err = os.MkdirTemp(shm.SHM_DIR, shm_template)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return os.MkdirTemp("", shm_template)
|
||||
}
|
||||
|
||||
func Render(path string, ro *RenderOptions, frames []IdentifyRecord) (ans []*image_frame, err error) {
|
||||
find_exe_lock.Do(find_magick_exe)
|
||||
cmd := []string{"convert"}
|
||||
if magick_exe != "" {
|
||||
cmd = []string{magick_exe, cmd[0]}
|
||||
}
|
||||
ans = make([]*image_frame, 0, len(frames))
|
||||
if ro.RemoveAlpha != nil {
|
||||
cmd = append(cmd, "-background", ro.RemoveAlpha.AsSharp(), "-alpha", "remove")
|
||||
} else {
|
||||
cmd = append(cmd, "-background", "none")
|
||||
}
|
||||
if ro.Flip {
|
||||
cmd = append(cmd, "-flip")
|
||||
}
|
||||
if ro.Flop {
|
||||
cmd = append(cmd, "-flop")
|
||||
}
|
||||
cpath := path
|
||||
if ro.OnlyFirstFrame {
|
||||
cpath += "[0]"
|
||||
}
|
||||
has_multiple_frames := len(frames) > 1
|
||||
get_multiple_frames := has_multiple_frames && !ro.OnlyFirstFrame
|
||||
cmd = append(cmd, "--", cpath, "-auto-orient")
|
||||
if ro.ResizeTo.X > 0 {
|
||||
rcmd := []string{"-resize", fmt.Sprintf("%dx%d!", ro.ResizeTo.X, ro.ResizeTo.Y)}
|
||||
if get_multiple_frames {
|
||||
cmd = append(cmd, "-coalesce")
|
||||
cmd = append(cmd, rcmd...)
|
||||
cmd = append(cmd, "-deconstruct")
|
||||
} else {
|
||||
cmd = append(cmd, rcmd...)
|
||||
}
|
||||
}
|
||||
cmd = append(cmd, "-depth", "8", "-set", "filename:f", "%w-%h-%g-%p")
|
||||
if get_multiple_frames {
|
||||
cmd = append(cmd, "+adjoin")
|
||||
}
|
||||
tdir, err := make_temp_dir()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create temporary directory to hold ImageMagick output with error: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tdir)
|
||||
mode := "rgba"
|
||||
if frames[0].Mode == graphics.GRT_format_rgb {
|
||||
mode = "rgb"
|
||||
}
|
||||
cmd = append(cmd, filepath.Join(tdir, "im-%[filename:f]."+mode))
|
||||
_, err = run_magick(path, cmd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
entries, err := os.ReadDir(tdir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to read temp dir used to store ImageMagick output with error: %w", err)
|
||||
}
|
||||
base_dir := filepath.Dir(tdir)
|
||||
defer func() {
|
||||
if err != nil && ans != nil {
|
||||
for _, frame := range ans {
|
||||
if frame.filename_is_temporary {
|
||||
os.Remove(frame.filename)
|
||||
}
|
||||
}
|
||||
ans = nil
|
||||
}
|
||||
}()
|
||||
gaps := make([]int, len(frames))
|
||||
for i, frame := range frames {
|
||||
gaps[i] = frame.Gap
|
||||
}
|
||||
min_gap := calc_min_gap(gaps)
|
||||
for _, entry := range entries {
|
||||
fname := entry.Name()
|
||||
p, _, _ := utils.Cut(fname, ".")
|
||||
parts := strings.Split(p, "-")
|
||||
if len(parts) < 1 {
|
||||
continue
|
||||
}
|
||||
index, cerr := strconv.Atoi(parts[len(parts)-1])
|
||||
if cerr != nil || index < 0 || index >= len(frames) {
|
||||
continue
|
||||
}
|
||||
identify_data := frames[index]
|
||||
df, err := os.CreateTemp(base_dir, graphics.TempTemplate+"."+mode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create a temporary file in %s with error: %w", base_dir, err)
|
||||
}
|
||||
err = os.Rename(filepath.Join(tdir, fname), df.Name())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to rename a temporary file in %s with error: %w", tdir, err)
|
||||
}
|
||||
df.Close()
|
||||
frame := image_frame{
|
||||
number: index + 1, width: identify_data.Width, height: identify_data.Height,
|
||||
left: identify_data.Canvas.Left, top: identify_data.Canvas.Top,
|
||||
transmission_format: identify_data.Mode, filename_is_temporary: true,
|
||||
filename: df.Name(),
|
||||
}
|
||||
frame.set_delay(identify_data.Gap, min_gap)
|
||||
ans = append(ans, &frame)
|
||||
}
|
||||
if len(ans) < len(frames) {
|
||||
return nil, fmt.Errorf("Failed to render %d out of %d frames", len(frames)-len(ans), len(frames))
|
||||
}
|
||||
ans = utils.Sort(ans, func(a, b *image_frame) bool { return a.number < b.number })
|
||||
anchor_frame := 1
|
||||
for i, frame := range ans {
|
||||
anchor_frame = frame.set_disposal(anchor_frame, byte(frames[i].Disposal))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func render_image_with_magick(imgd *image_data, src *opened_input) (err error) {
|
||||
err = src.PutOnFilesystem()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
frames, err := Identify(src.FileSystemName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imgd.format_uppercase = frames[0].FmtUppercase
|
||||
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 := 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(), &ro, frames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -134,8 +134,9 @@ func print_error(format string, args ...any) {
|
||||
} else {
|
||||
lp.QueueWriteString("\r")
|
||||
lp.ClearToEndOfLine()
|
||||
lp.QueueWriteString(fmt.Sprintf(format, args...))
|
||||
lp.QueueWriteString("\r\n")
|
||||
for _, line := range utils.Splitlines(fmt.Sprintf(format, args...)) {
|
||||
lp.Println(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,8 @@ func resize_frame(imgd *image_data, img image.Image) (image.Image, image.Rectang
|
||||
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) *image_frame {
|
||||
is_opaque := false
|
||||
if imgd.format_uppercase == "JPEG" {
|
||||
@ -42,7 +44,6 @@ func add_frame(ctx *images.Context, imgd *image_data, img image.Image) *image_fr
|
||||
f := image_frame{width: b.Dx(), height: b.Dy(), number: len(imgd.frames) + 1, left: b.Min.X, top: b.Min.Y}
|
||||
dest_rect := image.Rect(0, 0, f.width, f.height)
|
||||
var final_img image.Image
|
||||
const shm_template = "kitty-icat-*"
|
||||
bytes_per_pixel := 4
|
||||
|
||||
if is_opaque || remove_alpha != nil {
|
||||
@ -88,7 +89,7 @@ func add_frame(ctx *images.Context, imgd *image_data, img image.Image) *image_fr
|
||||
return &f
|
||||
}
|
||||
|
||||
func scale_image(imgd *image_data) {
|
||||
func scale_image(imgd *image_data) bool {
|
||||
if imgd.needs_scaling {
|
||||
width, height := imgd.canvas_width, imgd.canvas_height
|
||||
if imgd.canvas_width < imgd.available_width && opts.ScaleUp && place != nil {
|
||||
@ -101,7 +102,9 @@ func scale_image(imgd *image_data) {
|
||||
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
|
||||
}
|
||||
|
||||
func load_one_frame_image(ctx *images.Context, imgd *image_data, src *opened_input) (img image.Image, err error) {
|
||||
@ -118,38 +121,51 @@ func load_one_frame_image(ctx *images.Context, imgd *image_data, src *opened_inp
|
||||
return
|
||||
}
|
||||
|
||||
func add_gif_frames(ctx *images.Context, imgd *image_data, gf *gif.GIF) error {
|
||||
func calc_min_gap(gaps []int) int {
|
||||
// Some broken GIF images have all zero gaps, browsers with their usual
|
||||
// idiot ideas render these with a default 100ms gap https://bugzilla.mozilla.org/show_bug.cgi?id=125137
|
||||
// Browsers actually force a 100ms gap at any zero gap frame, but that
|
||||
// just means it is impossible to deliberately use zero gap frames for
|
||||
// sophisticated blending, so we dont do that.
|
||||
max_gap := utils.Max(0, gf.Delay...)
|
||||
max_gap := utils.Max(0, gaps...)
|
||||
min_gap := 0
|
||||
if max_gap <= 0 {
|
||||
min_gap = 10
|
||||
}
|
||||
min_gap *= 1
|
||||
return min_gap
|
||||
}
|
||||
|
||||
func (frame *image_frame) set_disposal(anchor_frame int, disposal byte) int {
|
||||
if frame.number > 1 {
|
||||
switch disposal {
|
||||
case gif.DisposalNone:
|
||||
frame.compose_onto = frame.number - 1
|
||||
anchor_frame = frame.number
|
||||
case gif.DisposalBackground:
|
||||
// see https://github.com/golang/go/issues/20694
|
||||
anchor_frame = frame.number
|
||||
case gif.DisposalPrevious:
|
||||
frame.compose_onto = anchor_frame
|
||||
}
|
||||
}
|
||||
return anchor_frame
|
||||
}
|
||||
|
||||
func (frame *image_frame) set_delay(gap, min_gap int) {
|
||||
frame.delay_ms = utils.Max(min_gap, gap) * 10
|
||||
if frame.delay_ms == 0 {
|
||||
frame.delay_ms = -1
|
||||
}
|
||||
}
|
||||
|
||||
func add_gif_frames(ctx *images.Context, imgd *image_data, gf *gif.GIF) error {
|
||||
min_gap := calc_min_gap(gf.Delay)
|
||||
scale_image(imgd)
|
||||
anchor_frame := 1
|
||||
for i, paletted_img := range gf.Image {
|
||||
frame := add_frame(ctx, imgd, paletted_img)
|
||||
frame.delay_ms = utils.Max(min_gap, gf.Delay[i]) * 10
|
||||
if frame.delay_ms == 0 {
|
||||
frame.delay_ms = -1
|
||||
}
|
||||
if i > 0 {
|
||||
switch gf.Disposal[i] {
|
||||
case gif.DisposalNone:
|
||||
frame.compose_onto = frame.number - 1
|
||||
anchor_frame = frame.number
|
||||
case gif.DisposalBackground:
|
||||
// see https://github.com/golang/go/issues/20694
|
||||
anchor_frame = frame.number
|
||||
case gif.DisposalPrevious:
|
||||
frame.compose_onto = anchor_frame
|
||||
}
|
||||
}
|
||||
frame.set_delay(gf.Delay[i], min_gap)
|
||||
anchor_frame = frame.set_disposal(anchor_frame, gf.Disposal[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -138,6 +138,28 @@ func (self *opened_input) Release() {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *opened_input) PutOnFilesystem() (err error) {
|
||||
if self.name_to_unlink != "" {
|
||||
return
|
||||
}
|
||||
f, err := graphics.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
|
||||
@ -250,31 +272,42 @@ func process_arg(arg input_arg) {
|
||||
f.file = q
|
||||
}
|
||||
defer f.Release()
|
||||
c, format, err := image.DecodeConfig(f.file)
|
||||
f.Rewind()
|
||||
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 == "native" {
|
||||
c, format, err = image.DecodeConfig(f.file)
|
||||
f.Rewind()
|
||||
can_use_go = err == nil
|
||||
}
|
||||
if !keep_going.Load() {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
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 {
|
||||
report_error(arg.value, "Could not render image to RGB", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = render_image_with_magick(&imgd, &f)
|
||||
if err != nil {
|
||||
report_error(arg.value, "ImageMagick failed", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
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 {
|
||||
report_error(arg.value, "Could not render image to RGB", err)
|
||||
if !keep_going.Load() {
|
||||
return
|
||||
}
|
||||
send_output(&imgd)
|
||||
|
@ -19,13 +19,15 @@ import (
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
const TempTemplate = "kitty-tty-graphics-protocol-*"
|
||||
|
||||
func CreateTemp() (*os.File, error) {
|
||||
return os.CreateTemp("", "tty-graphics-protocol-*")
|
||||
return os.CreateTemp("", TempTemplate)
|
||||
}
|
||||
|
||||
func CreateTempInRAM() (*os.File, error) {
|
||||
if shm.SHM_DIR != "" {
|
||||
f, err := os.CreateTemp(shm.SHM_DIR, "tty-graphics-protocol-*")
|
||||
f, err := os.CreateTemp(shm.SHM_DIR, TempTemplate)
|
||||
if err == nil {
|
||||
return f, err
|
||||
}
|
||||
|
@ -14,6 +14,10 @@ type NRGBColor struct {
|
||||
R, G, B uint8
|
||||
}
|
||||
|
||||
func (c NRGBColor) AsSharp() string {
|
||||
return fmt.Sprintf("#%02X%02X%02X", c.R, c.G, c.B)
|
||||
}
|
||||
|
||||
func (c NRGBColor) RGBA() (r, g, b, a uint32) {
|
||||
r = uint32(c.R)
|
||||
r |= r << 8
|
||||
|
Loading…
Reference in New Issue
Block a user