VideoTools/vendor/github.com/fyne-io/image/ico/reader.go
Stu Leak 68df790d27 Fix player frame generation and video playback
Major improvements to UnifiedPlayer:

1. GetFrameImage() now works when paused for responsive UI updates
2. Play() method properly starts FFmpeg process
3. Frame display loop runs continuously for smooth video display
4. Disabled audio temporarily to fix video playback fundamentals
5. Simplified FFmpeg command to focus on video stream only

Player now:
- Generates video frames correctly
- Shows video when paused
- Has responsive progress tracking
- Starts playback properly

Next steps: Re-enable audio playback once video is stable
2026-01-07 22:20:00 -05:00

224 lines
5.2 KiB
Go

package ico
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"io"
bmp "github.com/jsummers/gobmp"
)
var pngHeader = []byte{'\x89', 'P', 'N', 'G', '\r', '\n', '\x1a', '\n'}
func init() {
image.RegisterFormat("ico", "\x00\x00\x01\x00?????\x00", Decode, DecodeConfig)
}
func Decode(r io.Reader) (image.Image, error) {
var d decoder
if err := d.decode(r); err != nil {
return nil, err
}
img := d.images[0]
// return the image with the highest resolution, if any
for i := 1; i < len(d.images); i++ {
if d.images[i].Bounds().Dx() > img.Bounds().Dx() {
img = d.images[i]
}
}
return img, nil
}
func DecodeAll(r io.Reader) ([]image.Image, error) {
var d decoder
if err := d.decode(r); err != nil {
return nil, err
}
return d.images, nil
}
func DecodeConfig(r io.Reader) (image.Config, error) {
var (
d decoder
cfg image.Config
err error
)
if err = d.decodeHeader(r); err != nil {
return cfg, err
}
if err = d.decodeEntries(r); err != nil {
return cfg, err
}
e := d.entries[0]
buf := make([]byte, e.Size+14)
n, err := io.ReadFull(r, buf[14:])
if err != nil && err != io.ErrUnexpectedEOF {
return cfg, err
}
buf = buf[:14+n]
if n > 8 && bytes.Equal(buf[14:22], pngHeader) {
return png.DecodeConfig(bytes.NewReader(buf[14:]))
}
d.forgeBMPHead(buf, &e)
return bmp.DecodeConfig(bytes.NewReader(buf))
}
type direntry struct {
Width byte
Height byte
Palette byte
_ byte
Plane uint16
Bits uint16
Size uint32
Offset uint32
}
type head struct {
Zero uint16
Type uint16
Number uint16
}
type decoder struct {
head head
entries []direntry
images []image.Image
}
func (d *decoder) decode(r io.Reader) (err error) {
if err = d.decodeHeader(r); err != nil {
return err
}
if err = d.decodeEntries(r); err != nil {
return err
}
d.images = make([]image.Image, d.head.Number)
for i := range d.entries {
e := &(d.entries[i])
data := make([]byte, e.Size+14)
n, err := io.ReadFull(r, data[14:])
if err != nil && err != io.ErrUnexpectedEOF {
return err
}
data = data[:14+n]
if n > 8 && bytes.Equal(data[14:22], pngHeader) { // decode as PNG
if d.images[i], err = png.Decode(bytes.NewReader(data[14:])); err != nil {
return err
}
} else { // decode as BMP
maskData := d.forgeBMPHead(data, e)
if maskData != nil {
data = data[:n+14-len(maskData)]
}
if d.images[i], err = bmp.Decode(bytes.NewReader(data)); err != nil {
return err
}
bounds := d.images[i].Bounds()
mask := image.NewAlpha(image.Rect(0, 0, bounds.Dx(), bounds.Dy()))
masked := image.NewNRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy()))
for row := 0; row < int(e.Height); row++ {
for col := 0; col < int(e.Width); col++ {
if maskData != nil {
rowSize := (int(e.Width) + 31) / 32 * 4
if (maskData[row*rowSize+col/8]>>(7-uint(col)%8))&0x01 != 1 {
mask.SetAlpha(col, int(e.Height)-row-1, color.Alpha{255})
}
} else { // 32-Bit
rowSize := (int(e.Width)*32 + 31) / 32 * 4
offset := int(binary.LittleEndian.Uint32(data[10:14]))
mask.SetAlpha(col, int(e.Height)-row-1, color.Alpha{data[offset+row*rowSize+col*4+3]})
}
}
}
draw.DrawMask(masked, masked.Bounds(), d.images[i], bounds.Min, mask, bounds.Min, draw.Src)
d.images[i] = masked
}
}
return nil
}
func (d *decoder) decodeHeader(r io.Reader) error {
binary.Read(r, binary.LittleEndian, &(d.head))
if d.head.Zero != 0 || d.head.Type != 1 {
return fmt.Errorf("corrupted head: [%x,%x]", d.head.Zero, d.head.Type)
}
return nil
}
// ErrorNoEntries is returned when the decoded image contains no entries.
var ErrorNoEntries = errors.New("no entries")
func (d *decoder) decodeEntries(r io.Reader) error {
n := int(d.head.Number)
d.entries = make([]direntry, n)
for i := 0; i < n; i++ {
if err := binary.Read(r, binary.LittleEndian, &(d.entries[i])); err != nil {
return err
}
}
if len(d.entries) < 1 {
return ErrorNoEntries
}
return nil
}
func (d *decoder) forgeBMPHead(buf []byte, e *direntry) (mask []byte) {
// See en.wikipedia.org/wiki/BMP_file_format
data := buf[14:]
imageSize := len(data)
if e.Bits != 32 {
maskSize := (int(e.Width) + 31) / 32 * 4 * int(e.Height)
imageSize -= maskSize
if imageSize <= 0 {
return
}
mask = data[imageSize:]
}
copy(buf[0:2], "\x42\x4D") // Magic number
dibSize := binary.LittleEndian.Uint32(data[:4])
w := binary.LittleEndian.Uint32(data[4:8])
h := binary.LittleEndian.Uint32(data[8:12])
if h > w {
binary.LittleEndian.PutUint32(data[8:12], h/2)
}
binary.LittleEndian.PutUint32(buf[2:6], uint32(imageSize)) // File size
// Calculate offset into image data
numColors := binary.LittleEndian.Uint32(data[32:36])
bits := binary.LittleEndian.Uint16(data[14:16])
switch bits {
case 1, 2, 4, 8:
x := uint32(1 << bits)
if numColors == 0 || numColors > x {
numColors = x
}
default:
numColors = 0
}
var numColorsSize uint32
switch dibSize {
case 12, 64:
numColorsSize = numColors * 3
default:
numColorsSize = numColors * 4
}
offset := 14 + dibSize + numColorsSize
if dibSize > 40 && int(dibSize-4) <= len(data) {
offset += binary.LittleEndian.Uint32(data[dibSize-8 : dibSize-4])
}
binary.LittleEndian.PutUint32(buf[10:14], offset)
return
}