Start work on image support for new diff kitten
This commit is contained in:
parent
18445e20ff
commit
404a775f4b
@ -136,18 +136,7 @@ func calc_min_gap(gaps []int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (frame *image_frame) set_disposal(anchor_frame int, disposal byte) int {
|
func (frame *image_frame) set_disposal(anchor_frame int, disposal byte) int {
|
||||||
if frame.number > 1 {
|
anchor_frame, frame.compose_onto = images.SetGIFFrameDisposal(frame.number, anchor_frame, disposal)
|
||||||
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
|
return anchor_frame
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +148,7 @@ func (frame *image_frame) set_delay(gap, min_gap int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func add_gif_frames(ctx *images.Context, imgd *image_data, gf *gif.GIF) error {
|
func add_gif_frames(ctx *images.Context, imgd *image_data, gf *gif.GIF) error {
|
||||||
min_gap := calc_min_gap(gf.Delay)
|
min_gap := images.CalcMinimumGIFGap(gf.Delay)
|
||||||
scale_image(imgd)
|
scale_image(imgd)
|
||||||
anchor_frame := 1
|
anchor_frame := 1
|
||||||
for i, paletted_img := range gf.Image {
|
for i, paletted_img := range gf.Image {
|
||||||
|
|||||||
133
tools/utils/images/loading.go
Normal file
133
tools/utils/images/loading.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
package images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/gif"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"kitty/tools/utils"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = fmt.Print
|
||||||
|
|
||||||
|
type ImageFrame struct {
|
||||||
|
Width, Height, Left, Top int
|
||||||
|
Number int // 1-based number
|
||||||
|
Compose_onto int // number of frame to compose onto
|
||||||
|
Delay_ms int32 // negative for gapless frame, zero ignored, positive is number of ms
|
||||||
|
Is_opaque bool
|
||||||
|
Img image.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageData struct {
|
||||||
|
Width, Height int
|
||||||
|
Format_uppercase string
|
||||||
|
Frames []*ImageFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalcMinimumGIFGap(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, gaps...)
|
||||||
|
min_gap := 0
|
||||||
|
if max_gap <= 0 {
|
||||||
|
min_gap = 10
|
||||||
|
}
|
||||||
|
return min_gap
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetGIFFrameDisposal(number, anchor_frame int, disposal byte) (int, int) {
|
||||||
|
compose_onto := 0
|
||||||
|
if number > 1 {
|
||||||
|
switch disposal {
|
||||||
|
case gif.DisposalNone:
|
||||||
|
compose_onto = number - 1
|
||||||
|
anchor_frame = number
|
||||||
|
case gif.DisposalBackground:
|
||||||
|
// see https://github.com/golang/go/issues/20694
|
||||||
|
anchor_frame = number
|
||||||
|
case gif.DisposalPrevious:
|
||||||
|
compose_onto = anchor_frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return anchor_frame, compose_onto
|
||||||
|
}
|
||||||
|
|
||||||
|
func open_native_gif(f io.Reader, ans *ImageData) error {
|
||||||
|
gif_frames, err := gif.DecodeAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
min_gap := CalcMinimumGIFGap(gif_frames.Delay)
|
||||||
|
anchor_frame := 1
|
||||||
|
for i, paletted_img := range gif_frames.Image {
|
||||||
|
b := paletted_img.Bounds()
|
||||||
|
frame := ImageFrame{Img: paletted_img, Left: b.Min.X, Top: b.Min.Y, Width: b.Dx(), Height: b.Dy(), Number: len(ans.Frames) + 1, Is_opaque: paletted_img.Opaque()}
|
||||||
|
frame.Delay_ms = int32(utils.Max(min_gap, gif_frames.Delay[i]) * 10)
|
||||||
|
if frame.Delay_ms == 0 {
|
||||||
|
frame.Delay_ms = -1 // gapless frame
|
||||||
|
}
|
||||||
|
anchor_frame, frame.Compose_onto = SetGIFFrameDisposal(frame.Number, anchor_frame, gif_frames.Disposal[i])
|
||||||
|
ans.Frames = append(ans.Frames, &frame)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func OpenNativeImageFromReader(f io.ReadSeeker) (ans *ImageData, err error) {
|
||||||
|
c, fmt, err := image.DecodeConfig(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.Seek(0, os.SEEK_SET)
|
||||||
|
ans = &ImageData{Width: c.Width, Height: c.Height, Format_uppercase: strings.ToUpper(fmt)}
|
||||||
|
|
||||||
|
if ans.Format_uppercase == "GIF" {
|
||||||
|
err = open_native_gif(f, ans)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
img, err := imaging.Decode(f, imaging.AutoOrientation(true))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b := img.Bounds()
|
||||||
|
ans.Frames = []*ImageFrame{{Img: img, Left: b.Min.X, Top: b.Min.Y, Width: b.Dx(), Height: b.Dy()}}
|
||||||
|
ans.Frames[0].Is_opaque = c.ColorModel == color.YCbCrModel || c.ColorModel == color.GrayModel || c.ColorModel == color.Gray16Model || c.ColorModel == color.CMYKModel || ans.Format_uppercase == "JPEG" || ans.Format_uppercase == "JPG" || IsOpaque(img)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func OpenMagickImageFromPath(path string) (ans *ImageData, err error) {
|
||||||
|
// TODO: Implement this
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func OpenImageFromPath(path string) (ans *ImageData, err error) {
|
||||||
|
mt := utils.GuessMimeType(path)
|
||||||
|
if DecodableImageTypes[mt] {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
ans, err = OpenNativeImageFromReader(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to load image at %#v with error: %w", path, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return OpenMagickImageFromPath(path)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user