1
0
mirror of https://github.com/fumiama/imgsz.git synced 2026-06-05 00:32:53 +08:00

🎉 完成

This commit is contained in:
fumiama
2022-01-08 15:09:05 +08:00
parent d016c6e69d
commit fd0b2eae31
14 changed files with 1181 additions and 0 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@
# Dependency directories (remove the comment below to include it)
# vendor/
.DS_Store

View File

@@ -1,2 +1,12 @@
# imgsz
Image size analyzer for jpg/png/gif/webp
# Usage
```go
// DecodeSize decodes the dimensions of an image that has
// been encoded in a registered format. The string returned is the format name
// used during format registration. Format registration is typically done by
// an init function in the codec-specific package.
func DecodeSize(r io.Reader) (Size, string, error)
```

48
gif.go Normal file
View File

@@ -0,0 +1,48 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package gif implements a GIF image gifdecoder and encoder.
//
// The GIF specification is at https://www.w3.org/Graphics/GIF/spec-gif89a.txt.
package imgsz
import (
"fmt"
"io"
)
func readFull(r io.Reader, b []byte) error {
_, err := io.ReadFull(r, b)
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return err
}
// gifdecoder is the type used to decode a GIF file.
type gifdecoder struct {
// From header.
vers string
width int
height int
// Used when decoding.
tmp [1024]byte // must be at least 768 so we can read color table
}
func (d *gifdecoder) readHeaderAndScreenDescriptor(r io.Reader) error {
err := readFull(r, d.tmp[:13])
if err != nil {
return fmt.Errorf("gif: reading header: %v", err)
}
d.vers = string(d.tmp[:6])
if d.vers != "GIF87a" && d.vers != "GIF89a" {
return fmt.Errorf("gif: can't recognize format %q", d.vers)
}
d.width = int(d.tmp[6]) + int(d.tmp[7])<<8
d.height = int(d.tmp[8]) + int(d.tmp[9])<<8
// d.tmp[12] is the Pixel Aspect Ratio, which is ignored.
return nil
}

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/fumiama/imgsz
go 1.17

108
image.go Normal file
View File

@@ -0,0 +1,108 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package imgsz
import (
"bufio"
"errors"
"io"
"sync"
"sync/atomic"
)
// A FormatError reports that the input is not a valid JPEG.
type FormatError string
func (e FormatError) Error() string { return "invalid format: " + string(e) }
// An UnsupportedError reports that the input uses a valid but unimplemented JPEG feature.
type UnsupportedError string
func (e UnsupportedError) Error() string { return "unsupported feature: " + string(e) }
// ErrFormat indicates that decoding encountered an unknown format.
var ErrFormat = errors.New("image: unknown format")
type Size struct {
Width, Height int
}
// A format holds an image format's name, magic header and how to decode it.
type format struct {
name, magic string
decodeSize func(io.Reader) (Size, error)
}
// Formats is the list of registered formats.
var (
formatsMu sync.Mutex
atomicFormats atomic.Value
)
// RegisterFormat registers an image format for use by Decode.
// Name is the name of the format, like "jpeg" or "png".
// Magic is the magic prefix that identifies the format's encoding. The magic
// string can contain "?" wildcards that each match any one byte.
// Decode is the function that decodes the encoded image.
// DecodeSize is the function that decodes just its configuration.
func RegisterFormat(name, magic string, decodeSize func(io.Reader) (Size, error)) {
formatsMu.Lock()
formats, _ := atomicFormats.Load().([]format)
atomicFormats.Store(append(formats, format{name, magic, decodeSize}))
formatsMu.Unlock()
}
// A reader is an io.Reader that can also peek ahead.
type reader interface {
io.Reader
Peek(int) ([]byte, error)
}
// asReader converts an io.Reader to a reader.
func asReader(r io.Reader) reader {
if rr, ok := r.(reader); ok {
return rr
}
return bufio.NewReader(r)
}
// Match reports whether magic matches b. Magic may contain "?" wildcards.
func match(magic string, b []byte) bool {
if len(magic) != len(b) {
return false
}
for i, c := range b {
if magic[i] != c && magic[i] != '?' {
return false
}
}
return true
}
// Sniff determines the format of r's data.
func sniff(r reader) format {
formats, _ := atomicFormats.Load().([]format)
for _, f := range formats {
b, err := r.Peek(len(f.magic))
if err == nil && match(f.magic, b) {
return f
}
}
return format{}
}
// DecodeSize decodes the dimensions of an image that has
// been encoded in a registered format. The string returned is the format name
// used during format registration. Format registration is typically done by
// an init function in the codec-specific package.
func DecodeSize(r io.Reader) (Size, string, error) {
rr := asReader(r)
f := sniff(rr)
if f.decodeSize == nil {
return Size{}, "", ErrFormat
}
c, err := f.decodeSize(rr)
return c, f.name, err
}

