mirror of
https://github.com/fumiama/imgsz.git
synced 2026-06-23 12:40:28 +08:00
🎉 完成
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@
|
|||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
|
.DS_Store
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -1,2 +1,12 @@
|
|||||||
# imgsz
|
# imgsz
|
||||||
Image size analyzer for jpg/png/gif/webp
|
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
48
gif.go
Normal 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
|
||||||
|
}
|
||||||
108
image.go
Normal file
108
image.go
Normal 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
60
image_test.go
Normal 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
19
init.go
Normal 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
327
jpg.go
Normal 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
240
png.go
Normal 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
BIN
testdata/test.gif
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 261 KiB |
BIN
testdata/test.jpg
vendored
Normal file
BIN
testdata/test.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
BIN
testdata/test.png
vendored
Normal file
BIN
testdata/test.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 635 KiB |
BIN
testdata/test.webp
vendored
Normal file
BIN
testdata/test.webp
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 263 KiB |
365
webp.go
Normal file
365
webp.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user