Get GIF basically working

This commit is contained in:
Kovid Goyal 2023-01-02 21:40:39 +05:30
parent cd5dab581b
commit 7237e5cf9c
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 109 additions and 21 deletions

View File

@ -5,7 +5,9 @@ package icat
import (
"fmt"
"image"
"image/gif"
"kitty/tools/tui/graphics"
"kitty/tools/utils"
"kitty/tools/utils/images"
"kitty/tools/utils/shm"
@ -14,7 +16,7 @@ import (
var _ = fmt.Print
func add_frame(imgd *image_data, img image.Image, is_opaque bool) {
func add_frame(imgd *image_data, img image.Image, is_opaque bool) *image_frame {
if flip {
img = imaging.FlipV(img)
}
@ -22,7 +24,7 @@ func add_frame(imgd *image_data, img image.Image, is_opaque bool) {
img = imaging.FlipH(img)
}
b := img.Bounds()
f := image_frame{width: b.Dx(), height: b.Dy()}
f := image_frame{width: b.Dx(), height: b.Dy(), number: len(imgd.frames), left: b.Min.X, top: b.Min.Y}
dest_rect := image.Rect(0, 0, f.width, f.height)
var final_img image.Image
@ -53,6 +55,7 @@ func add_frame(imgd *image_data, img image.Image, is_opaque bool) {
}
images.PasteCenter(final_img, img, remove_alpha)
imgd.frames = append(imgd.frames, &f)
return &f
}
func load_one_frame_image(imgd *image_data, src *opened_input) (img image.Image, is_opaque bool, err error) {
@ -78,10 +81,49 @@ func load_one_frame_image(imgd *image_data, src *opened_input) (img image.Image,
return
}
func add_gif_frames(imgd *image_data, gf *gif.GIF) error {
max_gap := utils.Max(0, gf.Delay...)
min_gap := 0
if max_gap <= 0 {
min_gap = 1
}
min_gap *= 1
anchor_frame := 1
for i, img := range gf.Image {
frame := add_frame(imgd, img, img.Opaque())
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
}
}
}
return nil
}
func render_image_with_go(imgd *image_data, src *opened_input) (err error) {
switch imgd.format_uppercase {
case "GIF":
return fmt.Errorf("TODO: implement GIF decoding")
switch {
case imgd.format_uppercase == "GIF" && opts.Loop != 0:
gif_frames, err := gif.DecodeAll(src.file)
src.Rewind()
if err != nil {
return fmt.Errorf("Failed to decode GIF file with error: %w", err)
}
err = add_gif_frames(imgd, gif_frames)
if err != nil {
return err
}
default:
img, is_opaque, err := load_one_frame_image(imgd, src)
if err != nil {

View File

@ -6,6 +6,7 @@ import (
"bytes"
"fmt"
"image"
"image/color"
"io"
"io/fs"
"net/http"
@ -138,12 +139,16 @@ func (self *opened_input) Release() {
}
type image_frame struct {
filename string
shm shm.MMap
in_memory_bytes []byte
filename_is_temporary bool
width, height int
transmission_format graphics.GRT_f
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
number int
disposal_background color.NRGBA
delay_ms int
}
type image_data struct {

View File

@ -21,19 +21,31 @@ var _ = fmt.Print
func gc_for_image(imgd *image_data, frame_num int, frame *image_frame) *graphics.GraphicsCommand {
gc := graphics.GraphicsCommand{}
gc.SetAction(graphics.GRT_action_transmit_and_display)
gc.SetDataWidth(uint64(frame.width)).SetDataHeight(uint64(frame.height))
gc.SetQuiet(graphics.GRT_quiet_silent)
if frame_num == 0 && imgd.cell_x_offset > 0 {
gc.SetXOffset(uint64(imgd.cell_x_offset))
}
if z_index != 0 {
gc.SetZIndex(z_index)
}
gc.SetFormat(frame.transmission_format)
if imgd.image_number != 0 {
gc.SetImageNumber(imgd.image_number)
}
gc.SetFormat(frame.transmission_format)
if frame_num == 0 {
gc.SetAction(graphics.GRT_action_transmit_and_display)
if imgd.cell_x_offset > 0 {
gc.SetXOffset(uint64(imgd.cell_x_offset))
}
if z_index != 0 {
gc.SetZIndex(z_index)
}
} else {
gc.SetAction(graphics.GRT_action_frame)
gc.SetGap(int32(frame.delay_ms))
if frame.compose_onto > 0 {
gc.SetOverlaidFrame(uint64(frame.compose_onto))
} else {
bg := (uint32(frame.disposal_background.R) << 24) | (uint32(frame.disposal_background.G) << 16) | (uint32(frame.disposal_background.B) << 8) | uint32(frame.disposal_background.A)
gc.SetBackgroundColor(bg)
}
gc.SetLeftEdge(uint64(frame.left)).SetTopEdge(uint64(frame.top))
}
return &gc
}
@ -222,7 +234,9 @@ func transmit_image(imgd *image_data) {
f = transmit_stream
}
if len(imgd.frames) > 1 {
imgd.image_number = rand.Uint32()
for imgd.image_number == 0 {
imgd.image_number = rand.Uint32()
}
}
place_cursor(imgd)
lp.QueueWriteString("\r")
@ -232,11 +246,38 @@ func transmit_image(imgd *image_data) {
if imgd.move_to.x > 0 {
lp.MoveCursorTo(imgd.move_to.x, imgd.move_to.y)
}
frame_control_cmd := graphics.GraphicsCommand{}
frame_control_cmd.SetAction(graphics.GRT_action_animate).SetImageNumber(imgd.image_number)
for frame_num, frame := range imgd.frames {
err := f(imgd, frame_num, frame)
if err != nil {
print_error("Failed to transmit %s with error: %v", imgd.source_name, err)
print_error("\rFailed to transmit %s with error: %v", imgd.source_name, err)
return
}
switch frame_num {
case 0:
// set gap for the first frame and number of loops for the animation
c := frame_control_cmd
c.SetTargetFrame(uint64(frame.number))
c.SetGap(int32(frame.delay_ms))
switch {
case opts.Loop < 0:
c.SetNumberOfLoops(1)
case opts.Loop > 0:
c.SetNumberOfLoops(uint64(opts.Loop) + 1)
}
c.WriteWithPayloadToLoop(lp, nil)
case 1:
c := frame_control_cmd
c.SetAnimationControl(2) // set animation to loading mode
c.WriteWithPayloadToLoop(lp, nil)
}
}
if len(imgd.frames) > 1 {
c := frame_control_cmd
c.SetAnimationControl(3) // set animation to normal mode
c.WriteWithPayloadToLoop(lp, nil)
}
if imgd.move_to.x == 0 {
lp.Println() // ensure cursor is on new line