60
image_test.go Normal file
View File

@@ -0,0 +1,60 @@
package imgsz
import (
"os"
"testing"
)
func TestSizes(t *testing.T) {
f, err := os.Open("testdata/test.webp")
if err != nil {
t.Fatal(err)
}
sz, n, err := DecodeSize(f)
if err != nil {
t.Fatal(err)
}
if sz.Width != 3507 || sz.Height != 2480 || n != "webp" {
t.Fatal(sz, n)
}
f.Close()
f, err = os.Open("testdata/test.jpg")
if err != nil {
t.Fatal(err)
}
sz, n, err = DecodeSize(f)
if err != nil {
t.Fatal(err)
}
if sz.Width != 858 || sz.Height != 1126 || n != "jpeg" {
t.Fatal(sz, n)
}
f.Close()
f, err = os.Open("testdata/test.png")
if err != nil {
t.Fatal(err)
}
sz, n, err = DecodeSize(f)
if err != nil {
t.Fatal(err)
}
if sz.Width != 670 || sz.Height != 717 || n != "png" {
t.Fatal(sz, n)
}
f.Close()
f, err = os.Open("testdata/test.gif")
if err != nil {
t.Fatal(err)
}
sz, n, err = DecodeSize(f)
if err != nil {
t.Fatal(err)
}
if sz.Width != 184 || sz.Height != 166 || n != "gif" {
t.Fatal(sz, n)
}
f.Close()
}

19
init.go Normal file
View File

@@ -0,0 +1,19 @@
package imgsz
import "io"
func init() {
RegisterFormat("jpeg", "\xff\xd8", func(r io.Reader) (Size, error) {
var d jpgdecoder
return d.decode(r)
})
RegisterFormat("png", pngHeader, decodepng)
RegisterFormat("gif", "GIF8?a", func(r io.Reader) (Size, error) {
var d gifdecoder
if err := d.readHeaderAndScreenDescriptor(r); err != nil {
return Size{}, err
}
return Size{d.width, d.height}, nil
})
RegisterFormat("webp", "RIFF????WEBPVP8", decodewebp)
}

327
jpg.go Normal file
View File

