go.image/vp8l: new package.
The blue-purple-pink image comes from http://blog.golang.org/gophercon The tux and yellow_rose images come from https://developers.google.com/speed/webp/gallery2 and according to that page, those images are in the public domain. The gopher-doc images are http://golang.org/doc/gopher/doc.png after quantizing its palette to 2/4/16/256 colors. LGTM=r R=r CC=golang-codereviews https://golang.org/cl/109010043
BIN
testdata/blue-purple-pink.lossless.webp
vendored
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
testdata/gopher-doc.1bpp.lossless.webp
vendored
Normal file
After Width: | Height: | Size: 442 B |
BIN
testdata/gopher-doc.1bpp.png
vendored
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
testdata/gopher-doc.2bpp.lossless.webp
vendored
Normal file
After Width: | Height: | Size: 772 B |
BIN
testdata/gopher-doc.2bpp.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
testdata/gopher-doc.4bpp.lossless.webp
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
testdata/gopher-doc.4bpp.png
vendored
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
testdata/gopher-doc.8bpp.lossless.webp
vendored
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
testdata/gopher-doc.8bpp.png
vendored
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
testdata/tux.lossless.webp
vendored
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
testdata/tux.png
vendored
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
testdata/yellow_rose.lossless.webp
vendored
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
testdata/yellow_rose.lossy.webp
vendored
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
testdata/yellow_rose.png
vendored
Normal file
After Width: | Height: | Size: 122 KiB |
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// Package vp8 implements a vp8 image and video decoder.
|
// Package vp8 implements a decoder for the VP8 lossy image format.
|
||||||
//
|
//
|
||||||
// The VP8 specification is RFC 6386.
|
// The VP8 specification is RFC 6386.
|
||||||
package vp8
|
package vp8
|
||||||
|
|
599
vp8l/decode.go
Normal file
|
@ -0,0 +1,599 @@
|
||||||
|
// Copyright 2014 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 vp8l implements a decoder for the VP8L lossless image format.
|
||||||
|
//
|
||||||
|
// The VP8L specification is at:
|
||||||
|
// https://developers.google.com/speed/webp/docs/riff_container
|
||||||
|
package vp8l
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidCodeLengths = errors.New("vp8l: invalid code lengths")
|
||||||
|
errInvalidHuffmanTree = errors.New("vp8l: invalid Huffman tree")
|
||||||
|
)
|
||||||
|
|
||||||
|
// colorCacheMultiplier is the multiplier used for the color cache hash
|
||||||
|
// function, specified in section 4.2.3.
|
||||||
|
const colorCacheMultiplier = 0x1e35a7bd
|
||||||
|
|
||||||
|
// distanceMapTable is the look-up table for distanceMap.
|
||||||
|
var distanceMapTable = [120]uint8{
|
||||||
|
0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a,
|
||||||
|
0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a,
|
||||||
|
0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b,
|
||||||
|
0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03,
|
||||||
|
0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c,
|
||||||
|
0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e,
|
||||||
|
0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b,
|
||||||
|
0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f,
|
||||||
|
0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b,
|
||||||
|
0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41,
|
||||||
|
0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f,
|
||||||
|
0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70,
|
||||||
|
}
|
||||||
|
|
||||||
|
// distanceMap maps a LZ77 backwards reference distance to a two-dimensional
|
||||||
|
// pixel offset, specified in section 4.2.2.
|
||||||
|
func distanceMap(w int32, code uint32) int32 {
|
||||||
|
if int32(code) > int32(len(distanceMapTable)) {
|
||||||
|
return int32(code) - int32(len(distanceMapTable))
|
||||||
|
}
|
||||||
|
distCode := int32(distanceMapTable[code-1])
|
||||||
|
yOffset := distCode >> 4
|
||||||
|
xOffset := 8 - distCode&0xf
|
||||||
|
if d := yOffset*w + xOffset; d >= 1 {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// decoder holds the bit-stream for a VP8L image.
|
||||||
|
type decoder struct {
|
||||||
|
r io.ByteReader
|
||||||
|
bits uint32
|
||||||
|
nBits uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// read reads the next n bits from the decoder's bit-stream.
|
||||||
|
func (d *decoder) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeTransform decodes the next transform and the width of the image after
|
||||||
|
// transformation (or equivalently, before inverse transformation), specified
|
||||||
|
// in section 3.
|
||||||
|
func (d *decoder) decodeTransform(w int32, h int32) (t transform, newWidth int32, err error) {
|
||||||
|
t.oldWidth = w
|
||||||
|
t.transformType, err = d.read(2)
|
||||||
|
if err != nil {
|
||||||
|
return transform{}, 0, err
|
||||||
|
}
|
||||||
|
switch t.transformType {
|
||||||
|
case transformTypePredictor, transformTypeCrossColor:
|
||||||
|
t.bits, err = d.read(3)
|
||||||
|
if err != nil {
|
||||||
|
return transform{}, 0, err
|
||||||
|
}
|
||||||
|
t.bits += 2
|
||||||
|
t.pix, err = d.decodePix(nTiles(w, t.bits), nTiles(h, t.bits), 0, false)
|
||||||
|
if err != nil {
|
||||||
|
return transform{}, 0, err
|
||||||
|
}
|
||||||
|
case transformTypeSubtractGreen:
|
||||||
|
// No-op.
|
||||||
|
case transformTypeColorIndexing:
|
||||||
|
nColors, err := d.read(8)
|
||||||
|
if err != nil {
|
||||||
|
return transform{}, 0, err
|
||||||
|
}
|
||||||
|
nColors++
|
||||||
|
t.bits = 0
|
||||||
|
switch {
|
||||||
|
case nColors <= 2:
|
||||||
|
t.bits = 3
|
||||||
|
case nColors <= 4:
|
||||||
|
t.bits = 2
|
||||||
|
case nColors <= 16:
|
||||||
|
t.bits = 1
|
||||||
|
}
|
||||||
|
w = nTiles(w, t.bits)
|
||||||
|
pix, err := d.decodePix(int32(nColors), 1, 4*256, false)
|
||||||
|
if err != nil {
|
||||||
|
return transform{}, 0, err
|
||||||
|
}
|
||||||
|
for p := 4; p < len(pix); p += 4 {
|
||||||
|
pix[p+0] += pix[p-4]
|
||||||
|
pix[p+1] += pix[p-3]
|
||||||
|
pix[p+2] += pix[p-2]
|
||||||
|
pix[p+3] += pix[p-1]
|
||||||
|
}
|
||||||
|
// The C code fills in palette entries past the nColors upper limit as
|
||||||
|
// transparent black. In Go, we re-slice up to 256 4-byte pixels.
|
||||||
|
t.pix = pix[:4*256]
|
||||||
|
}
|
||||||
|
return t, w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// repeatsCodeLength is the minimum code length for repeated codes.
|
||||||
|
const repeatsCodeLength = 16
|
||||||
|
|
||||||
|
// These magic numbers are specified at the end of section 5.2.2.
|
||||||
|
// The 3-length arrays apply to code lengths >= repeatsCodeLength.
|
||||||
|
var (
|
||||||
|
codeLengthCodeOrder = [19]uint8{
|
||||||
|
17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||||
|
}
|
||||||
|
repeatBits = [3]uint8{2, 3, 7}
|
||||||
|
repeatOffsets = [3]uint8{3, 3, 11}
|
||||||
|
)
|
||||||
|
|
||||||
|
// decodeCodeLengths decodes a Huffman tree's code lengths which are themselves
|
||||||
|
// encoded via a Huffman tree, specified in section 5.2.2.
|
||||||
|
func (d *decoder) decodeCodeLengths(dst []uint32, codeLengthCodeLengths []uint32) error {
|
||||||
|
h := hTree{}
|
||||||
|
if err := h.build(codeLengthCodeLengths); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
maxSymbol := len(dst)
|
||||||
|
useLength, err := d.read(1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if useLength != 0 {
|
||||||
|
n, err := d.read(3)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n = 2 + 2*n
|
||||||
|
ms, err := d.read(n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
maxSymbol = int(ms) + 2
|
||||||
|
if maxSymbol > len(dst) {
|
||||||
|
return errInvalidCodeLengths
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prevCodeLength := uint32(0)
|
||||||
|
for symbol := 0; symbol < len(dst); {
|
||||||
|
if maxSymbol == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
maxSymbol--
|
||||||
|
codeLength, err := h.next(d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if codeLength < repeatsCodeLength {
|
||||||
|
dst[symbol] = codeLength
|
||||||
|
symbol++
|
||||||
|
if codeLength != 0 {
|
||||||
|
prevCodeLength = codeLength
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat, err := d.read(uint32(repeatBits[codeLength-repeatsCodeLength]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
repeat += uint32(repeatOffsets[codeLength-repeatsCodeLength])
|
||||||
|
if symbol+int(repeat) > len(dst) {
|
||||||
|
return errInvalidCodeLengths
|
||||||
|
}
|
||||||
|
// A code length of 16 repeats the previous non-zero code.
|
||||||
|
// A code length of 17 or 18 repeats zeroes.
|
||||||
|
cl := uint32(0)
|
||||||
|
if codeLength == 16 {
|
||||||
|
cl = prevCodeLength
|
||||||
|
}
|
||||||
|
for ; repeat > 0; repeat-- {
|
||||||
|
dst[symbol] = cl
|
||||||
|
symbol++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeHuffmanTree decodes a Huffman tree into h.
|
||||||
|
func (d *decoder) decodeHuffmanTree(h *hTree, alphabetSize uint32) error {
|
||||||
|
useSimple, err := d.read(1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if useSimple != 0 {
|
||||||
|
nSymbols, err := d.read(1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nSymbols++
|
||||||
|
firstSymbolLengthCode, err := d.read(1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
firstSymbolLengthCode = 7*firstSymbolLengthCode + 1
|
||||||
|
var symbols [2]uint32
|
||||||
|
symbols[0], err = d.read(firstSymbolLengthCode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if nSymbols == 2 {
|
||||||
|
symbols[1], err = d.read(8)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h.buildSimple(nSymbols, symbols, alphabetSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
nCodes, err := d.read(4)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nCodes += 4
|
||||||
|
if int(nCodes) > len(codeLengthCodeOrder) {
|
||||||
|
return errInvalidHuffmanTree
|
||||||
|
}
|
||||||
|
codeLengthCodeLengths := [len(codeLengthCodeOrder)]uint32{}
|
||||||
|
for i := uint32(0); i < nCodes; i++ {
|
||||||
|
codeLengthCodeLengths[codeLengthCodeOrder[i]], err = d.read(3)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
codeLengths := make([]uint32, alphabetSize)
|
||||||
|
if err = d.decodeCodeLengths(codeLengths, codeLengthCodeLengths[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return h.build(codeLengths)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
huffGreen = 0
|
||||||
|
huffRed = 1
|
||||||
|
huffBlue = 2
|
||||||
|
huffAlpha = 3
|
||||||
|
huffDistance = 4
|
||||||
|
nHuff = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// hGroup is an array of 5 Huffman trees.
|
||||||
|
type hGroup [nHuff]hTree
|
||||||
|
|
||||||
|
// decodeHuffmanGroups decodes the one or more hGroups used to decode the pixel
|
||||||
|
// data. If one hGroup is used for the entire image, then hPix and hBits will
|
||||||
|
// be zero. If more than one hGroup is used, then hPix contains the meta-image
|
||||||
|
// that maps tiles to hGroup index, and hBits contains the log-2 tile size.
|
||||||
|
func (d *decoder) decodeHuffmanGroups(w int32, h int32, topLevel bool, ccBits uint32) (
|
||||||
|
hGroups []hGroup, hPix []byte, hBits uint32, err error) {
|
||||||
|
|
||||||
|
maxHGroupIndex := 0
|
||||||
|
if topLevel {
|
||||||
|
useMeta, err := d.read(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
if useMeta != 0 {
|
||||||
|
hBits, err = d.read(3)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
hBits += 2
|
||||||
|
hPix, err = d.decodePix(nTiles(w, hBits), nTiles(h, hBits), 0, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
for p := 0; p < len(hPix); p += 4 {
|
||||||
|
i := int(hPix[p])<<8 | int(hPix[p+1])
|
||||||
|
if maxHGroupIndex < i {
|
||||||
|
maxHGroupIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hGroups = make([]hGroup, maxHGroupIndex+1)
|
||||||
|
for i := range hGroups {
|
||||||
|
for j, alphabetSize := range alphabetSizes {
|
||||||
|
if j == 0 && ccBits > 0 {
|
||||||
|
alphabetSize += 1 << ccBits
|
||||||
|
}
|
||||||
|
if err := d.decodeHuffmanTree(&hGroups[i][j], alphabetSize); err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hGroups, hPix, hBits, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
nLiteralCodes = 256
|
||||||
|
nLengthCodes = 24
|
||||||
|
nDistanceCodes = 40
|
||||||
|
)
|
||||||
|
|
||||||
|
var alphabetSizes = [nHuff]uint32{
|
||||||
|
nLiteralCodes + nLengthCodes,
|
||||||
|
nLiteralCodes,
|
||||||
|
nLiteralCodes,
|
||||||
|
nLiteralCodes,
|
||||||
|
nDistanceCodes,
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodePix decodes pixel data, specified in section 5.2.2.
|
||||||
|
func (d *decoder) decodePix(w int32, h int32, minCap int32, topLevel bool) ([]byte, error) {
|
||||||
|
// Decode the color cache parameters.
|
||||||
|
ccBits, ccShift, ccEntries := uint32(0), uint32(0), ([]uint32)(nil)
|
||||||
|
useColorCache, err := d.read(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if useColorCache != 0 {
|
||||||
|
ccBits, err = d.read(4)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ccBits < 1 || 11 < ccBits {
|
||||||
|
return nil, errors.New("vp8l: invalid color cache parameters")
|
||||||
|
}
|
||||||
|
ccShift = 32 - ccBits
|
||||||
|
ccEntries = make([]uint32, 1<<ccBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the Huffman groups.
|
||||||
|
hGroups, hPix, hBits, err := d.decodeHuffmanGroups(w, h, topLevel, ccBits)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hMask, tilesPerRow := int32(0), int32(0)
|
||||||
|
if hBits != 0 {
|
||||||
|
hMask, tilesPerRow = 1<<hBits-1, nTiles(w, hBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the pixels.
|
||||||
|
if minCap < 4*w*h {
|
||||||
|
minCap = 4 * w * h
|
||||||
|
}
|
||||||
|
pix := make([]byte, 4*w*h, minCap)
|
||||||
|
p, cachedP := 0, 0
|
||||||
|
x, y := int32(0), int32(0)
|
||||||
|
hg, lookupHG := &hGroups[0], hMask != 0
|
||||||
|
for p < len(pix) {
|
||||||
|
if lookupHG {
|
||||||
|
i := 4 * (tilesPerRow*(y>>hBits) + (x >> hBits))
|
||||||
|
hg = &hGroups[uint32(hPix[i])<<8|uint32(hPix[i+1])]
|
||||||
|
}
|
||||||
|
|
||||||
|
green, err := hg[huffGreen].next(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case green < nLiteralCodes:
|
||||||
|
// We have a literal pixel.
|
||||||
|
red, err := hg[huffRed].next(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
blue, err := hg[huffBlue].next(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
alpha, err := hg[huffAlpha].next(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pix[p+0] = uint8(red)
|
||||||
|
pix[p+1] = uint8(green)
|
||||||
|
pix[p+2] = uint8(blue)
|
||||||
|
pix[p+3] = uint8(alpha)
|
||||||
|
p += 4
|
||||||
|
|
||||||
|
x++
|
||||||
|
if x == w {
|
||||||
|
x, y = 0, y+1
|
||||||
|
}
|
||||||
|
lookupHG = hMask != 0 && x&hMask == 0
|
||||||
|
|
||||||
|
case green < nLiteralCodes+nLengthCodes:
|
||||||
|
// We have a LZ77 backwards reference.
|
||||||
|
length, err := d.lz77Param(green - nLiteralCodes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
distSym, err := hg[huffDistance].next(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
distCode, err := d.lz77Param(distSym)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dist := distanceMap(w, distCode)
|
||||||
|
pEnd := p + 4*int(length)
|
||||||
|
q := p - 4*int(dist)
|
||||||
|
qEnd := pEnd - 4*int(dist)
|
||||||
|
if p < 0 || len(pix) < pEnd || q < 0 || len(pix) < qEnd {
|
||||||
|
return nil, errors.New("vp8l: invalid LZ77 parameters")
|
||||||
|
}
|
||||||
|
for ; p < pEnd; p, q = p+1, q+1 {
|
||||||
|
pix[p] = pix[q]
|
||||||
|
}
|
||||||
|
|
||||||
|
x += int32(length)
|
||||||
|
for x >= w {
|
||||||
|
x, y = x-w, y+1
|
||||||
|
}
|
||||||
|
lookupHG = hMask != 0
|
||||||
|
|
||||||
|
default:
|
||||||
|
// We have a color cache lookup. First, insert previous pixels
|
||||||
|
// into the cache. Note that VP8L assumes ARGB order, but the
|
||||||
|
// Go image.RGBA type is in RGBA order.
|
||||||
|
for ; cachedP < p; cachedP += 4 {
|
||||||
|
argb := uint32(pix[cachedP+0])<<16 |
|
||||||
|
uint32(pix[cachedP+1])<<8 |
|
||||||
|
uint32(pix[cachedP+2])<<0 |
|
||||||
|
uint32(pix[cachedP+3])<<24
|
||||||
|
ccEntries[(argb*colorCacheMultiplier)>>ccShift] = argb
|
||||||
|
}
|
||||||
|
green -= nLiteralCodes + nLengthCodes
|
||||||
|
if int(green) >= len(ccEntries) {
|
||||||
|
return nil, errors.New("vp8l: invalid color cache index")
|
||||||
|
}
|
||||||
|
argb := ccEntries[green]
|
||||||
|
pix[p+0] = uint8(argb >> 16)
|
||||||
|
pix[p+1] = uint8(argb >> 8)
|
||||||
|
pix[p+2] = uint8(argb >> 0)
|
||||||
|
pix[p+3] = uint8(argb >> 24)
|
||||||
|
p += 4
|
||||||
|
|
||||||
|
x++
|
||||||
|
if x == w {
|
||||||
|
x, y = 0, y+1
|
||||||
|
}
|
||||||
|
lookupHG = hMask != 0 && x&hMask == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pix, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lz77Param returns the next LZ77 parameter: a length or a distance, specified
|
||||||
|
// in section 4.2.2.
|
||||||
|
func (d *decoder) lz77Param(symbol uint32) (uint32, error) {
|
||||||
|
if symbol < 4 {
|
||||||
|
return symbol + 1, nil
|
||||||
|
}
|
||||||
|
extraBits := (symbol - 2) >> 1
|
||||||
|
offset := (2 + symbol&1) << extraBits
|
||||||
|
n, err := d.read(extraBits)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return offset + n + 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeHeader decodes the VP8L header from r.
|
||||||
|
func decodeHeader(r io.Reader) (d *decoder, w int32, h int32, err error) {
|
||||||
|
rr, ok := r.(io.ByteReader)
|
||||||
|
if !ok {
|
||||||
|
rr = bufio.NewReader(r)
|
||||||
|
}
|
||||||
|
d = &decoder{r: rr}
|
||||||
|
magic, err := d.read(8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
if magic != 0x2f {
|
||||||
|
return nil, 0, 0, errors.New("vp8l: invalid header")
|
||||||
|
}
|
||||||
|
width, err := d.read(14)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
width++
|
||||||
|
height, err := d.read(14)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
height++
|
||||||
|
_, err = d.read(1) // Read and ignore the hasAlpha hint.
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
version, err := d.read(3)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
if version != 0 {
|
||||||
|
return nil, 0, 0, errors.New("vp8l: unsupported version")
|
||||||
|
}
|
||||||
|
return d, int32(width), int32(height), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeConfig decodes the color model and dimensions of a VP8L image from r.
|
||||||
|
func DecodeConfig(r io.Reader) (image.Config, error) {
|
||||||
|
_, w, h, err := decodeHeader(r)
|
||||||
|
if err != nil {
|
||||||
|
return image.Config{}, err
|
||||||
|
}
|
||||||
|
return image.Config{
|
||||||
|
ColorModel: color.NRGBAModel,
|
||||||
|
Width: int(w),
|
||||||
|
Height: int(h),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes a VP8L image from r.
|
||||||
|
func Decode(r io.Reader) (image.Image, error) {
|
||||||
|
d, w, h, err := decodeHeader(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Decode the transforms.
|
||||||
|
var (
|
||||||
|
nTransforms int
|
||||||
|
transforms [nTransformTypes]transform
|
||||||
|
transformsSeen [nTransformTypes]bool
|
||||||
|
originalW = w
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
more, err := d.read(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if more == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var t transform
|
||||||
|
t, w, err = d.decodeTransform(w, h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if transformsSeen[t.transformType] {
|
||||||
|
return nil, errors.New("vp8l: repeated transform")
|
||||||
|
}
|
||||||
|
transformsSeen[t.transformType] = true
|
||||||
|
transforms[nTransforms] = t
|
||||||
|
nTransforms++
|
||||||
|
}
|
||||||
|
// Decode the transformed pixels.
|
||||||
|
pix, err := d.decodePix(w, h, 0, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Apply the inverse transformations.
|
||||||
|
for i := nTransforms - 1; i >= 0; i-- {
|
||||||
|
t := &transforms[i]
|
||||||
|
pix = inverseTransforms[t.transformType](t, pix, h)
|
||||||
|
}
|
||||||
|
return &image.NRGBA{
|
||||||
|
Pix: pix,
|
||||||
|
Stride: 4 * int(originalW),
|
||||||
|
Rect: image.Rect(0, 0, int(originalW), int(h)),
|
||||||
|
}, nil
|
||||||
|
}
|
245
vp8l/huffman.go
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
// Copyright 2014 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 vp8l
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// reverseBits reverses the bits in a byte.
|
||||||
|
var reverseBits = [256]uint8{
|
||||||
|
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
|
||||||
|
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
|
||||||
|
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
|
||||||
|
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
|
||||||
|
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
|
||||||
|
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
|
||||||
|
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
|
||||||
|
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
|
||||||
|
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
|
||||||
|
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
|
||||||
|
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
|
||||||
|
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
|
||||||
|
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
|
||||||
|
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
|
||||||
|
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
|
||||||
|
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
|
||||||
|
}
|
||||||
|
|
||||||
|
// hNode is a node in a Huffman tree.
|
||||||
|
type hNode struct {
|
||||||
|
// symbol is the symbol held by this node.
|
||||||
|
symbol uint32
|
||||||
|
// children, if positive, is the hTree.nodes index of the first of
|
||||||
|
// this node's two children. Zero means an uninitialized node,
|
||||||
|
// and -1 means a leaf node.
|
||||||
|
children int32
|
||||||
|
}
|
||||||
|
|
||||||
|
const leafNode = -1
|
||||||
|
|
||||||
|
// lutSize is the log-2 size of an hTree's look-up table.
|
||||||
|
const lutSize, lutMask = 7, 1<<7 - 1
|
||||||
|
|
||||||
|
// hTree is a Huffman tree.
|
||||||
|
type hTree struct {
|
||||||
|
// nodes are the nodes of the Huffman tree. During construction,
|
||||||
|
// len(nodes) grows from 1 up to cap(nodes) by steps of two.
|
||||||
|
// After construction, len(nodes) == cap(nodes), and both equal
|
||||||
|
// 2*theNumberOfSymbols - 1.
|
||||||
|
nodes []hNode
|
||||||
|
// lut is a look-up table for walking the nodes. The x in lut[x] is
|
||||||
|
// the next lutSize bits in the bit-stream. The low 8 bits of lut[x]
|
||||||
|
// equals 1 plus the number of bits in the next code, or 0 if the
|
||||||
|
// next code requires more than lutSize bits. The high 24 bits are:
|
||||||
|
// - the symbol, if the code requires lutSize or fewer bits, or
|
||||||
|
// - the hTree.nodes index to start the tree traversal from, if
|
||||||
|
// the next code requires more than lutSize bits.
|
||||||
|
lut [1 << lutSize]uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert inserts into the hTree a symbol whose encoding is the least
|
||||||
|
// significant codeLength bits of code.
|
||||||
|
func (h *hTree) insert(symbol uint32, code uint32, codeLength uint32) error {
|
||||||
|
if symbol > 0xffff || codeLength > 0xfe {
|
||||||
|
return errInvalidHuffmanTree
|
||||||
|
}
|
||||||
|
baseCode := uint32(0)
|
||||||
|
if codeLength > lutSize {
|
||||||
|
baseCode = uint32(reverseBits[(code>>(codeLength-lutSize))&0xff]) >> (8 - lutSize)
|
||||||
|
} else {
|
||||||
|
baseCode = uint32(reverseBits[code&0xff]) >> (8 - codeLength)
|
||||||
|
for i := 0; i < 1<<(lutSize-codeLength); i++ {
|
||||||
|
h.lut[baseCode|uint32(i)<<codeLength] = symbol<<8 | (codeLength + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := uint32(0)
|
||||||
|
for jump := lutSize; codeLength > 0; {
|
||||||
|
codeLength--
|
||||||
|
if int(n) > len(h.nodes) {
|
||||||
|
return errInvalidHuffmanTree
|
||||||
|
}
|
||||||
|
switch h.nodes[n].children {
|
||||||
|
case leafNode:
|
||||||
|
return errInvalidHuffmanTree
|
||||||
|
case 0:
|
||||||
|
if len(h.nodes) == cap(h.nodes) {
|
||||||
|
return errInvalidHuffmanTree
|
||||||
|
}
|
||||||
|
// Create two empty child nodes.
|
||||||
|
h.nodes[n].children = int32(len(h.nodes))
|
||||||
|
h.nodes = h.nodes[:len(h.nodes)+2]
|
||||||
|
}
|
||||||
|
n = uint32(h.nodes[n].children) + 1&(code>>codeLength)
|
||||||
|
jump--
|
||||||
|
if jump == 0 && h.lut[baseCode] == 0 {
|
||||||
|
h.lut[baseCode] = n << 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch h.nodes[n].children {
|
||||||
|
case leafNode:
|
||||||
|
// No-op.
|
||||||
|
case 0:
|
||||||
|
// Turn the uninitialized node into a leaf.
|
||||||
|
h.nodes[n].children = leafNode
|
||||||
|
default:
|
||||||
|
return errInvalidHuffmanTree
|
||||||
|
}
|
||||||
|
h.nodes[n].symbol = symbol
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// codeLengthsToCodes returns the canonical Huffman codes implied by the
|
||||||
|
// sequence of code lengths.
|
||||||
|
func codeLengthsToCodes(codeLengths []uint32) ([]uint32, error) {
|
||||||
|
maxCodeLength := uint32(0)
|
||||||
|
for _, cl := range codeLengths {
|
||||||
|
if maxCodeLength < cl {
|
||||||
|
maxCodeLength = cl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const maxAllowedCodeLength = 15
|
||||||
|
if len(codeLengths) == 0 || maxCodeLength > maxAllowedCodeLength {
|
||||||
|
return nil, errInvalidHuffmanTree
|
||||||
|
}
|
||||||
|
histogram := [maxAllowedCodeLength + 1]uint32{}
|
||||||
|
for _, cl := range codeLengths {
|
||||||
|
histogram[cl]++
|
||||||
|
}
|
||||||
|
currCode, nextCodes := uint32(0), [maxAllowedCodeLength + 1]uint32{}
|
||||||
|
for cl := 1; cl < len(nextCodes); cl++ {
|
||||||
|
currCode = (currCode + histogram[cl-1]) << 1
|
||||||
|
nextCodes[cl] = currCode
|
||||||
|
}
|
||||||
|
codes := make([]uint32, len(codeLengths))
|
||||||
|
for symbol, cl := range codeLengths {
|
||||||
|
if cl > 0 {
|
||||||
|
codes[symbol] = nextCodes[cl]
|
||||||
|
nextCodes[cl]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return codes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// build builds a canonical Huffman tree from the given code lengths.
|
||||||
|
func (h *hTree) build(codeLengths []uint32) error {
|
||||||
|
// Calculate the number of symbols.
|
||||||
|
var nSymbols, lastSymbol uint32
|
||||||
|
for symbol, cl := range codeLengths {
|
||||||
|
if cl != 0 {
|
||||||
|
nSymbols++
|
||||||
|
lastSymbol = uint32(symbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nSymbols == 0 {
|
||||||
|
return errInvalidHuffmanTree
|
||||||
|
}
|
||||||
|
h.nodes = make([]hNode, 1, 2*nSymbols-1)
|
||||||
|
// Handle the trivial case.
|
||||||
|
if nSymbols == 1 {
|
||||||
|
if len(codeLengths) <= int(lastSymbol) {
|
||||||
|
return errInvalidHuffmanTree
|
||||||
|
}
|
||||||
|
return h.insert(lastSymbol, 0, 0)
|
||||||
|
}
|
||||||
|
// Handle the non-trivial case.
|
||||||
|
codes, err := codeLengthsToCodes(codeLengths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for symbol, cl := range codeLengths {
|
||||||
|
if cl > 0 {
|
||||||
|
if err := h.insert(uint32(symbol), codes[symbol], cl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildSimple builds a Huffman tree with 1 or 2 symbols.
|
||||||
|
func (h *hTree) buildSimple(nSymbols uint32, symbols [2]uint32, alphabetSize uint32) error {
|
||||||
|
h.nodes = make([]hNode, 1, 2*nSymbols-1)
|
||||||
|
for i := uint32(0); i < nSymbols; i++ {
|
||||||
|
if symbols[i] >= alphabetSize {
|
||||||
|
return errInvalidHuffmanTree
|
||||||
|
}
|
||||||
|
if err := h.insert(symbols[i], i, nSymbols-1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// next returns the next Huffman-encoded symbol from the bit-stream d.
|
||||||
|
func (h *hTree) next(d *decoder) (uint32, error) {
|
||||||
|
var n uint32
|
||||||
|
// Read enough bits so that we can use the look-up table.
|
||||||
|
if d.nBits < lutSize {
|
||||||
|
c, err := d.r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
// There are no more bytes of data, but we may still be able
|
||||||
|
// to read the next symbol out of the previously read bits.
|
||||||
|
goto slowPath
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
d.bits |= uint32(c) << d.nBits
|
||||||
|
d.nBits += 8
|
||||||
|
}
|
||||||
|
// Use the look-up table.
|
||||||
|
n = h.lut[d.bits&lutMask]
|
||||||
|
if b := n & 0xff; b != 0 {
|
||||||
|
b--
|
||||||
|
d.bits >>= b
|
||||||
|
d.nBits -= b
|
||||||
|
return n >> 8, nil
|
||||||
|
}
|
||||||
|
n >>= 8
|
||||||
|
d.bits >>= lutSize
|
||||||
|
d.nBits -= lutSize
|
||||||
|
|
||||||
|
slowPath:
|
||||||
|
for h.nodes[n].children != leafNode {
|
||||||
|
if d.nBits == 0 {
|
||||||
|
c, err := d.r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
d.bits = uint32(c)
|
||||||
|
d.nBits = 8
|
||||||
|
}
|
||||||
|
n = uint32(h.nodes[n].children) + 1&d.bits
|
||||||
|
d.bits >>= 1
|
||||||
|
d.nBits--
|
||||||
|
}
|
||||||
|
return h.nodes[n].symbol, nil
|
||||||
|
}
|
299
vp8l/transform.go
Normal file
|
@ -0,0 +1,299 @@
|
||||||
|
// Copyright 2014 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 vp8l
|
||||||
|
|
||||||
|
// This file deals with image transforms, specified in section 3.
|
||||||
|
|
||||||
|
// nTiles returns the number of tiles needed to cover size pixels, where each
|
||||||
|
// tile's side is 1<<bits pixels long.
|
||||||
|
func nTiles(size int32, bits uint32) int32 {
|
||||||
|
return (size + 1<<bits - 1) >> bits
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
transformTypePredictor = 0
|
||||||
|
transformTypeCrossColor = 1
|
||||||
|
transformTypeSubtractGreen = 2
|
||||||
|
transformTypeColorIndexing = 3
|
||||||
|
nTransformTypes = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// transform holds the parameters for an invertible transform.
|
||||||
|
type transform struct {
|
||||||
|
// transformType is the type of the transform.
|
||||||
|
transformType uint32
|
||||||
|
// oldWidth is the width of the image before transformation (or
|
||||||
|
// equivalently, after inverse transformation). The color-indexing
|
||||||
|
// transform can reduce the width. For example, a 50-pixel-wide
|
||||||
|
// image that only needs 4 bits (half a byte) per color index can
|
||||||
|
// be transformed into a 25-pixel-wide image.
|
||||||
|
oldWidth int32
|
||||||
|
// bits is the log-2 size of the transform's tiles, for the predictor
|
||||||
|
// and cross-color transforms. 8>>bits is the number of bits per
|
||||||
|
// color index, for the color-index transform.
|
||||||
|
bits uint32
|
||||||
|
// pix is the tile values, for the predictor and cross-color
|
||||||
|
// transforms, and the color palette, for the color-index transform.
|
||||||
|
pix []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var inverseTransforms = [nTransformTypes]func(*transform, []byte, int32) []byte{
|
||||||
|
transformTypePredictor: inversePredictor,
|
||||||
|
transformTypeCrossColor: inverseCrossColor,
|
||||||
|
transformTypeSubtractGreen: inverseSubtractGreen,
|
||||||
|
transformTypeColorIndexing: inverseColorIndexing,
|
||||||
|
}
|
||||||
|
|
||||||
|
func inversePredictor(t *transform, pix []byte, h int32) []byte {
|
||||||
|
if t.oldWidth == 0 || h == 0 {
|
||||||
|
return pix
|
||||||
|
}
|
||||||
|
// The first pixel's predictor is mode 0 (opaque black).
|
||||||
|
pix[3] += 0xff
|
||||||
|
p, mask := int32(4), int32(1)<<t.bits-1
|
||||||
|
for x := int32(1); x < t.oldWidth; x++ {
|
||||||
|
// The rest of the first row's predictor is mode 1 (L).
|
||||||
|
pix[p+0] += pix[p-4]
|
||||||
|
pix[p+1] += pix[p-3]
|
||||||
|
pix[p+2] += pix[p-2]
|
||||||
|
pix[p+3] += pix[p-1]
|
||||||
|
p += 4
|
||||||
|
}
|
||||||
|
top, tilesPerRow := 0, nTiles(t.oldWidth, t.bits)
|
||||||
|
for y := int32(1); y < h; y++ {
|
||||||
|
// The first column's predictor is mode 2 (T).
|
||||||
|
pix[p+0] += pix[top+0]
|
||||||
|
pix[p+1] += pix[top+1]
|
||||||
|
pix[p+2] += pix[top+2]
|
||||||
|
pix[p+3] += pix[top+3]
|
||||||
|
p, top = p+4, top+4
|
||||||
|
|
||||||
|
q := 4 * (y >> t.bits) * tilesPerRow
|
||||||
|
predictorMode := t.pix[q+1] & 0x0f
|
||||||
|
q += 4
|
||||||
|
for x := int32(1); x < t.oldWidth; x++ {
|
||||||
|
if x&mask == 0 {
|
||||||
|
predictorMode = t.pix[q+1] & 0x0f
|
||||||
|
q += 4
|
||||||
|
}
|
||||||
|
switch predictorMode {
|
||||||
|
case 0: // Opaque black.
|
||||||
|
pix[p+3] += 0xff
|
||||||
|
|
||||||
|
case 1: // L.
|
||||||
|
pix[p+0] += pix[p-4]
|
||||||
|
pix[p+1] += pix[p-3]
|
||||||
|
pix[p+2] += pix[p-2]
|
||||||
|
pix[p+3] += pix[p-1]
|
||||||
|
|
||||||
|
case 2: // T.
|
||||||
|
pix[p+0] += pix[top+0]
|
||||||
|
pix[p+1] += pix[top+1]
|
||||||
|
pix[p+2] += pix[top+2]
|
||||||
|
pix[p+3] += pix[top+3]
|
||||||
|
|
||||||
|
case 3: // TR.
|
||||||
|
pix[p+0] += pix[top+4]
|
||||||
|
pix[p+1] += pix[top+5]
|
||||||
|
pix[p+2] += pix[top+6]
|
||||||
|
pix[p+3] += pix[top+7]
|
||||||
|
|
||||||
|
case 4: // TL.
|
||||||
|
pix[p+0] += pix[top-4]
|
||||||
|
pix[p+1] += pix[top-3]
|
||||||
|
pix[p+2] += pix[top-2]
|
||||||
|
pix[p+3] += pix[top-1]
|
||||||
|
|
||||||
|
case 5: // Average2(Average2(L, TR), T).
|
||||||
|
pix[p+0] += avg2(avg2(pix[p-4], pix[top+4]), pix[top+0])
|
||||||
|
pix[p+1] += avg2(avg2(pix[p-3], pix[top+5]), pix[top+1])
|
||||||
|
pix[p+2] += avg2(avg2(pix[p-2], pix[top+6]), pix[top+2])
|
||||||
|
pix[p+3] += avg2(avg2(pix[p-1], pix[top+7]), pix[top+3])
|
||||||
|
|
||||||
|
case 6: // Average2(L, TL).
|
||||||
|
pix[p+0] += avg2(pix[p-4], pix[top-4])
|
||||||
|
pix[p+1] += avg2(pix[p-3], pix[top-3])
|
||||||
|
pix[p+2] += avg2(pix[p-2], pix[top-2])
|
||||||
|
pix[p+3] += avg2(pix[p-1], pix[top-1])
|
||||||
|
|
||||||
|
case 7: // Average2(L, T).
|
||||||
|
pix[p+0] += avg2(pix[p-4], pix[top+0])
|
||||||
|
pix[p+1] += avg2(pix[p-3], pix[top+1])
|
||||||
|
pix[p+2] += avg2(pix[p-2], pix[top+2])
|
||||||
|
pix[p+3] += avg2(pix[p-1], pix[top+3])
|
||||||
|
|
||||||
|
case 8: // Average2(TL, T).
|
||||||
|
pix[p+0] += avg2(pix[top-4], pix[top+0])
|
||||||
|
pix[p+1] += avg2(pix[top-3], pix[top+1])
|
||||||
|
pix[p+2] += avg2(pix[top-2], pix[top+2])
|
||||||
|
pix[p+3] += avg2(pix[top-1], pix[top+3])
|
||||||
|
|
||||||
|
case 9: // Average2(T, TR).
|
||||||
|
pix[p+0] += avg2(pix[top+0], pix[top+4])
|
||||||
|
pix[p+1] += avg2(pix[top+1], pix[top+5])
|
||||||
|
pix[p+2] += avg2(pix[top+2], pix[top+6])
|
||||||
|
pix[p+3] += avg2(pix[top+3], pix[top+7])
|
||||||
|
|
||||||
|
case 10: // Average2(Average2(L, TL), Average2(T, TR)).
|
||||||
|
pix[p+0] += avg2(avg2(pix[p-4], pix[top-4]), avg2(pix[top+0], pix[top+4]))
|
||||||
|
pix[p+1] += avg2(avg2(pix[p-3], pix[top-3]), avg2(pix[top+1], pix[top+5]))
|
||||||
|
pix[p+2] += avg2(avg2(pix[p-2], pix[top-2]), avg2(pix[top+2], pix[top+6]))
|
||||||
|
pix[p+3] += avg2(avg2(pix[p-1], pix[top-1]), avg2(pix[top+3], pix[top+7]))
|
||||||
|
|
||||||
|
case 11: // Select(T, L, TL).
|
||||||
|
l0 := int32(pix[p-4])
|
||||||
|
l1 := int32(pix[p-3])
|
||||||
|
l2 := int32(pix[p-2])
|
||||||
|
l3 := int32(pix[p-1])
|
||||||
|
c0 := int32(pix[top-4])
|
||||||
|
c1 := int32(pix[top-3])
|
||||||
|
c2 := int32(pix[top-2])
|
||||||
|
c3 := int32(pix[top-1])
|
||||||
|
t0 := int32(pix[top+0])
|
||||||
|
t1 := int32(pix[top+1])
|
||||||
|
t2 := int32(pix[top+2])
|
||||||
|
t3 := int32(pix[top+3])
|
||||||
|
t := abs(c0-l0) + abs(c1-l1) + abs(c2-l2) + abs(c3-l3)
|
||||||
|
l := abs(c0-t0) + abs(c1-t1) + abs(c2-t2) + abs(c3-t3)
|
||||||
|
if t <= l {
|
||||||
|
pix[p+0] += uint8(t0)
|
||||||
|
pix[p+1] += uint8(t1)
|
||||||
|
pix[p+2] += uint8(t2)
|
||||||
|
pix[p+3] += uint8(t3)
|
||||||
|
} else {
|
||||||
|
pix[p+0] += uint8(l0)
|
||||||
|
pix[p+1] += uint8(l1)
|
||||||
|
pix[p+2] += uint8(l2)
|
||||||
|
pix[p+3] += uint8(l3)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 12: // ClampAddSubtractFull(L, T, TL).
|
||||||
|
pix[p+0] += clampAddSubtractFull(pix[p-4], pix[top+0], pix[top-4])
|
||||||
|
pix[p+1] += clampAddSubtractFull(pix[p-3], pix[top+1], pix[top-3])
|
||||||
|
pix[p+2] += clampAddSubtractFull(pix[p-2], pix[top+2], pix[top-2])
|
||||||
|
pix[p+3] += clampAddSubtractFull(pix[p-1], pix[top+3], pix[top-1])
|
||||||
|
|
||||||
|
case 13: // ClampAddSubtractHalf(Average2(L, T), TL).
|
||||||
|
pix[p+0] += clampAddSubtractHalf(avg2(pix[p-4], pix[top+0]), pix[top-4])
|
||||||
|
pix[p+1] += clampAddSubtractHalf(avg2(pix[p-3], pix[top+1]), pix[top-3])
|
||||||
|
pix[p+2] += clampAddSubtractHalf(avg2(pix[p-2], pix[top+2]), pix[top-2])
|
||||||
|
pix[p+3] += clampAddSubtractHalf(avg2(pix[p-1], pix[top+3]), pix[top-1])
|
||||||
|
}
|
||||||
|
p, top = p+4, top+4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pix
|
||||||
|
}
|
||||||
|
|
||||||
|
func inverseCrossColor(t *transform, pix []byte, h int32) []byte {
|
||||||
|
var greenToRed, greenToBlue, redToBlue int32
|
||||||
|
p, mask, tilesPerRow := int32(0), int32(1)<<t.bits-1, nTiles(t.oldWidth, t.bits)
|
||||||
|
for y := int32(0); y < h; y++ {
|
||||||
|
q := 4 * (y >> t.bits) * tilesPerRow
|
||||||
|
for x := int32(0); x < t.oldWidth; x++ {
|
||||||
|
if x&mask == 0 {
|
||||||
|
redToBlue = int32(int8(t.pix[q+0]))
|
||||||
|
greenToBlue = int32(int8(t.pix[q+1]))
|
||||||
|
greenToRed = int32(int8(t.pix[q+2]))
|
||||||
|
q += 4
|
||||||
|
}
|
||||||
|
red := pix[p+0]
|
||||||
|
green := pix[p+1]
|
||||||
|
blue := pix[p+2]
|
||||||
|
red += uint8(uint32(greenToRed*int32(int8(green))) >> 5)
|
||||||
|
blue += uint8(uint32(greenToBlue*int32(int8(green))) >> 5)
|
||||||
|
blue += uint8(uint32(redToBlue*int32(int8(red))) >> 5)
|
||||||
|
pix[p+0] = red
|
||||||
|
pix[p+2] = blue
|
||||||
|
p += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pix
|
||||||
|
}
|
||||||
|
|
||||||
|
func inverseSubtractGreen(t *transform, pix []byte, h int32) []byte {
|
||||||
|
for p := 0; p < len(pix); p += 4 {
|
||||||
|
green := pix[p+1]
|
||||||
|
pix[p+0] += green
|
||||||
|
pix[p+2] += green
|
||||||
|
}
|
||||||
|
return pix
|
||||||
|
}
|
||||||
|
|
||||||
|
func inverseColorIndexing(t *transform, pix []byte, h int32) []byte {
|
||||||
|
if t.bits == 0 {
|
||||||
|
for p := 0; p < len(pix); p += 4 {
|
||||||
|
i := 4 * uint32(pix[p+1])
|
||||||
|
pix[p+0] = t.pix[i+0]
|
||||||
|
pix[p+1] = t.pix[i+1]
|
||||||
|
pix[p+2] = t.pix[i+2]
|
||||||
|
pix[p+3] = t.pix[i+3]
|
||||||
|
}
|
||||||
|
return pix
|
||||||
|
}
|
||||||
|
|
||||||
|
vMask, xMask, bitsPerPixel := uint32(0), int32(0), uint32(8>>t.bits)
|
||||||
|
switch t.bits {
|
||||||
|
case 1:
|
||||||
|
vMask, xMask = 0x0f, 0x01
|
||||||
|
case 2:
|
||||||
|
vMask, xMask = 0x03, 0x03
|
||||||
|
case 3:
|
||||||
|
vMask, xMask = 0x01, 0x07
|
||||||
|
}
|
||||||
|
|
||||||
|
d, p, v, dst := 0, 0, uint32(0), make([]byte, 4*t.oldWidth*h)
|
||||||
|
for y := int32(0); y < h; y++ {
|
||||||
|
for x := int32(0); x < t.oldWidth; x++ {
|
||||||
|
if x&xMask == 0 {
|
||||||
|
v = uint32(pix[p+1])
|
||||||
|
p += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 4 * (v & vMask)
|
||||||
|
dst[d+0] = t.pix[i+0]
|
||||||
|
dst[d+1] = t.pix[i+1]
|
||||||
|
dst[d+2] = t.pix[i+2]
|
||||||
|
dst[d+3] = t.pix[i+3]
|
||||||
|
d += 4
|
||||||
|
|
||||||
|
v >>= bitsPerPixel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func abs(x int32) int32 {
|
||||||
|
if x < 0 {
|
||||||
|
return -x
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func avg2(a, b uint8) uint8 {
|
||||||
|
return uint8((int32(a) + int32(b)) / 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func clampAddSubtractFull(a, b, c uint8) uint8 {
|
||||||
|
x := int32(a) + int32(b) - int32(c)
|
||||||
|
if x < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if x > 255 {
|
||||||
|
return 255
|
||||||
|
}
|
||||||
|
return uint8(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func clampAddSubtractHalf(a, b uint8) uint8 {
|
||||||
|
x := int32(a) + (int32(a)-int32(b))/2
|
||||||
|
if x < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if x > 255 {
|
||||||
|
return 255
|
||||||
|
}
|
||||||
|
return uint8(x)
|
||||||
|
}
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
// Package webp implements a decoder for WEBP images.
|
// Package webp implements a decoder for WEBP images.
|
||||||
//
|
//
|
||||||
// WEBP is defined in the VP8 specification at:
|
// WEBP is defined at:
|
||||||
// http://datatracker.ietf.org/doc/rfc6386/
|
// https://developers.google.com/speed/webp/docs/riff_container
|
||||||
package webp
|
package webp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -15,59 +15,79 @@ import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"code.google.com/p/go.image/vp8"
|
"code.google.com/p/go.image/vp8"
|
||||||
|
"code.google.com/p/go.image/vp8l"
|
||||||
)
|
)
|
||||||
|
|
||||||
func decode(r io.Reader) (d *vp8.Decoder, fh vp8.FrameHeader, err error) {
|
const (
|
||||||
|
formatVP8 = 1
|
||||||
|
formatVP8L = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
|
||||||
var b [20]byte
|
var b [20]byte
|
||||||
if _, err = io.ReadFull(r, b[:]); err != nil {
|
if _, err := io.ReadFull(r, b[:]); err != nil {
|
||||||
return
|
return nil, image.Config{}, err
|
||||||
}
|
}
|
||||||
if string(b[0:4]) != "RIFF" || string(b[8:16]) != "WEBPVP8 " {
|
format := 0
|
||||||
err = errors.New("webp: invalid format")
|
switch string(b[8:16]) {
|
||||||
return
|
case "WEBPVP8 ":
|
||||||
|
format = formatVP8
|
||||||
|
case "WEBPVP8L":
|
||||||
|
format = formatVP8L
|
||||||
|
}
|
||||||
|
if string(b[:4]) != "RIFF" || format == 0 {
|
||||||
|
return nil, image.Config{}, errors.New("webp: invalid format")
|
||||||
}
|
}
|
||||||
riffLen := uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16 | uint32(b[7])<<24
|
riffLen := uint32(b[4]) | uint32(b[5])<<8 | uint32(b[6])<<16 | uint32(b[7])<<24
|
||||||
dataLen := uint32(b[16]) | uint32(b[17])<<8 | uint32(b[18])<<16 | uint32(b[19])<<24
|
dataLen := uint32(b[16]) | uint32(b[17])<<8 | uint32(b[18])<<16 | uint32(b[19])<<24
|
||||||
if riffLen < dataLen+12 {
|
if riffLen < dataLen+12 {
|
||||||
err = errors.New("webp: invalid format")
|
return nil, image.Config{}, errors.New("webp: invalid format")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if dataLen >= 1<<31 {
|
if dataLen >= 1<<31 {
|
||||||
err = errors.New("webp: invalid format")
|
return nil, image.Config{}, errors.New("webp: invalid format")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
d = vp8.NewDecoder()
|
|
||||||
|
if format == formatVP8 {
|
||||||
|
d := vp8.NewDecoder()
|
||||||
d.Init(r, int(dataLen))
|
d.Init(r, int(dataLen))
|
||||||
fh, err = d.DecodeFrameHeader()
|
fh, err := d.DecodeFrameHeader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d, fh = nil, vp8.FrameHeader{}
|
return nil, image.Config{}, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
return
|
if configOnly {
|
||||||
|
return nil, image.Config{
|
||||||
|
ColorModel: color.YCbCrModel,
|
||||||
|
Width: fh.Width,
|
||||||
|
Height: fh.Height,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
m, err := d.DecodeFrame()
|
||||||
|
return m, image.Config{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r = &io.LimitedReader{r, int64(dataLen)}
|
||||||
|
if configOnly {
|
||||||
|
c, err := vp8l.DecodeConfig(r)
|
||||||
|
return nil, c, err
|
||||||
|
}
|
||||||
|
m, err := vp8l.Decode(r)
|
||||||
|
return m, image.Config{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode reads a WEBP image from r and returns it as an image.Image.
|
// Decode reads a WEBP image from r and returns it as an image.Image.
|
||||||
func Decode(r io.Reader) (image.Image, error) {
|
func Decode(r io.Reader) (image.Image, error) {
|
||||||
d, _, err := decode(r)
|
m, _, err := decode(r, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return d.DecodeFrame()
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeConfig returns the color model and dimensions of a WEBP image without
|
// DecodeConfig returns the color model and dimensions of a WEBP image without
|
||||||
// decoding the entire image.
|
// decoding the entire image.
|
||||||
func DecodeConfig(r io.Reader) (image.Config, error) {
|
func DecodeConfig(r io.Reader) (image.Config, error) {
|
||||||
_, fh, err := decode(r)
|
_, c, err := decode(r, true)
|
||||||
if err != nil {
|
return c, err
|
||||||
return image.Config{}, err
|
|
||||||
}
|
|
||||||
c := image.Config{
|
|
||||||
ColorModel: color.YCbCrModel,
|
|
||||||
Width: fh.Width,
|
|
||||||
Height: fh.Height,
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -9,7 +9,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/png"
|
"image/png"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -108,3 +110,105 @@ func TestDecodeVP8(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDecodeVP8L(t *testing.T) {
|
||||||
|
testCases := []string{
|
||||||
|
"blue-purple-pink",
|
||||||
|
"gopher-doc.1bpp",
|
||||||
|
"gopher-doc.2bpp",
|
||||||
|
"gopher-doc.4bpp",
|
||||||
|
"gopher-doc.8bpp",
|
||||||
|
"tux",
|
||||||
|
"yellow_rose",
|
||||||
|
}
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for _, tc := range testCases {
|
||||||
|
f0, err := os.Open("../testdata/" + tc + ".lossless.webp")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Open WEBP: %v", tc, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer f0.Close()
|
||||||
|
img0, err := Decode(f0)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Decode WEBP: %v", tc, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m0, ok := img0.(*image.NRGBA)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: WEBP image is %T, want *image.NRGBA", tc, img0)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
f1, err := os.Open("../testdata/" + tc + ".png")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Open PNG: %v", tc, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer f1.Close()
|
||||||
|
img1, err := png.Decode(f1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Decode PNG: %v", tc, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m1, ok := img1.(*image.NRGBA)
|
||||||
|
if !ok {
|
||||||
|
rgba1, ok := img1.(*image.RGBA)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%s: PNG image is %T, want *image.NRGBA", tc, img1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !rgba1.Opaque() {
|
||||||
|
t.Fatalf("%s: PNG image is non-opaque *image.RGBA, want *image.NRGBA", tc)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The image is fully opaque, so we can re-interpret the RGBA pixels
|
||||||
|
// as NRGBA pixels.
|
||||||
|
m1 = &image.NRGBA{
|
||||||
|
Pix: rgba1.Pix,
|
||||||
|
Stride: rgba1.Stride,
|
||||||
|
Rect: rgba1.Rect,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b0, b1 := m0.Bounds(), m1.Bounds()
|
||||||
|
if b0 != b1 {
|
||||||
|
t.Errorf("%s: bounds: got %v, want %v", tc, b0, b1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := range m0.Pix {
|
||||||
|
if m0.Pix[i] != m1.Pix[i] {
|
||||||
|
y := i / m0.Stride
|
||||||
|
x := (i - y*m0.Stride) / 4
|
||||||
|
i = 4 * (y*m0.Stride + x)
|
||||||
|
t.Errorf("%s: at (%d, %d):\ngot %02x %02x %02x %02x\nwant %02x %02x %02x %02x",
|
||||||
|
tc, x, y,
|
||||||
|
m0.Pix[i+0], m0.Pix[i+1], m0.Pix[i+2], m0.Pix[i+3],
|
||||||
|
m1.Pix[i+0], m1.Pix[i+1], m1.Pix[i+2], m1.Pix[i+3],
|
||||||
|
)
|
||||||
|
continue loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkDecode(b *testing.B, filename string) {
|
||||||
|
data, err := ioutil.ReadFile("../testdata/" + filename + ".webp")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
s := string(data)
|
||||||
|
cfg, err := DecodeConfig(strings.NewReader(s))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(cfg.Width * cfg.Height * 4))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Decode(strings.NewReader(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeVP8(b *testing.B) { benchmarkDecode(b, "yellow_rose.lossy") }
|
||||||
|
func BenchmarkDecodeVP8L(b *testing.B) { benchmarkDecode(b, "yellow_rose.lossless") }
|
||||||
|
|