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
588 lines
14 KiB
Go
588 lines
14 KiB
Go
// ◄◄◄ gobmp/reader.go ►►►
|
|
// Copyright © 2012 Jason Summers
|
|
// Use of this code is governed by an MIT-style license that can
|
|
// be found in the readme.md file.
|
|
//
|
|
// BMP file decoder
|
|
//
|
|
|
|
// Package gobmp implements a BMP image decoder and encoder.
|
|
package gobmp
|
|
|
|
import "image"
|
|
import "image/color"
|
|
import "io"
|
|
import "fmt"
|
|
|
|
const (
|
|
bI_RGB = 0
|
|
bI_RLE8 = 1
|
|
bI_RLE4 = 2
|
|
bI_BITFIELDS = 3
|
|
)
|
|
|
|
type bitFieldsInfo struct {
|
|
mask uint32
|
|
shift uint
|
|
scale float64 // Amount to multiply the sample value by, to scale it to [0..255]
|
|
}
|
|
|
|
type decoder struct {
|
|
r io.Reader
|
|
|
|
img_Paletted *image.Paletted // Used if dstHasPalette is true
|
|
img_NRGBA *image.NRGBA // Used otherwise
|
|
|
|
bfOffBits uint32
|
|
headerSize uint32
|
|
width int
|
|
height int
|
|
bitCount int
|
|
biCompression uint32
|
|
isTopDown bool
|
|
|
|
srcPalNumEntries int
|
|
srcPalBytesPerEntry int
|
|
srcPalSizeInBytes int
|
|
dstPalNumEntries int
|
|
dstHasPalette bool
|
|
dstPalette color.Palette
|
|
|
|
hasBitFieldsSegment bool
|
|
bitFieldsSegmentSize int
|
|
bitFields [4]bitFieldsInfo
|
|
}
|
|
|
|
// An UnsupportedError reports that the input uses a valid but unimplemented
|
|
// BMP feature.
|
|
type UnsupportedError string
|
|
|
|
func (e UnsupportedError) Error() string { return "bmp: unsupported feature: " + string(e) }
|
|
|
|
// A FormatError reports that the input is not a valid BMP file.
|
|
type FormatError string
|
|
|
|
func (e FormatError) Error() string { return "bmp: invalid format: " + string(e) }
|
|
|
|
func getWORD(b []byte) uint32 {
|
|
return uint32(b[0]) | uint32(b[1])<<8
|
|
}
|
|
func getDWORD(b []byte) uint32 {
|
|
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
|
}
|
|
|
|
func decodeRow_paletted(d *decoder, buf []byte, j int) error {
|
|
for i := 0; i < d.width; i++ {
|
|
var v byte
|
|
|
|
switch d.bitCount {
|
|
case 8:
|
|
v = buf[i]
|
|
case 4:
|
|
v = (buf[i/2] >> (4 * (1 - uint(i)%2))) & 0x0f
|
|
case 2:
|
|
v = (buf[i/4] >> (2 * (3 - uint(i)%4))) & 0x03
|
|
case 1:
|
|
v = (buf[i/8] >> (1 * (7 - uint(i)%8))) & 0x01
|
|
}
|
|
if int(v) >= d.dstPalNumEntries {
|
|
// Out-of-range palette index.
|
|
// Most BMP viewers use the first palette color for such pixels, so
|
|
// that's what we'll do.
|
|
v = 0
|
|
}
|
|
d.img_Paletted.Pix[j*d.img_Paletted.Stride+i] = v
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func decodeRow_16or32(d *decoder, buf []byte, j int) error {
|
|
for i := 0; i < d.width; i++ {
|
|
var v uint32
|
|
if d.bitCount == 16 {
|
|
v = uint32(getWORD(buf[i*2 : i*2+2]))
|
|
} else { // bitCount == 32
|
|
v = getDWORD(buf[i*4 : i*4+4])
|
|
}
|
|
for k := 0; k < 4; k++ {
|
|
var sv uint8
|
|
if d.bitFields[k].mask == 0 {
|
|
if k == 3 {
|
|
// If alpha mask is missing, make the pixel opaque.
|
|
sv = 255
|
|
} else {
|
|
// If some other mask is missing, who knows what to do?
|
|
sv = 0
|
|
}
|
|
} else {
|
|
sv = uint8(0.5 + float64((v&d.bitFields[k].mask)>>d.bitFields[k].shift)*
|
|
d.bitFields[k].scale)
|
|
}
|
|
d.img_NRGBA.Pix[j*d.img_NRGBA.Stride+i*4+k] = sv
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func decodeRow_24(d *decoder, buf []byte, j int) error {
|
|
for i := 0; i < d.width; i++ {
|
|
for k := 0; k < 3; k++ {
|
|
d.img_NRGBA.Pix[j*d.img_NRGBA.Stride+i*4+k] = buf[i*3+2-k]
|
|
}
|
|
d.img_NRGBA.Pix[j*d.img_NRGBA.Stride+i*4+3] = 255
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type decodeRowFuncType func(d *decoder, buf []byte, j int) error
|
|
|
|
var rowDecoders = map[int]decodeRowFuncType{
|
|
1: decodeRow_paletted,
|
|
2: decodeRow_paletted,
|
|
4: decodeRow_paletted,
|
|
8: decodeRow_paletted,
|
|
16: decodeRow_16or32,
|
|
24: decodeRow_24,
|
|
32: decodeRow_16or32,
|
|
}
|
|
|
|
func (d *decoder) readBitsUncompressed() error {
|
|
var err error
|
|
|
|
srcRowStride := ((d.width*d.bitCount + 31) / 32) * 4
|
|
buf := make([]byte, srcRowStride)
|
|
|
|
decodeRowFunc := rowDecoders[d.bitCount]
|
|
if decodeRowFunc == nil {
|
|
return nil
|
|
}
|
|
|
|
for srcRow := 0; srcRow < d.height; srcRow++ {
|
|
var dstRow int
|
|
|
|
if d.isTopDown {
|
|
dstRow = srcRow
|
|
} else {
|
|
dstRow = d.height - srcRow - 1
|
|
}
|
|
|
|
_, err = io.ReadFull(d.r, buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = decodeRowFunc(d, buf, dstRow)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *decoder) skipBytes(n int) error {
|
|
var buf [1024]byte
|
|
|
|
for n > 0 {
|
|
bytesToRead := len(buf)
|
|
if bytesToRead > n {
|
|
bytesToRead = n
|
|
}
|
|
_, err := io.ReadFull(d.r, buf[:bytesToRead])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
n -= bytesToRead
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// If there is a gap before the bits, skip over it.
|
|
func (d *decoder) readGap() error {
|
|
currentOffset := 14 + int(d.headerSize) + d.bitFieldsSegmentSize + d.srcPalSizeInBytes
|
|
if currentOffset == int(d.bfOffBits) {
|
|
return nil
|
|
}
|
|
if currentOffset > int(d.bfOffBits) {
|
|
return FormatError("bad bfOffBits field")
|
|
}
|
|
gapSize := int(d.bfOffBits) - currentOffset
|
|
|
|
return d.skipBytes(gapSize)
|
|
}
|
|
|
|
// Read a 12-byte BITMAPCOREHEADER.
|
|
func decodeInfoHeader12(d *decoder, h []byte, configOnly bool) error {
|
|
d.width = int(getWORD(h[4:6]))
|
|
d.height = int(getWORD(h[6:8]))
|
|
d.bitCount = int(getWORD(h[10:12]))
|
|
d.srcPalBytesPerEntry = 3
|
|
if d.bitCount >= 1 && d.bitCount <= 8 {
|
|
d.srcPalNumEntries = 1 << uint(d.bitCount)
|
|
}
|
|
|
|
// Figure out how many palette entries there are.
|
|
// A full-size palette is expected, but if the palette is overlapped by the
|
|
// bitmap, we assume the palette is less than full size.
|
|
paletteStart := 14 + int(d.headerSize)
|
|
paletteEnd := paletteStart + d.srcPalBytesPerEntry*d.srcPalNumEntries
|
|
if int(d.bfOffBits) >= paletteStart+d.srcPalBytesPerEntry && int(d.bfOffBits) < paletteEnd {
|
|
d.srcPalNumEntries = (int(d.bfOffBits) - paletteStart) / 3
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Read a 40-byte BITMAPINFOHEADER.
|
|
// Use of this function does not imply that the entire header is 40 bytes.
|
|
// We may just be decoding the first 40 bytes of a 108- or 124-byte header.
|
|
func decodeInfoHeader40(d *decoder, h []byte, configOnly bool) error {
|
|
d.width = int(int32(getDWORD(h[4:8])))
|
|
d.height = int(int32(getDWORD(h[8:12])))
|
|
if d.height < 0 {
|
|
d.isTopDown = true
|
|
d.height = -d.height
|
|
}
|
|
d.bitCount = int(getWORD(h[14:16]))
|
|
if configOnly {
|
|
return nil
|
|
}
|
|
|
|
if len(h) >= 20 {
|
|
d.biCompression = getDWORD(h[16:20])
|
|
}
|
|
|
|
if d.biCompression == bI_BITFIELDS && d.headerSize == 40 && d.bitCount != 1 {
|
|
d.hasBitFieldsSegment = true
|
|
d.bitFieldsSegmentSize = 12
|
|
}
|
|
|
|
if d.biCompression == bI_RGB {
|
|
if d.bitCount == 16 {
|
|
// Default bitfields for 16-bit images:
|
|
d.recordBitFields(0x7c00, 0x03e0, 0x001f, 0)
|
|
} else if d.bitCount == 32 {
|
|
// Default bitfields for 32-bit images:
|
|
d.recordBitFields(0x00ff0000, 0x0000ff00, 0x000000ff, 0)
|
|
}
|
|
}
|
|
|
|
var biClrUsed uint32
|
|
if len(h) >= 36 {
|
|
biClrUsed = getDWORD(h[32:36])
|
|
}
|
|
if biClrUsed > 10000 {
|
|
return FormatError(fmt.Sprintf("bad palette size %d", biClrUsed))
|
|
}
|
|
|
|
// Figure out how many colors (that we care about) are in the palette.
|
|
if d.bitCount >= 1 && d.bitCount <= 8 {
|
|
if biClrUsed == 0 || biClrUsed > (1<<uint(d.bitCount)) {
|
|
d.srcPalNumEntries = 1 << uint(d.bitCount)
|
|
} else {
|
|
d.srcPalNumEntries = int(biClrUsed)
|
|
}
|
|
} else {
|
|
d.srcPalNumEntries = 0
|
|
}
|
|
|
|
d.srcPalBytesPerEntry = 4
|
|
|
|
if d.headerSize == 64 && d.srcPalNumEntries > 0 {
|
|
// A hack to allow (invalid?) OS/2v2 BMPs that have 3 bytes per palette
|
|
// entry instead of 4.
|
|
if 14+d.headerSize+uint32(d.bitFieldsSegmentSize)+3*uint32(d.srcPalNumEntries) ==
|
|
d.bfOffBits {
|
|
d.srcPalBytesPerEntry = 3
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func decodeInfoHeader108(d *decoder, h []byte, configOnly bool) error {
|
|
var err error
|
|
err = decodeInfoHeader40(d, h[:40], configOnly)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.biCompression == bI_BITFIELDS {
|
|
var bf_alpha uint32
|
|
if len(h) >= 56 {
|
|
bf_alpha = getDWORD(h[52:56])
|
|
}
|
|
d.recordBitFields(getDWORD(h[40:44]), getDWORD(h[44:48]),
|
|
getDWORD(h[48:52]), bf_alpha)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type decodeInfoHeaderFuncType func(d *decoder, h []byte, configOnly bool) error
|
|
|
|
func readInfoHeader(d *decoder, configOnly bool) error {
|
|
var h []byte
|
|
var err error
|
|
var decodeFn decodeInfoHeaderFuncType
|
|
|
|
switch d.headerSize {
|
|
case 12:
|
|
decodeFn = decodeInfoHeader12
|
|
case 16, 20, 24, 32, 36, 40, 42, 44, 46, 48, 60, 64:
|
|
decodeFn = decodeInfoHeader40
|
|
case 52, 56, 108, 124:
|
|
decodeFn = decodeInfoHeader108
|
|
default:
|
|
return UnsupportedError(fmt.Sprintf("BMP version (header size %d)", d.headerSize))
|
|
}
|
|
|
|
// Read the rest of the infoheader
|
|
h = make([]byte, d.headerSize)
|
|
_, err = io.ReadFull(d.r, h[4:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = decodeFn(d, h, configOnly)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.width < 1 {
|
|
return FormatError(fmt.Sprintf("bad width %d", d.width))
|
|
}
|
|
if d.height < 1 {
|
|
return FormatError(fmt.Sprintf("bad height %d", d.height))
|
|
}
|
|
|
|
if d.bitCount >= 1 && d.bitCount <= 8 {
|
|
d.dstHasPalette = true
|
|
}
|
|
|
|
d.srcPalSizeInBytes = d.srcPalNumEntries * d.srcPalBytesPerEntry
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *decoder) recordBitFields(r, g, b, a uint32) {
|
|
d.bitFields[0].mask = r
|
|
d.bitFields[1].mask = g
|
|
d.bitFields[2].mask = b
|
|
d.bitFields[3].mask = a
|
|
|
|
// Based on .mask, set the other fields of the bitFields struct
|
|
for k := 0; k < 4; k++ {
|
|
if d.bitFields[k].mask == 0 {
|
|
continue
|
|
}
|
|
|
|
// Starting with the low bit, count the number of 0 bits before
|
|
// the first 1 bit.
|
|
tmpMask := d.bitFields[k].mask
|
|
for tmpMask&0x1 == 0 {
|
|
d.bitFields[k].shift++
|
|
tmpMask >>= 1
|
|
}
|
|
d.bitFields[k].scale = 255.0 / float64(tmpMask)
|
|
}
|
|
}
|
|
|
|
func (d *decoder) readBitFieldsSegment() error {
|
|
buf := make([]byte, d.bitFieldsSegmentSize)
|
|
_, err := io.ReadFull(d.r, buf[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.recordBitFields(getDWORD(buf[0:4]), getDWORD(buf[4:8]),
|
|
getDWORD(buf[8:12]), 0)
|
|
return nil
|
|
}
|
|
|
|
func (d *decoder) readPalette() error {
|
|
var err error
|
|
buf := make([]byte, d.srcPalSizeInBytes)
|
|
_, err = io.ReadFull(d.r, buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !d.dstHasPalette {
|
|
d.dstPalNumEntries = 0
|
|
return nil
|
|
}
|
|
|
|
d.dstPalNumEntries = d.srcPalNumEntries
|
|
if d.dstPalNumEntries > 256 {
|
|
d.dstPalNumEntries = 256
|
|
}
|
|
|
|
d.dstPalette = make(color.Palette, d.dstPalNumEntries)
|
|
for i := 0; i < d.dstPalNumEntries; i++ {
|
|
d.dstPalette[i] = color.RGBA{buf[i*d.srcPalBytesPerEntry+2],
|
|
buf[i*d.srcPalBytesPerEntry+1],
|
|
buf[i*d.srcPalBytesPerEntry+0], 255}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *decoder) decodeFileHeader(b []byte) error {
|
|
if b[0] != 0x42 || b[1] != 0x4d {
|
|
return FormatError("not a BMP file")
|
|
}
|
|
d.bfOffBits = getDWORD(b[10:14])
|
|
return nil
|
|
}
|
|
|
|
func (d *decoder) readHeaders(configOnly bool) error {
|
|
var fh [18]byte
|
|
var err error
|
|
|
|
// Read the file header, and the first 4 bytes of the info header
|
|
_, err = io.ReadFull(d.r, fh[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = d.decodeFileHeader(fh[0:14])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.headerSize = getDWORD(fh[14:18])
|
|
|
|
err = readInfoHeader(d, configOnly)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *decoder) readMain(r io.Reader, configOnly bool) (image.Image, error) {
|
|
var err error
|
|
|
|
// Read the FILEHEADER and INFOHEADER.
|
|
err = d.readHeaders(false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Make sure bitcount and "compression" are valid and compatible.
|
|
switch d.biCompression {
|
|
case bI_RGB:
|
|
// We allow bitCount=2 because Windows CE defines it to be valid
|
|
// (at least if headerSize=40).
|
|
if d.bitCount != 1 && d.bitCount != 2 && d.bitCount != 4 && d.bitCount != 8 &&
|
|
d.bitCount != 16 && d.bitCount != 24 && d.bitCount != 32 {
|
|
return nil, FormatError(fmt.Sprintf("bad bit count %d", d.bitCount))
|
|
}
|
|
case bI_RLE4:
|
|
if d.bitCount != 4 {
|
|
return nil, FormatError(fmt.Sprintf("bad RLE4 bit count %d", d.bitCount))
|
|
}
|
|
case bI_RLE8:
|
|
if d.bitCount != 8 {
|
|
return nil, FormatError(fmt.Sprintf("bad RLE8 bit count %d", d.bitCount))
|
|
}
|
|
case bI_BITFIELDS:
|
|
if d.bitCount == 1 {
|
|
return nil, UnsupportedError("Huffman 1D compression")
|
|
} else if d.bitCount != 16 && d.bitCount != 32 {
|
|
return nil, FormatError(fmt.Sprintf("bad BITFIELDS bit count %d", d.bitCount))
|
|
}
|
|
default:
|
|
return nil, UnsupportedError(fmt.Sprintf("compression or image type %d", d.biCompression))
|
|
}
|
|
|
|
// Assuming 'int' is 32 bits, an NRGBA image can't handle more than (2^31-1)/4
|
|
// pixels. This test is more conservative than it could be.
|
|
if d.width > 46340 || d.height > 46340 || d.width*d.height >= 0x20000000 {
|
|
return nil, UnsupportedError("dimensions too large")
|
|
}
|
|
|
|
// Read the BITFIELDS segment, if present.
|
|
if d.hasBitFieldsSegment {
|
|
err = d.readBitFieldsSegment()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Read the palette, if present.
|
|
if d.srcPalNumEntries > 0 {
|
|
err = d.readPalette()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if configOnly {
|
|
return nil, nil
|
|
}
|
|
|
|
// Create the target image.
|
|
if d.dstHasPalette {
|
|
d.img_Paletted = image.NewPaletted(image.Rect(0, 0, d.width, d.height), d.dstPalette)
|
|
} else {
|
|
d.img_NRGBA = image.NewNRGBA(image.Rect(0, 0, d.width, d.height))
|
|
}
|
|
|
|
// Skip over any unused space preceding the bitmap bits.
|
|
err = d.readGap()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Read the bitmap bits.
|
|
if d.biCompression == bI_RLE4 || d.biCompression == bI_RLE8 {
|
|
err = d.readBitsRLE()
|
|
} else {
|
|
err = d.readBitsUncompressed()
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if d.dstHasPalette {
|
|
return d.img_Paletted, nil
|
|
}
|
|
return d.img_NRGBA, nil
|
|
}
|
|
|
|
// Decode reads a BMP image from r and returns it as an image.Image.
|
|
func Decode(r io.Reader) (image.Image, error) {
|
|
var err error
|
|
|
|
d := new(decoder)
|
|
d.r = r
|
|
|
|
im, err := d.readMain(r, false)
|
|
return im, err
|
|
}
|
|
|
|
// DecodeConfig returns the color model and dimensions of the BMP image without
|
|
// decoding the entire image.
|
|
func DecodeConfig(r io.Reader) (image.Config, error) {
|
|
var err error
|
|
var cfg image.Config
|
|
|
|
d := new(decoder)
|
|
d.r = r
|
|
|
|
_, err = d.readMain(r, true)
|
|
if err != nil {
|
|
return cfg, err
|
|
}
|
|
|
|
cfg.Width = d.width
|
|
cfg.Height = d.height
|
|
if d.dstHasPalette {
|
|
cfg.ColorModel = d.dstPalette
|
|
} else {
|
|
cfg.ColorModel = color.NRGBAModel
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func init() {
|
|
image.RegisterFormat("bmp", "BM", Decode, DecodeConfig)
|
|
}
|