@@ -0,0 +1,327 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package jpeg implements a JPEG image decoder and encoder.
//
// JPEG is defined in ITU-T T.81: https://www.w3.org/Graphics/JPEG/itu-t81.pdf.
package imgsz
import (
"io"
)
const blockSize = 64 // A DCT block is 8x8.
const (
sof0Marker = 0xc0 // Start Of Frame (Baseline Sequential).
sof1Marker = 0xc1 // Start Of Frame (Extended Sequential).
sof2Marker = 0xc2 // Start Of Frame (Progressive).
dhtMarker = 0xc4 // Define Huffman Table.
rst0Marker = 0xd0 // ReSTart (0).
rst7Marker = 0xd7 // ReSTart (7).
soiMarker = 0xd8 // Start Of Image.
eoiMarker = 0xd9 // End Of Image.
sosMarker = 0xda // Start Of Scan.
dqtMarker = 0xdb // Define Quantization Table.
driMarker = 0xdd // Define Restart Interval.
comMarker = 0xfe // COMment.
// "APPlication specific" markers aren't part of the JPEG spec per se,
// but in practice, their use is described at
// https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html
app0Marker = 0xe0
app14Marker = 0xee
app15Marker = 0xef
)
// bits holds the unprocessed bits that have been taken from the byte-stream.
// The n least significant bits of a form the unread bits, to be read in MSB to
// LSB order.
type bits struct {
a uint32 // accumulator.
m uint32 // mask. m==1<<(n-1) when n>0, with m==0 when n==0.
n int32 // the number of unread bits in a.
}
type jpgdecoder struct {
r io.Reader
bits bits
// bytes is a byte buffer, similar to a bufio.Reader, except that it
// has to be able to unread more than 1 byte, due to byte stuffing.
// Byte stuffing is specified in section F.1.2.3.
bytes struct {
// buf[i:j] are the buffered bytes read from the underlying
// io.Reader that haven't yet been passed further on.
buf [4096]byte
i, j int
// nUnreadable is the number of bytes to back up i after
// overshooting. It can be 0, 1 or 2.
nUnreadable int
}
width, height int
nComp int
// As per section 4.5, there are four modes of operation (selected by the
// SOF? markers): sequential DCT, progressive DCT, lossless and
// hierarchical, although this implementation does not support the latter
// two non-DCT modes. Sequential DCT is further split into baseline and
// extended, as per section 4.11.
baseline bool
progressive bool
jfif bool
tmp [2 * blockSize]byte
}
// fill fills up the d.bytes.buf buffer from the underlying io.Reader. It
// should only be called when there are no unread bytes in d.bytes.
func (d *jpgdecoder) fill() error {
if d.bytes.i != d.bytes.j {
panic("jpeg: fill called when unread bytes exist")
}
// Move the last 2 bytes to the start of the buffer, in case we need
// to call unreadByteStuffedByte.
if d.bytes.j > 2 {
d.bytes.buf[0] = d.bytes.buf[d.bytes.j-2]
d.bytes.buf[1] = d.bytes.buf[d.bytes.j-1]
d.bytes.i, d.bytes.j = 2, 2
}
// Fill in the rest of the buffer.
n, err := d.r.Read(d.bytes.buf[d.bytes.j:])
d.bytes.j += n
if n > 0 {
err = nil
}
return err
}
// unreadByteStuffedByte undoes the most recent readByteStuffedByte call,
// giving a byte of data back from d.bits to d.bytes. The Huffman look-up table
// requires at least 8 bits for look-up, which means that Huffman decoding can
// sometimes overshoot and read one or two too many bytes. Two-byte overshoot
// can happen when expecting to read a 0xff 0x00 byte-stuffed byte.
func (d *jpgdecoder) unreadByteStuffedByte() {
d.bytes.i -= d.bytes.nUnreadable
d.bytes.nUnreadable = 0
if d.bits.n >= 8 {
d.bits.a >>= 8
d.bits.n -= 8
d.bits.m >>= 8
}
}
// readByte returns the next byte, whether buffered or not buffered. It does
// not care about byte stuffing.
func (d *jpgdecoder) readByte() (x byte, err error) {
for d.bytes.i == d.bytes.j {
if err = d.fill(); err != nil {
return 0, err
}
}
x = d.bytes.buf[d.bytes.i]
d.bytes.i++
d.bytes.nUnreadable = 0
return x, nil
}
// readFull reads exactly len(p) bytes into p. It does not care about byte
// stuffing.
func (d *jpgdecoder) readFull(p []byte) error {
// Unread the overshot bytes, if any.
if d.bytes.nUnreadable != 0 {
if d.bits.n >= 8 {
d.unreadByteStuffedByte()
}
d.bytes.nUnreadable = 0
}
for {
n := copy(p, d.bytes.buf[d.bytes.i:d.bytes.j])
p = p[n:]
d.bytes.i += n
if len(p) == 0 {
break
}
if err := d.fill(); err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return err
}
}
return nil
}
// ignore ignores the next n bytes.
func (d *jpgdecoder) ignore(n int) error {
// Unread the overshot bytes, if any.
if d.bytes.nUnreadable != 0 {
if d.bits.n >= 8 {
d.unreadByteStuffedByte()
}
d.bytes.nUnreadable = 0
}
for {
m := d.bytes.j - d.bytes.i
if m > n {
m = n
}
d.bytes.i += m
n -= m
if n == 0 {
break
}
if err := d.fill(); err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return err
}
}
return nil
}
// Specified in section B.2.2.
func (d *jpgdecoder) processSOF(n int) error {
if d.nComp != 0 {
return FormatError("multiple SOF markers")
}
switch n {
case 6 + 3*1: // Grayscale image.
d.nComp = 1
case 6 + 3*3: // YCbCr or RGB image.
d.nComp = 3
case 6 + 3*4: // YCbCrK or CMYK image.
d.nComp = 4
default:
return UnsupportedError("number of components")
}
if err := d.readFull(d.tmp[:n]); err != nil {
return err
}
// We only support 8-bit precision.
if d.tmp[0] != 8 {
return UnsupportedError("precision")
}
d.height = int(d.tmp[1])<<8 + int(d.tmp[2])
d.width = int(d.tmp[3])<<8 + int(d.tmp[4])
if int(d.tmp[5]) != d.nComp {
return FormatError("SOF has wrong length")
}
return nil
}
// decode reads a JPEG image from r and returns its Size
func (d *jpgdecoder) decode(r io.Reader) (Size, error) {
d.r = r
// Check for the Start Of Image marker.
if err := d.readFull(d.tmp[:2]); err != nil {
return Size{}, err
}
if d.tmp[0] != 0xff || d.tmp[1] != soiMarker {
return Size{}, FormatError("missing SOI marker")
}
// Process the remaining segments until the End Of Image marker.
for {
err := d.readFull(d.tmp[:2])
if err != nil {
return Size{}, err
}
for d.tmp[0] != 0xff {
// Strictly speaking, this is a format error. However, libjpeg is
// liberal in what it accepts. As of version 9, next_marker in
// jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and
// continues to decode the stream. Even before next_marker sees
// extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many
// bytes as it can, possibly past the end of a scan's data. It
// effectively puts back any markers that it overscanned (e.g. an
// "\xff\xd9" EOI marker), but it does not put back non-marker data,
// and thus it can silently ignore a small number of extraneous
// non-marker bytes before next_marker has a chance to see them (and
// print a warning).
//
// We are therefore also liberal in what we accept. Extraneous data
// is silently ignored.
//
// This is similar to, but not exactly the same as, the restart
// mechanism within a scan (the RST[0-7] markers).
//
// Note that extraneous 0xff bytes in e.g. SOS data are escaped as
// "\xff\x00", and so are detected a little further down below.
d.tmp[0] = d.tmp[1]
d.tmp[1], err = d.readByte()
if err != nil {
return Size{}, err
}
}
marker := d.tmp[1]
if marker == 0 {
// Treat "\xff\x00" as extraneous data.
continue
}
for marker == 0xff {
// Section B.1.1.2 says, "Any marker may optionally be preceded by any
// number of fill bytes, which are bytes assigned code X'FF'".
marker, err = d.readByte()
if err != nil {
return Size{}, err
}
}
if marker == eoiMarker { // End Of Image.
break
}
if rst0Marker <= marker && marker <= rst7Marker {
// Figures B.2 and B.16 of the specification suggest that restart markers should
// only occur between Entropy Coded Segments and not after the final ECS.
// However, some encoders may generate incorrect JPEGs with a final restart
// marker. That restart marker will be seen here instead of inside the processSOS
// method, and is ignored as a harmless error. Restart markers have no extra data,
// so we check for this before we read the 16-bit length of the segment.
continue
}
// Read the 16-bit length of the segment. The value includes the 2 bytes for the
// length itself, so we subtract 2 to get the number of remaining bytes.
if err = d.readFull(d.tmp[:2]); err != nil {
return Size{}, err
}
n := int(d.tmp[0])<<8 + int(d.tmp[1]) - 2
if n < 0 {
return Size{}, FormatError("short segment length")
}
switch marker {
case sof0Marker, sof1Marker, sof2Marker:
d.baseline = marker == sof0Marker
d.progressive = marker == sof2Marker
err = d.processSOF(n)
if d.jfif {
return Size{}, err
}
return Size{d.width, d.height}, nil
case sosMarker:
return Size{}, nil
case dhtMarker, dqtMarker, driMarker, app0Marker, app14Marker:
err = d.ignore(n)
default:
if app0Marker <= marker && marker <= app15Marker || marker == comMarker {
err = d.ignore(n)
} else if marker < 0xc0 { // See Table B.1 "Marker code assignments".
err = FormatError("unknown marker")
} else {
err = UnsupportedError("unknown marker")
}
}
if err != nil {
return Size{}, err
}
}
return Size{d.width, d.height}, nil
}

