go.image: initial code.
Manual edits to README. Moved from main Go repository, deleted Makefiles. Tested with go test code.google.com/p/go.image/... Fixes golang/go#2797. R=rsc, rsc, r CC=golang-dev https://golang.org/cl/5593053
This commit is contained in:
parent
f61fbb80d2
commit
cc3e6234e7
17
README
17
README
|
@ -1,16 +1,3 @@
|
||||||
This is an empty Go subrepository. To create a new subrepository,
|
This repository holds supplementary Go image libraries.
|
||||||
visit http://code.google.com/p/go/adminSource and under repositories,
|
|
||||||
type the new name, check [x] Clone contents and select [empty] as
|
|
||||||
the one you want to clone.
|
|
||||||
|
|
||||||
Then execute:
|
To submit changes to this repository, see http://golang.org/doc/contribute.html.
|
||||||
go get code.google.com/p/go.newrepo
|
|
||||||
cd $(go list -e -f '{{.Dir}}' code.google.com/p/go.newrepo)
|
|
||||||
|
|
||||||
The go get will complain about not finding source code, but it will
|
|
||||||
successfully check out the repository.
|
|
||||||
|
|
||||||
Edit the README (this file) to describe the new subrepository, and then
|
|
||||||
use the usual hg change, mail, submit to send in the change.
|
|
||||||
You will need to follow http://golang.org/doc/contribute.html#Code_review
|
|
||||||
to enable the Code Review extension (pointing into your Go root).
|
|
||||||
|
|
152
bmp/reader.go
Normal file
152
bmp/reader.go
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
// 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 bmp implements a BMP image decoder.
|
||||||
|
//
|
||||||
|
// The BMP specification is at http://www.digicamsoft.com/bmp/bmp.html.
|
||||||
|
package bmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrUnsupported means that the input BMP image uses a valid but unsupported
|
||||||
|
// feature.
|
||||||
|
var ErrUnsupported = errors.New("bmp: unsupported BMP image")
|
||||||
|
|
||||||
|
func readUint16(b []byte) uint16 {
|
||||||
|
return uint16(b[0]) | uint16(b[1])<<8
|
||||||
|
}
|
||||||
|
|
||||||
|
func readUint32(b []byte) uint32 {
|
||||||
|
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodePaletted reads an 8 bit-per-pixel BMP image from r.
|
||||||
|
func decodePaletted(r io.Reader, c image.Config) (image.Image, error) {
|
||||||
|
var tmp [4]byte
|
||||||
|
paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), c.ColorModel.(color.Palette))
|
||||||
|
// BMP images are stored bottom-up rather than top-down.
|
||||||
|
for y := c.Height - 1; y >= 0; y-- {
|
||||||
|
p := paletted.Pix[y*paletted.Stride : y*paletted.Stride+c.Width]
|
||||||
|
_, err := io.ReadFull(r, p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Each row is 4-byte aligned.
|
||||||
|
if c.Width%4 != 0 {
|
||||||
|
_, err := io.ReadFull(r, tmp[:4-c.Width%4])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paletted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeRGBA reads a 24 bit-per-pixel BMP image from r.
|
||||||
|
func decodeRGBA(r io.Reader, c image.Config) (image.Image, error) {
|
||||||
|
rgba := image.NewRGBA(image.Rect(0, 0, c.Width, c.Height))
|
||||||
|
// There are 3 bytes per pixel, and each row is 4-byte aligned.
|
||||||
|
b := make([]byte, (3*c.Width+3)&^3)
|
||||||
|
// BMP images are stored bottom-up rather than top-down.
|
||||||
|
for y := c.Height - 1; y >= 0; y-- {
|
||||||
|
_, err := io.ReadFull(r, b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4]
|
||||||
|
for i, j := 0, 0; i < len(p); i, j = i+4, j+3 {
|
||||||
|
// BMP images are stored in BGR order rather than RGB order.
|
||||||
|
p[i+0] = b[j+2]
|
||||||
|
p[i+1] = b[j+1]
|
||||||
|
p[i+2] = b[j+0]
|
||||||
|
p[i+3] = 0xFF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rgba, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode reads a BMP image from r and returns it as an image.Image.
|
||||||
|
// Limitation: The file must be 8 or 24 bits per pixel.
|
||||||
|
func Decode(r io.Reader) (image.Image, error) {
|
||||||
|
c, err := DecodeConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c.ColorModel == color.RGBAModel {
|
||||||
|
return decodeRGBA(r, c)
|
||||||
|
}
|
||||||
|
return decodePaletted(r, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeConfig returns the color model and dimensions of a BMP image without
|
||||||
|
// decoding the entire image.
|
||||||
|
// Limitation: The file must be 8 or 24 bits per pixel.
|
||||||
|
func DecodeConfig(r io.Reader) (config image.Config, err error) {
|
||||||
|
// We only support those BMP images that are a BITMAPFILEHEADER
|
||||||
|
// immediately followed by a BITMAPINFOHEADER.
|
||||||
|
const (
|
||||||
|
fileHeaderLen = 14
|
||||||
|
infoHeaderLen = 40
|
||||||
|
)
|
||||||
|
var b [1024]byte
|
||||||
|
if _, err = io.ReadFull(r, b[:fileHeaderLen+infoHeaderLen]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if string(b[:2]) != "BM" {
|
||||||
|
err = errors.New("bmp: invalid format")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
offset := readUint32(b[10:14])
|
||||||
|
if readUint32(b[14:18]) != infoHeaderLen {
|
||||||
|
err = ErrUnsupported
|
||||||
|
return
|
||||||
|
}
|
||||||
|
width := int(readUint32(b[18:22]))
|
||||||
|
height := int(readUint32(b[22:26]))
|
||||||
|
if width < 0 || height < 0 {
|
||||||
|
err = ErrUnsupported
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// We only support 1 plane, 8 or 24 bits per pixel and no compression.
|
||||||
|
planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34])
|
||||||
|
if planes != 1 || compression != 0 {
|
||||||
|
err = ErrUnsupported
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch bpp {
|
||||||
|
case 8:
|
||||||
|
if offset != fileHeaderLen+infoHeaderLen+256*4 {
|
||||||
|
err = ErrUnsupported
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = io.ReadFull(r, b[:256*4])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pcm := make(color.Palette, 256)
|
||||||
|
for i := range pcm {
|
||||||
|
// BMP images are stored in BGR order rather than RGB order.
|
||||||
|
// Every 4th byte is padding.
|
||||||
|
pcm[i] = color.RGBA{b[4*i+2], b[4*i+1], b[4*i+0], 0xFF}
|
||||||
|
}
|
||||||
|
return image.Config{pcm, width, height}, nil
|
||||||
|
case 24:
|
||||||
|
if offset != fileHeaderLen+infoHeaderLen {
|
||||||
|
err = ErrUnsupported
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return image.Config{color.RGBAModel, width, height}, nil
|
||||||
|
}
|
||||||
|
err = ErrUnsupported
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
image.RegisterFormat("bmp", "BM????\x00\x00\x00\x00", Decode, DecodeConfig)
|
||||||
|
}
|
54
tiff/buffer.go
Normal file
54
tiff/buffer.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// 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 tiff
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// buffer buffers an io.Reader to satisfy io.ReaderAt.
|
||||||
|
type buffer struct {
|
||||||
|
r io.Reader
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *buffer) ReadAt(p []byte, off int64) (int, error) {
|
||||||
|
o := int(off)
|
||||||
|
end := o + len(p)
|
||||||
|
if int64(end) != off+int64(len(p)) {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
m := len(b.buf)
|
||||||
|
if end > m {
|
||||||
|
if end > cap(b.buf) {
|
||||||
|
newcap := 1024
|
||||||
|
for newcap < end {
|
||||||
|
newcap *= 2
|
||||||
|
}
|
||||||
|
newbuf := make([]byte, end, newcap)
|
||||||
|
copy(newbuf, b.buf)
|
||||||
|
b.buf = newbuf
|
||||||
|
} else {
|
||||||
|
b.buf = b.buf[:end]
|
||||||
|
}
|
||||||
|
if n, err := io.ReadFull(b.r, b.buf[m:end]); err != nil {
|
||||||
|
end = m + n
|
||||||
|
b.buf = b.buf[:end]
|
||||||
|
return copy(p, b.buf[o:end]), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy(p, b.buf[o:end]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newReaderAt converts an io.Reader into an io.ReaderAt.
|
||||||
|
func newReaderAt(r io.Reader) io.ReaderAt {
|
||||||
|
if ra, ok := r.(io.ReaderAt); ok {
|
||||||
|
return ra
|
||||||
|
}
|
||||||
|
return &buffer{
|
||||||
|
r: r,
|
||||||
|
buf: make([]byte, 0, 1024),
|
||||||
|
}
|
||||||
|
}
|
36
tiff/buffer_test.go
Normal file
36
tiff/buffer_test.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// 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 tiff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var readAtTests = []struct {
|
||||||
|
n int
|
||||||
|
off int64
|
||||||
|
s string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{2, 0, "ab", nil},
|
||||||
|
{6, 0, "abcdef", nil},
|
||||||
|
{3, 3, "def", nil},
|
||||||
|
{3, 5, "f", io.EOF},
|
||||||
|
{3, 6, "", io.EOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadAt(t *testing.T) {
|
||||||
|
r := newReaderAt(strings.NewReader("abcdef"))
|
||||||
|
b := make([]byte, 10)
|
||||||
|
for _, test := range readAtTests {
|
||||||
|
n, err := r.ReadAt(b[:test.n], test.off)
|
||||||
|
s := string(b[:n])
|
||||||
|
if s != test.s || err != test.err {
|
||||||
|
t.Errorf("buffer.ReadAt(<%v bytes>, %v): got %v, %q; want %v, %q", test.n, test.off, err, s, test.err, test.s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
tiff/compress.go
Normal file
59
tiff/compress.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// 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 tiff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type byteReader interface {
|
||||||
|
io.Reader
|
||||||
|
io.ByteReader
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackBits decodes the PackBits-compressed data in src and returns the
|
||||||
|
// uncompressed data.
|
||||||
|
//
|
||||||
|
// The PackBits compression format is described in section 9 (p. 42)
|
||||||
|
// of the TIFF spec.
|
||||||
|
func unpackBits(r io.Reader) ([]byte, error) {
|
||||||
|
buf := make([]byte, 128)
|
||||||
|
dst := make([]byte, 0, 1024)
|
||||||
|
br, ok := r.(byteReader)
|
||||||
|
if !ok {
|
||||||
|
br = bufio.NewReader(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
b, err := br.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
code := int(int8(b))
|
||||||
|
switch {
|
||||||
|
case code >= 0:
|
||||||
|
n, err := io.ReadFull(br, buf[:code+1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dst = append(dst, buf[:n]...)
|
||||||
|
case code == -128:
|
||||||
|
// No-op.
|
||||||
|
default:
|
||||||
|
if b, err = br.ReadByte(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for j := 0; j < 1-code; j++ {
|
||||||
|
buf[j] = b
|
||||||
|
}
|
||||||
|
dst = append(dst, buf[:1-code]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
103
tiff/consts.go
Normal file
103
tiff/consts.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
// 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 tiff
|
||||||
|
|
||||||
|
// A tiff image file contains one or more images. The metadata
|
||||||
|
// of each image is contained in an Image File Directory (IFD),
|
||||||
|
// which contains entries of 12 bytes each and is described
|
||||||
|
// on page 14-16 of the specification. An IFD entry consists of
|
||||||
|
//
|
||||||
|
// - a tag, which describes the signification of the entry,
|
||||||
|
// - the data type and length of the entry,
|
||||||
|
// - the data itself or a pointer to it if it is more than 4 bytes.
|
||||||
|
//
|
||||||
|
// The presence of a length means that each IFD is effectively an array.
|
||||||
|
|
||||||
|
const (
|
||||||
|
leHeader = "II\x2A\x00" // Header for little-endian files.
|
||||||
|
beHeader = "MM\x00\x2A" // Header for big-endian files.
|
||||||
|
|
||||||
|
ifdLen = 12 // Length of an IFD entry in bytes.
|
||||||
|
)
|
||||||
|
|
||||||
|
// Data types (p. 14-16 of the spec).
|
||||||
|
const (
|
||||||
|
dtByte = 1
|
||||||
|
dtASCII = 2
|
||||||
|
dtShort = 3
|
||||||
|
dtLong = 4
|
||||||
|
dtRational = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// The length of one instance of each data type in bytes.
|
||||||
|
var lengths = [...]uint32{0, 1, 1, 2, 4, 8}
|
||||||
|
|
||||||
|
// Tags (see p. 28-41 of the spec).
|
||||||
|
const (
|
||||||
|
tImageWidth = 256
|
||||||
|
tImageLength = 257
|
||||||
|
tBitsPerSample = 258
|
||||||
|
tCompression = 259
|
||||||
|
tPhotometricInterpretation = 262
|
||||||
|
|
||||||
|
tStripOffsets = 273
|
||||||
|
tSamplesPerPixel = 277
|
||||||
|
tRowsPerStrip = 278
|
||||||
|
tStripByteCounts = 279
|
||||||
|
|
||||||
|
tXResolution = 282
|
||||||
|
tYResolution = 283
|
||||||
|
tResolutionUnit = 296
|
||||||
|
|
||||||
|
tPredictor = 317
|
||||||
|
tColorMap = 320
|
||||||
|
tExtraSamples = 338
|
||||||
|
tSampleFormat = 339
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compression types (defined in various places in the spec and supplements).
|
||||||
|
const (
|
||||||
|
cNone = 1
|
||||||
|
cCCITT = 2
|
||||||
|
cG3 = 3 // Group 3 Fax.
|
||||||
|
cG4 = 4 // Group 4 Fax.
|
||||||
|
cLZW = 5
|
||||||
|
cJPEGOld = 6 // Superseded by cJPEG.
|
||||||
|
cJPEG = 7
|
||||||
|
cDeflate = 8 // zlib compression.
|
||||||
|
cPackBits = 32773
|
||||||
|
cDeflateOld = 32946 // Superseded by cDeflate.
|
||||||
|
)
|
||||||
|
|
||||||
|
// Photometric interpretation values (see p. 37 of the spec).
|
||||||
|
const (
|
||||||
|
pWhiteIsZero = 0
|
||||||
|
pBlackIsZero = 1
|
||||||
|
pRGB = 2
|
||||||
|
pPaletted = 3
|
||||||
|
pTransMask = 4 // transparency mask
|
||||||
|
pCMYK = 5
|
||||||
|
pYCbCr = 6
|
||||||
|
pCIELab = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// Values for the tPredictor tag (page 64-65 of the spec).
|
||||||
|
const (
|
||||||
|
prNone = 1
|
||||||
|
prHorizontal = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// imageMode represents the mode of the image.
|
||||||
|
type imageMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
mBilevel imageMode = iota
|
||||||
|
mPaletted
|
||||||
|
mGray
|
||||||
|
mGrayInvert
|
||||||
|
mRGB
|
||||||
|
mRGBA
|
||||||
|
mNRGBA
|
||||||
|
)
|
430
tiff/reader.go
Normal file
430
tiff/reader.go
Normal file
|
@ -0,0 +1,430 @@
|
||||||
|
// 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 tiff implements a TIFF image decoder.
|
||||||
|
//
|
||||||
|
// The TIFF specification is at http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
|
||||||
|
package tiff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/lzw"
|
||||||
|
"compress/zlib"
|
||||||
|
"encoding/binary"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A FormatError reports that the input is not a valid TIFF image.
|
||||||
|
type FormatError string
|
||||||
|
|
||||||
|
func (e FormatError) Error() string {
|
||||||
|
return "tiff: invalid format: " + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An UnsupportedError reports that the input uses a valid but
|
||||||
|
// unimplemented feature.
|
||||||
|
type UnsupportedError string
|
||||||
|
|
||||||
|
func (e UnsupportedError) Error() string {
|
||||||
|
return "tiff: unsupported feature: " + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An InternalError reports that an internal error was encountered.
|
||||||
|
type InternalError string
|
||||||
|
|
||||||
|
func (e InternalError) Error() string {
|
||||||
|
return "tiff: internal error: " + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
r io.ReaderAt
|
||||||
|
byteOrder binary.ByteOrder
|
||||||
|
config image.Config
|
||||||
|
mode imageMode
|
||||||
|
features map[int][]uint
|
||||||
|
palette []color.Color
|
||||||
|
|
||||||
|
buf []byte
|
||||||
|
off int // Current offset in buf.
|
||||||
|
v uint32 // Buffer value for reading with arbitrary bit depths.
|
||||||
|
nbits uint // Remaining number of bits in v.
|
||||||
|
}
|
||||||
|
|
||||||
|
// firstVal returns the first uint of the features entry with the given tag,
|
||||||
|
// or 0 if the tag does not exist.
|
||||||
|
func (d *decoder) firstVal(tag int) uint {
|
||||||
|
f := d.features[tag]
|
||||||
|
if len(f) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return f[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ifdUint decodes the IFD entry in p, which must be of the Byte, Short
|
||||||
|
// or Long type, and returns the decoded uint values.
|
||||||
|
func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
|
||||||
|
var raw []byte
|
||||||
|
datatype := d.byteOrder.Uint16(p[2:4])
|
||||||
|
count := d.byteOrder.Uint32(p[4:8])
|
||||||
|
if datalen := lengths[datatype] * count; datalen > 4 {
|
||||||
|
// The IFD contains a pointer to the real value.
|
||||||
|
raw = make([]byte, datalen)
|
||||||
|
_, err = d.r.ReadAt(raw, int64(d.byteOrder.Uint32(p[8:12])))
|
||||||
|
} else {
|
||||||
|
raw = p[8 : 8+datalen]
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u = make([]uint, count)
|
||||||
|
switch datatype {
|
||||||
|
case dtByte:
|
||||||
|
for i := uint32(0); i < count; i++ {
|
||||||
|
u[i] = uint(raw[i])
|
||||||
|
}
|
||||||
|
case dtShort:
|
||||||
|
for i := uint32(0); i < count; i++ {
|
||||||
|
u[i] = uint(d.byteOrder.Uint16(raw[2*i : 2*(i+1)]))
|
||||||
|
}
|
||||||
|
case dtLong:
|
||||||
|
for i := uint32(0); i < count; i++ {
|
||||||
|
u[i] = uint(d.byteOrder.Uint32(raw[4*i : 4*(i+1)]))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, UnsupportedError("data type")
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseIFD decides whether the the IFD entry in p is "interesting" and
|
||||||
|
// stows away the data in the decoder.
|
||||||
|
func (d *decoder) parseIFD(p []byte) error {
|
||||||
|
tag := d.byteOrder.Uint16(p[0:2])
|
||||||
|
switch tag {
|
||||||
|
case tBitsPerSample,
|
||||||
|
tExtraSamples,
|
||||||
|
tPhotometricInterpretation,
|
||||||
|
tCompression,
|
||||||
|
tPredictor,
|
||||||
|
tStripOffsets,
|
||||||
|
tStripByteCounts,
|
||||||
|
tRowsPerStrip,
|
||||||
|
tImageLength,
|
||||||
|
tImageWidth:
|
||||||
|
val, err := d.ifdUint(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.features[int(tag)] = val
|
||||||
|
case tColorMap:
|
||||||
|
val, err := d.ifdUint(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
numcolors := len(val) / 3
|
||||||
|
if len(val)%3 != 0 || numcolors <= 0 || numcolors > 256 {
|
||||||
|
return FormatError("bad ColorMap length")
|
||||||
|
}
|
||||||
|
d.palette = make([]color.Color, numcolors)
|
||||||
|
for i := 0; i < numcolors; i++ {
|
||||||
|
d.palette[i] = color.RGBA64{
|
||||||
|
uint16(val[i]),
|
||||||
|
uint16(val[i+numcolors]),
|
||||||
|
uint16(val[i+2*numcolors]),
|
||||||
|
0xffff,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case tSampleFormat:
|
||||||
|
// Page 27 of the spec: If the SampleFormat is present and
|
||||||
|
// the value is not 1 [= unsigned integer data], a Baseline
|
||||||
|
// TIFF reader that cannot handle the SampleFormat value
|
||||||
|
// must terminate the import process gracefully.
|
||||||
|
val, err := d.ifdUint(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, v := range val {
|
||||||
|
if v != 1 {
|
||||||
|
return UnsupportedError("sample format")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readBits reads n bits from the internal buffer starting at the current offset.
|
||||||
|
func (d *decoder) readBits(n uint) uint32 {
|
||||||
|
for d.nbits < n {
|
||||||
|
d.v <<= 8
|
||||||
|
d.v |= uint32(d.buf[d.off])
|
||||||
|
d.off++
|
||||||
|
d.nbits += 8
|
||||||
|
}
|
||||||
|
d.nbits -= n
|
||||||
|
rv := d.v >> d.nbits
|
||||||
|
d.v &^= rv << d.nbits
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushBits discards the unread bits in the buffer used by readBits.
|
||||||
|
// It is used at the end of a line.
|
||||||
|
func (d *decoder) flushBits() {
|
||||||
|
d.v = 0
|
||||||
|
d.nbits = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode decodes the raw data of an image.
|
||||||
|
// It reads from d.buf and writes the strip with ymin <= y < ymax into dst.
|
||||||
|
func (d *decoder) decode(dst image.Image, ymin, ymax int) error {
|
||||||
|
d.off = 0
|
||||||
|
|
||||||
|
// Apply horizontal predictor if necessary.
|
||||||
|
// In this case, p contains the color difference to the preceding pixel.
|
||||||
|
// See page 64-65 of the spec.
|
||||||
|
if d.firstVal(tPredictor) == prHorizontal && d.firstVal(tBitsPerSample) == 8 {
|
||||||
|
var off int
|
||||||
|
spp := len(d.features[tBitsPerSample]) // samples per pixel
|
||||||
|
for y := ymin; y < ymax; y++ {
|
||||||
|
off += spp
|
||||||
|
for x := 0; x < (dst.Bounds().Dx()-1)*spp; x++ {
|
||||||
|
d.buf[off] += d.buf[off-spp]
|
||||||
|
off++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch d.mode {
|
||||||
|
case mGray, mGrayInvert:
|
||||||
|
img := dst.(*image.Gray)
|
||||||
|
bpp := d.firstVal(tBitsPerSample)
|
||||||
|
max := uint32((1 << bpp) - 1)
|
||||||
|
for y := ymin; y < ymax; y++ {
|
||||||
|
for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
|
||||||
|
v := uint8(d.readBits(bpp) * 0xff / max)
|
||||||
|
if d.mode == mGrayInvert {
|
||||||
|
v = 0xff - v
|
||||||
|
}
|
||||||
|
img.SetGray(x, y, color.Gray{v})
|
||||||
|
}
|
||||||
|
d.flushBits()
|
||||||
|
}
|
||||||
|
case mPaletted:
|
||||||
|
img := dst.(*image.Paletted)
|
||||||
|
bpp := d.firstVal(tBitsPerSample)
|
||||||
|
for y := ymin; y < ymax; y++ {
|
||||||
|
for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
|
||||||
|
img.SetColorIndex(x, y, uint8(d.readBits(bpp)))
|
||||||
|
}
|
||||||
|
d.flushBits()
|
||||||
|
}
|
||||||
|
case mRGB:
|
||||||
|
img := dst.(*image.RGBA)
|
||||||
|
min := img.PixOffset(0, ymin)
|
||||||
|
max := img.PixOffset(0, ymax)
|
||||||
|
var off int
|
||||||
|
for i := min; i < max; i += 4 {
|
||||||
|
img.Pix[i+0] = d.buf[off+0]
|
||||||
|
img.Pix[i+1] = d.buf[off+1]
|
||||||
|
img.Pix[i+2] = d.buf[off+2]
|
||||||
|
img.Pix[i+3] = 0xff
|
||||||
|
off += 3
|
||||||
|
}
|
||||||
|
case mNRGBA:
|
||||||
|
img := dst.(*image.NRGBA)
|
||||||
|
min := img.PixOffset(0, ymin)
|
||||||
|
max := img.PixOffset(0, ymax)
|
||||||
|
if len(d.buf) != max-min {
|
||||||
|
return FormatError("short data strip")
|
||||||
|
}
|
||||||
|
copy(img.Pix[min:max], d.buf)
|
||||||
|
case mRGBA:
|
||||||
|
img := dst.(*image.RGBA)
|
||||||
|
min := img.PixOffset(0, ymin)
|
||||||
|
max := img.PixOffset(0, ymax)
|
||||||
|
if len(d.buf) != max-min {
|
||||||
|
return FormatError("short data strip")
|
||||||
|
}
|
||||||
|
copy(img.Pix[min:max], d.buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDecoder(r io.Reader) (*decoder, error) {
|
||||||
|
d := &decoder{
|
||||||
|
r: newReaderAt(r),
|
||||||
|
features: make(map[int][]uint),
|
||||||
|
}
|
||||||
|
|
||||||
|
p := make([]byte, 8)
|
||||||
|
if _, err := d.r.ReadAt(p, 0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch string(p[0:4]) {
|
||||||
|
case leHeader:
|
||||||
|
d.byteOrder = binary.LittleEndian
|
||||||
|
case beHeader:
|
||||||
|
d.byteOrder = binary.BigEndian
|
||||||
|
default:
|
||||||
|
return nil, FormatError("malformed header")
|
||||||
|
}
|
||||||
|
|
||||||
|
ifdOffset := int64(d.byteOrder.Uint32(p[4:8]))
|
||||||
|
|
||||||
|
// The first two bytes contain the number of entries (12 bytes each).
|
||||||
|
if _, err := d.r.ReadAt(p[0:2], ifdOffset); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
numItems := int(d.byteOrder.Uint16(p[0:2]))
|
||||||
|
|
||||||
|
// All IFD entries are read in one chunk.
|
||||||
|
p = make([]byte, ifdLen*numItems)
|
||||||
|
if _, err := d.r.ReadAt(p, ifdOffset+2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(p); i += ifdLen {
|
||||||
|
if err := d.parseIFD(p[i : i+ifdLen]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.config.Width = int(d.firstVal(tImageWidth))
|
||||||
|
d.config.Height = int(d.firstVal(tImageLength))
|
||||||
|
|
||||||
|
if _, ok := d.features[tBitsPerSample]; !ok {
|
||||||
|
return nil, FormatError("BitsPerSample tag missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the image mode.
|
||||||
|
switch d.firstVal(tPhotometricInterpretation) {
|
||||||
|
case pRGB:
|
||||||
|
for _, b := range d.features[tBitsPerSample] {
|
||||||
|
if b != 8 {
|
||||||
|
return nil, UnsupportedError("non-8-bit RGB image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.config.ColorModel = color.RGBAModel
|
||||||
|
// RGB images normally have 3 samples per pixel.
|
||||||
|
// If there are more, ExtraSamples (p. 31-32 of the spec)
|
||||||
|
// gives their meaning (usually an alpha channel).
|
||||||
|
//
|
||||||
|
// This implementation does not support extra samples
|
||||||
|
// of an unspecified type.
|
||||||
|
switch len(d.features[tBitsPerSample]) {
|
||||||
|
case 3:
|
||||||
|
d.mode = mRGB
|
||||||
|
case 4:
|
||||||
|
switch d.firstVal(tExtraSamples) {
|
||||||
|
case 1:
|
||||||
|
d.mode = mRGBA
|
||||||
|
case 2:
|
||||||
|
d.mode = mNRGBA
|
||||||
|
d.config.ColorModel = color.NRGBAModel
|
||||||
|
default:
|
||||||
|
return nil, FormatError("wrong number of samples for RGB")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, FormatError("wrong number of samples for RGB")
|
||||||
|
}
|
||||||
|
case pPaletted:
|
||||||
|
d.mode = mPaletted
|
||||||
|
d.config.ColorModel = color.Palette(d.palette)
|
||||||
|
case pWhiteIsZero:
|
||||||
|
d.mode = mGrayInvert
|
||||||
|
d.config.ColorModel = color.GrayModel
|
||||||
|
case pBlackIsZero:
|
||||||
|
d.mode = mGray
|
||||||
|
d.config.ColorModel = color.GrayModel
|
||||||
|
default:
|
||||||
|
return nil, UnsupportedError("color model")
|
||||||
|
}
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeConfig returns the color model and dimensions of a TIFF image without
|
||||||
|
// decoding the entire image.
|
||||||
|
func DecodeConfig(r io.Reader) (image.Config, error) {
|
||||||
|
d, err := newDecoder(r)
|
||||||
|
if err != nil {
|
||||||
|
return image.Config{}, err
|
||||||
|
}
|
||||||
|
return d.config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode reads a TIFF image from r and returns it as an image.Image.
|
||||||
|
// The type of Image returned depends on the contents of the TIFF.
|
||||||
|
func Decode(r io.Reader) (img image.Image, err error) {
|
||||||
|
d, err := newDecoder(r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have the right number of strips, offsets and counts.
|
||||||
|
rps := int(d.firstVal(tRowsPerStrip))
|
||||||
|
if rps == 0 {
|
||||||
|
// Assume only one strip.
|
||||||
|
rps = d.config.Height
|
||||||
|
}
|
||||||
|
numStrips := (d.config.Height + rps - 1) / rps
|
||||||
|
if rps == 0 || len(d.features[tStripOffsets]) < numStrips || len(d.features[tStripByteCounts]) < numStrips {
|
||||||
|
return nil, FormatError("inconsistent header")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch d.mode {
|
||||||
|
case mGray, mGrayInvert:
|
||||||
|
img = image.NewGray(image.Rect(0, 0, d.config.Width, d.config.Height))
|
||||||
|
case mPaletted:
|
||||||
|
img = image.NewPaletted(image.Rect(0, 0, d.config.Width, d.config.Height), d.palette)
|
||||||
|
case mNRGBA:
|
||||||
|
img = image.NewNRGBA(image.Rect(0, 0, d.config.Width, d.config.Height))
|
||||||
|
case mRGB, mRGBA:
|
||||||
|
img = image.NewRGBA(image.Rect(0, 0, d.config.Width, d.config.Height))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < numStrips; i++ {
|
||||||
|
ymin := i * rps
|
||||||
|
// The last strip may be shorter.
|
||||||
|
if i == numStrips-1 && d.config.Height%rps != 0 {
|
||||||
|
rps = d.config.Height % rps
|
||||||
|
}
|
||||||
|
offset := int64(d.features[tStripOffsets][i])
|
||||||
|
n := int64(d.features[tStripByteCounts][i])
|
||||||
|
switch d.firstVal(tCompression) {
|
||||||
|
case cNone:
|
||||||
|
// TODO(bsiegert): Avoid copy if r is a tiff.buffer.
|
||||||
|
d.buf = make([]byte, n)
|
||||||
|
_, err = d.r.ReadAt(d.buf, offset)
|
||||||
|
case cLZW:
|
||||||
|
r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8)
|
||||||
|
d.buf, err = ioutil.ReadAll(r)
|
||||||
|
r.Close()
|
||||||
|
case cDeflate, cDeflateOld:
|
||||||
|
r, err := zlib.NewReader(io.NewSectionReader(d.r, offset, n))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d.buf, err = ioutil.ReadAll(r)
|
||||||
|
r.Close()
|
||||||
|
case cPackBits:
|
||||||
|
d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n))
|
||||||
|
default:
|
||||||
|
err = UnsupportedError("compression")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = d.decode(img, ymin, ymin+rps)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
image.RegisterFormat("tiff", leHeader, Decode, DecodeConfig)
|
||||||
|
image.RegisterFormat("tiff", beHeader, Decode, DecodeConfig)
|
||||||
|
}
|
119
tiff/reader_test.go
Normal file
119
tiff/reader_test.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
// 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 tiff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Read makes *buffer implements io.Reader, so that we can pass one to Decode.
|
||||||
|
func (*buffer) Read([]byte) (int, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNoRPS tries to decode an image that has no RowsPerStrip tag.
|
||||||
|
// The tag is mandatory according to the spec but some software omits
|
||||||
|
// it in the case of a single strip.
|
||||||
|
func TestNoRPS(t *testing.T) {
|
||||||
|
f, err := os.Open("testdata/no_rps.tiff")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = Decode(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUnpackBits tests the decoding of PackBits-encoded data.
|
||||||
|
func TestUnpackBits(t *testing.T) {
|
||||||
|
var unpackBitsTests = []struct {
|
||||||
|
compressed string
|
||||||
|
uncompressed string
|
||||||
|
}{{
|
||||||
|
// Example data from Wikipedia.
|
||||||
|
"\xfe\xaa\x02\x80\x00\x2a\xfd\xaa\x03\x80\x00\x2a\x22\xf7\xaa",
|
||||||
|
"\xaa\xaa\xaa\x80\x00\x2a\xaa\xaa\xaa\xaa\x80\x00\x2a\x22\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
|
||||||
|
}}
|
||||||
|
for _, u := range unpackBitsTests {
|
||||||
|
buf, err := unpackBits(strings.NewReader(u.compressed))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(buf) != u.uncompressed {
|
||||||
|
t.Fatalf("unpackBits: want %x, got %x", u.uncompressed, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDecompress tests that decoding some TIFF images that use different
|
||||||
|
// compression formats result in the same pixel data.
|
||||||
|
func TestDecompress(t *testing.T) {
|
||||||
|
var decompressTests = []string{
|
||||||
|
"bw-uncompressed.tiff",
|
||||||
|
"bw-deflate.tiff",
|
||||||
|
"bw-packbits.tiff",
|
||||||
|
}
|
||||||
|
var img0 image.Image
|
||||||
|
for _, name := range decompressTests {
|
||||||
|
f, err := os.Open("testdata/" + name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if img0 == nil {
|
||||||
|
img0, err = Decode(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("decoding %s: %v", name, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
img1, err := Decode(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("decoding %s: %v", name, err)
|
||||||
|
}
|
||||||
|
b := img1.Bounds()
|
||||||
|
// Compare images.
|
||||||
|
if !b.Eq(img0.Bounds()) {
|
||||||
|
t.Fatalf("wrong image size: want %s, got %s", img0.Bounds(), b)
|
||||||
|
}
|
||||||
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||||
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
|
c0 := img0.At(x, y)
|
||||||
|
c1 := img1.At(x, y)
|
||||||
|
r0, g0, b0, a0 := c0.RGBA()
|
||||||
|
r1, g1, b1, a1 := c1.RGBA()
|
||||||
|
if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {
|
||||||
|
t.Fatalf("pixel at (%d, %d) has wrong color: want %v, got %v", x, y, c0, c1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = "testdata/video-001-uncompressed.tiff"
|
||||||
|
|
||||||
|
// BenchmarkDecode benchmarks the decoding of an image.
|
||||||
|
func BenchmarkDecode(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
contents, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
r := &buffer{buf: contents}
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := Decode(r)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal("Decode:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
tiff/testdata/bw-deflate.tiff
vendored
Normal file
BIN
tiff/testdata/bw-deflate.tiff
vendored
Normal file
Binary file not shown.
BIN
tiff/testdata/bw-packbits.tiff
vendored
Normal file
BIN
tiff/testdata/bw-packbits.tiff
vendored
Normal file
Binary file not shown.
BIN
tiff/testdata/bw-uncompressed.tiff
vendored
Normal file
BIN
tiff/testdata/bw-uncompressed.tiff
vendored
Normal file
Binary file not shown.
BIN
tiff/testdata/no_rps.tiff
vendored
Normal file
BIN
tiff/testdata/no_rps.tiff
vendored
Normal file
Binary file not shown.
BIN
tiff/testdata/video-001-uncompressed.tiff
vendored
Normal file
BIN
tiff/testdata/video-001-uncompressed.tiff
vendored
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user