240
png.go Normal file
View File

@@ -0,0 +1,240 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package png implements a PNG image decoder and encoder.
//
// The PNG specification is at https://www.w3.org/TR/PNG/.
package imgsz
import (
"encoding/binary"
"fmt"
"hash"
"hash/crc32"
"io"
)
// A cb is a combination of color type and bit depth.
const (
cbInvalid = iota
cbG1
cbG2
cbG4
cbG8
cbGA8
cbTC8
cbP1
cbP2
cbP4
cbP8
cbTCA8
cbG16
cbGA16
cbTC16
cbTCA16
)
func cbPaletted(cb int) bool {
return cbP1 <= cb && cb <= cbP8
}
// Interlace type.
const (
itNone = 0
itAdam7 = 1
)
// Decoding stage.
// The PNG specification says that the IHDR, PLTE (if present), tRNS (if
// present), IDAT and IEND chunks must appear in that order. There may be
// multiple IDAT chunks, and IDAT chunks must be sequential (i.e. they may not
// have any other chunks between them).
// https://www.w3.org/TR/PNG/#5ChunkOrdering
const (
dsStart = iota
dsSeenIHDR
dsSeenPLTE
dsSeentRNS
dsSeenIDAT
dsSeenIEND
)
const pngHeader = "\x89PNG\r\n\x1a\n"
type decoder struct {
r io.Reader
crc hash.Hash32
width, height int
cb int
stage int
idatLength uint32
tmp [3 * 256]byte
interlace int
}
var chunkOrderError = FormatError("chunk out of order")
func min(a, b int) int {
if a < b {
return a
}
return b
}
func (d *decoder) parseIHDR(length uint32) error {
if length != 13 {
return FormatError("bad IHDR length")
}
if _, err := io.ReadFull(d.r, d.tmp[:13]); err != nil {
return err
}
d.crc.Write(d.tmp[:13])
if d.tmp[10] != 0 {
return UnsupportedError("compression method")
}
if d.tmp[11] != 0 {
return UnsupportedError("filter method")
}
if d.tmp[12] != itNone && d.tmp[12] != itAdam7 {
return FormatError("invalid interlace method")
}
d.interlace = int(d.tmp[12])
w := int32(binary.BigEndian.Uint32(d.tmp[0:4]))
h := int32(binary.BigEndian.Uint32(d.tmp[4:8]))
if w <= 0 || h <= 0 {
return FormatError("non-positive dimension")
}
d.width, d.height = int(w), int(h)
return d.verifyChecksum()
}
// Read presents one or more IDAT chunks as one continuous stream (minus the
// intermediate chunk headers and footers). If the PNG data looked like:
// ... len0 IDAT xxx crc0 len1 IDAT yy crc1 len2 IEND crc2
// then this reader presents xxxyy. For well-formed PNG data, the decoder state
// immediately before the first Read call is that d.r is positioned between the
// first IDAT and xxx, and the decoder state immediately after the last Read
// call is that d.r is positioned between yy and crc1.
func (d *decoder) Read(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
for d.idatLength == 0 {
// We have exhausted an IDAT chunk. Verify the checksum of that chunk.
if err := d.verifyChecksum(); err != nil {
return 0, err
}
// Read the length and chunk type of the next chunk, and check that
// it is an IDAT chunk.
if _, err := io.ReadFull(d.r, d.tmp[:8]); err != nil {
return 0, err
}
d.idatLength = binary.BigEndian.Uint32(d.tmp[:4])
if string(d.tmp[4:8]) != "IDAT" {
return 0, FormatError("not enough pixel data")
}
d.crc.Reset()
d.crc.Write(d.tmp[4:8])
}
if int(d.idatLength) < 0 {
return 0, UnsupportedError("IDAT chunk length overflow")
}
n, err := d.r.Read(p[:min(len(p), int(d.idatLength))])
d.crc.Write(p[:n])
d.idatLength -= uint32(n)
return n, err
}
func (d *decoder) parseSize() (ok bool, err error) {
// Read the length and chunk type.
if _, err = io.ReadFull(d.r, d.tmp[:8]); err != nil {
return
}
length := binary.BigEndian.Uint32(d.tmp[:4])
d.crc.Reset()
d.crc.Write(d.tmp[4:8])
// Read the chunk data.
switch string(d.tmp[4:8]) {
case "IHDR":
if d.stage != dsStart {
return false, chunkOrderError
}
d.stage = dsSeenIHDR
return true, d.parseIHDR(length)
}
if length > 0x7fffffff {
return false, FormatError(fmt.Sprintf("Bad chunk length: %d", length))
}
// Ignore this chunk (of a known length).
var ignored [4096]byte
for length > 0 {
n, err := io.ReadFull(d.r, ignored[:min(len(ignored), int(length))])
if err != nil {
return false, err
}
d.crc.Write(ignored[:n])
length -= uint32(n)
}
return false, d.verifyChecksum()
}
func (d *decoder) verifyChecksum() error {
if _, err := io.ReadFull(d.r, d.tmp[:4]); err != nil {
return err
}
if binary.BigEndian.Uint32(d.tmp[:4]) != d.crc.Sum32() {
return FormatError("invalid checksum")
}
return nil
}
func (d *decoder) checkHeader() error {
_, err := io.ReadFull(d.r, d.tmp[:len(pngHeader)])
if err != nil {
return err
}
if string(d.tmp[:len(pngHeader)]) != pngHeader {
return FormatError("not a PNG file")
}
return nil
}
// decodepng returns the color model and dimensions of a PNG image without
// decoding the entire image.
func decodepng(r io.Reader) (Size, error) {
d := &decoder{
r: r,
crc: crc32.NewIEEE(),
}
if err := d.checkHeader(); err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return Size{}, err
}
for {
ok, err := d.parseSize()
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return Size{}, err
}
if ok {
return Size{d.width, d.height}, nil
}
paletted := cbPaletted(d.cb)
if d.stage == dsSeenIHDR && !paletted {
break
}
if d.stage == dsSeenPLTE && paletted {
break
}
}
return Size{d.width, d.height}, nil
}

BIN
testdata/test.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

BIN
testdata/test.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
testdata/test.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 KiB

BIN
testdata/test.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

365
webp.go Normal file
View File

@@ -0,0 +1,365 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package imgsz
import (
"bufio"
"errors"
"io"
"io/ioutil"
"math"
)
var errInvalidFormat = errors.New("webp: invalid format")
// FourCC is a four character code.
type FourCC [4]byte
var (
fccALPH = FourCC{'A', 'L', 'P', 'H'}
fccVP8 = FourCC{'V', 'P', '8', ' '}
fccVP8L = FourCC{'V', 'P', '8', 'L'}
fccVP8X = FourCC{'V', 'P', '8', 'X'}
fccWEBP = FourCC{'W', 'E', 'B', 'P'}
)
const chunkHeaderSize = 8
var (
errMissingPaddingByte = errors.New("riff: missing padding byte")
errMissingRIFFChunkHeader = errors.New("riff: missing RIFF chunk header")
errListSubchunkTooLong = errors.New("riff: list subchunk too long")
errShortChunkData = errors.New("riff: short chunk data")
errShortChunkHeader = errors.New("riff: short chunk header")
errStaleReader = errors.New("riff: stale reader")
)
// u32 decodes the first four bytes of b as a little-endian integer.
func u32(b []byte) uint32 {
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}
// Reader reads chunks from an underlying io.Reader.
type Reader struct {
r io.Reader
err error
totalLen uint32
chunkLen uint32
chunkReader *chunkReader
buf [chunkHeaderSize]byte
padded bool
}
// NewListReader returns a LIST chunk's list type, such as "movi" or "wavl",
// and its chunks as a *Reader.
func NewListReader(chunkLen uint32, chunkData io.Reader) (listType FourCC, data *Reader, err error) {
if chunkLen < 4 {
return FourCC{}, nil, errShortChunkData
}
z := &Reader{r: chunkData}
if _, err := io.ReadFull(chunkData, z.buf[:4]); err != nil {
if err == io.EOF || err == io.ErrUnexpectedEOF {
err = errShortChunkData
}
return FourCC{}, nil, err
}
z.totalLen = chunkLen - 4
return FourCC{z.buf[0], z.buf[1], z.buf[2], z.buf[3]}, z, nil
}
// NewReader returns the RIFF stream's form type, such as "AVI " or "WAVE", and
// its chunks as a *Reader.
func NewReader(r io.Reader) (formType FourCC, data *Reader, err error) {
var buf [chunkHeaderSize]byte
if _, err := io.ReadFull(r, buf[:]); err != nil {
if err == io.EOF || err == io.ErrUnexpectedEOF {
err = errMissingRIFFChunkHeader
}
return FourCC{}, nil, err
}
if buf[0] != 'R' || buf[1] != 'I' || buf[2] != 'F' || buf[3] != 'F' {
return FourCC{}, nil, errMissingRIFFChunkHeader
}
return NewListReader(u32(buf[4:]), r)
}
// Next returns the next chunk's ID, length and data. It returns io.EOF if there
// are no more chunks. The io.Reader returned becomes stale after the next Next
// call, and should no longer be used.
//
// It is valid to call Next even if all of the previous chunk's data has not
// been read.
func (z *Reader) Next() (chunkID FourCC, chunkLen uint32, chunkData io.Reader, err error) {
if z.err != nil {
return FourCC{}, 0, nil, z.err
}
// Drain the rest of the previous chunk.
if z.chunkLen != 0 {
want := z.chunkLen
var got int64
got, z.err = io.Copy(ioutil.Discard, z.chunkReader)
if z.err == nil && uint32(got) != want {
z.err = errShortChunkData
}
if z.err != nil {
return FourCC{}, 0, nil, z.err
}
}
z.chunkReader = nil
if z.padded {
if z.totalLen == 0 {
z.err = errListSubchunkTooLong
return FourCC{}, 0, nil, z.err
}
z.totalLen--
_, z.err = io.ReadFull(z.r, z.buf[:1])
if z.err != nil {
if z.err == io.EOF {
z.err = errMissingPaddingByte
}
return FourCC{}, 0, nil, z.err
}
}
// We are done if we have no more data.
if z.totalLen == 0 {
z.err = io.EOF
return FourCC{}, 0, nil, z.err
}
// Read the next chunk header.
if z.totalLen < chunkHeaderSize {
z.err = errShortChunkHeader
return FourCC{}, 0, nil, z.err
}
z.totalLen -= chunkHeaderSize
if _, z.err = io.ReadFull(z.r, z.buf[:chunkHeaderSize]); z.err != nil {
if z.err == io.EOF || z.err == io.ErrUnexpectedEOF {
z.err = errShortChunkHeader
}
return FourCC{}, 0, nil, z.err
}
chunkID = FourCC{z.buf[0], z.buf[1], z.buf[2], z.buf[3]}
z.chunkLen = u32(z.buf[4:])
if z.chunkLen > z.totalLen {
z.err = errListSubchunkTooLong
return FourCC{}, 0, nil, z.err
}
z.padded = z.chunkLen&1 == 1
z.chunkReader = &chunkReader{z}
return chunkID, z.chunkLen, z.chunkReader, nil
}
type chunkReader struct {
z *Reader
}
func (c *chunkReader) Read(p []byte) (int, error) {
if c != c.z.chunkReader {
return 0, errStaleReader
}
z := c.z
if z.err != nil {
if z.err == io.EOF {
return 0, errStaleReader
}
return 0, z.err
}
n := int(z.chunkLen)
if n == 0 {
return 0, io.EOF
}
if n < 0 {
// Converting uint32 to int overflowed.
n = math.MaxInt32
}
if n > len(p) {
n = len(p)
}
n, err := z.r.Read(p[:n])
z.totalLen -= uint32(n)
z.chunkLen -= uint32(n)
if err != io.EOF {
z.err = err
}
return n, err
}
func decodewebp(r io.Reader) (Size, error) {
formType, riffReader, err := NewReader(r)
if err != nil {
return Size{}, err
}
if formType != fccWEBP {
return Size{}, errInvalidFormat
}
var (
alpha []byte
wantAlpha bool
widthMinusOne uint32
heightMinusOne uint32
buf [10]byte
)
for {
chunkID, chunkLen, chunkData, err := riffReader.Next()
if err == io.EOF {
err = errInvalidFormat
}
if err != nil {
return Size{}, err
}
switch chunkID {
case fccALPH:
if !wantAlpha {
return Size{}, errInvalidFormat
}
wantAlpha = false
// Read the Pre-processing | Filter | Compression byte.
if _, err := io.ReadFull(chunkData, buf[:1]); err != nil {
if err == io.EOF {
err = errInvalidFormat
}
return Size{}, err
}
case fccVP8:
if wantAlpha || int32(chunkLen) < 0 {
return Size{}, errInvalidFormat
}
w, h, err := decodeVP8FrameHeader(chunkData)
if err != nil {
return Size{}, err
}
return Size{w, h}, nil
case fccVP8L:
if wantAlpha || alpha != nil {
return Size{}, errInvalidFormat
}
w, h, err := decodeVP8LHeader(chunkData)
return Size{int(w), int(h)}, err
case fccVP8X:
if chunkLen != 10 {
return Size{}, errInvalidFormat
}
if _, err := io.ReadFull(chunkData, buf[:10]); err != nil {
return Size{}, err
}
const (
animationBit = 1 << 1
xmpMetadataBit = 1 << 2
exifMetadataBit = 1 << 3
alphaBit = 1 << 4
iccProfileBit = 1 << 5
)
wantAlpha = (buf[0] & alphaBit) != 0
widthMinusOne = uint32(buf[4]) | uint32(buf[5])<<8 | uint32(buf[6])<<16
heightMinusOne = uint32(buf[7]) | uint32(buf[8])<<8 | uint32(buf[9])<<16
if wantAlpha {
return Size{
Width: int(widthMinusOne) + 1,
Height: int(heightMinusOne) + 1,
}, nil
}
return Size{
Width: int(widthMinusOne) + 1,
Height: int(heightMinusOne) + 1,
}, nil
}
}
}
func decodeVP8FrameHeader(r io.Reader) (w, h int, err error) {
var scratch [8]byte
// All frame headers are at least 3 bytes long.
b := scratch[:3]
if _, err = io.ReadFull(r, b); err != nil {
return
}
if (b[0] & 1) != 0 {
return 0, 0, nil
}
// Frame headers for key frames are an additional 7 bytes long.
b = scratch[:7]
if _, err = io.ReadFull(r, b); err != nil {
return
}
// Check the magic sync code.
if b[0] != 0x9d || b[1] != 0x01 || b[2] != 0x2a {
err = errors.New("vp8: invalid format")
return
}
return int(b[4]&0x3f)<<8 | int(b[3]), int(b[6]&0x3f)<<8 | int(b[5]), nil
}
// vp8ldecoder holds the bit-stream for a VP8L image.
type vp8ldecoder struct {
r io.ByteReader
bits uint32
nBits uint32
}
// read reads the next n bits from the decoder's bit-stream.
func (d *vp8ldecoder) read(n uint32) (uint32, error) {
for d.nBits < n {
c, err := d.r.ReadByte()
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return 0, err
}
d.bits |= uint32(c) << d.nBits
d.nBits += 8
}
u := d.bits & (1<<n - 1)
d.bits >>= n
d.nBits -= n
return u, nil
}
func decodeVP8LHeader(r io.Reader) (w int32, h int32, err error) {
rr, ok := r.(io.ByteReader)
if !ok {
rr = bufio.NewReader(r)
}
d := &vp8ldecoder{r: rr}
magic, err := d.read(8)
if err != nil {
return 0, 0, err
}
if magic != 0x2f {
return 0, 0, errors.New("vp8l: invalid header")
}
width, err := d.read(14)
if err != nil {
return 0, 0, err
}
width++
height, err := d.read(14)
if err != nil {
return 0, 0, err
}
height++
_, err = d.read(1) // Read and ignore the hasAlpha hint.
if err != nil {
return 0, 0, err
}
version, err := d.read(3)
if err != nil {
return 0, 0, err
}
if version != 0 {
return 0, 0, errors.New("vp8l: invalid version")
}
return int32(width), int32(height), nil
}