package tiff
import (
"bytes"
"compress/zlib"
"encoding/binary"
"fmt"
"image"
"image/color"
"io"
"math"
"golang.org/x/image/ccitt"
"golang.org/x/image/internal/safemath"
"golang.org/x/image/tiff/lzw"
)
type FormatError string
func (e FormatError ) Error () string {
return "tiff: invalid format: " + string (e )
}
type UnsupportedError string
func (e UnsupportedError ) Error () string {
return "tiff: unsupported feature: " + string (e )
}
var (
errNoPixels = FormatError ("not enough pixel data" )
errInvalidColorIndex = FormatError ("invalid color index" )
)
const maxChunkSize = 10 << 20
func safeReadAt(r io .ReaderAt , n uint64 , off int64 ) ([]byte , error ) {
if int64 (n ) < 0 || n != uint64 (int (n )) {
return nil , io .ErrUnexpectedEOF
}
if n < maxChunkSize {
buf := make ([]byte , n )
_ , err := r .ReadAt (buf , off )
if err != nil {
if err != io .EOF || n > 0 {
return nil , err
}
}
return buf , nil
}
var buf []byte
buf1 := make ([]byte , maxChunkSize )
for n > 0 {
next := n
if next > maxChunkSize {
next = maxChunkSize
}
_ , err := r .ReadAt (buf1 [:next ], off )
if err != nil {
return nil , err
}
buf = append (buf , buf1 [:next ]...)
n -= next
off += int64 (next )
}
return buf , nil
}
type decoder struct {
r io .ReaderAt
byteOrder binary .ByteOrder
config image .Config
mode imageMode
bpp uint
features map [int ][]uint
ifd map [int ][ifdLen ]byte
palette []color .Color
buf []byte
off int
v uint32
nbits uint
}
func (d *decoder ) firstVal (tag int ) uint {
f := d .features [tag ]
if len (f ) == 0 {
return 0
}
return f [0 ]
}
func (d *decoder ) firstIntVal (tag int ) (int , error ) {
v := d .firstVal (tag )
if v > math .MaxInt {
return 0 , FormatError ("IFD value too large" )
}
return int (v ), nil
}
func (d *decoder ) ifdUint (p []byte , maxCount int ) (u []uint , err error ) {
var raw []byte
if len (p ) < ifdLen {
return nil , FormatError ("bad IFD entry" )
}
datatype := d .byteOrder .Uint16 (p [2 :4 ])
if dt := int (datatype ); dt <= 0 || dt >= len (lengths ) {
return nil , UnsupportedError ("IFD entry datatype" )
}
count := d .byteOrder .Uint32 (p [4 :8 ])
if count > math .MaxInt32 /lengths [datatype ] {
return nil , FormatError ("IFD data too large" )
}
truncatedCount := min (int (count ), maxCount )
if datalen := lengths [datatype ] * count ; datalen > 4 {
truncatedLen := uint64 (lengths [datatype ]) * uint64 (truncatedCount )
raw , err = safeReadAt (d .r , truncatedLen , int64 (d .byteOrder .Uint32 (p [8 :12 ])))
} else {
raw = p [8 : 8 +datalen ]
}
if err != nil {
return nil , err
}
u = make ([]uint , truncatedCount )
switch datatype {
case dtByte :
for i := range u {
u [i ] = uint (raw [i ])
}
case dtShort :
for i := range u {
u [i ] = uint (d .byteOrder .Uint16 (raw [2 *i : 2 *(i +1 )]))
}
case dtLong :
for i := range u {
u [i ] = uint (d .byteOrder .Uint32 (raw [4 *i : 4 *(i +1 )]))
}
default :
return nil , UnsupportedError ("data type" )
}
return u , nil
}
func (d *decoder ) parseIFDOffsets (tag int , maxCount int ) (u []uint , err error ) {
p , ok := d .ifd [tag ]
if !ok {
return nil , nil
}
return d .ifdUint (p [:], maxCount )
}
func (d *decoder ) parseIFD (p []byte ) (int , error ) {
const smallEntryMaxCount = 16
tag := d .byteOrder .Uint16 (p [0 :2 ])
switch tag {
case tBitsPerSample ,
tExtraSamples ,
tPhotometricInterpretation ,
tCompression ,
tPredictor ,
tRowsPerStrip ,
tTileWidth ,
tTileLength ,
tImageLength ,
tImageWidth ,
tFillOrder ,
tT4Options ,
tT6Options :
val , err := d .ifdUint (p , smallEntryMaxCount )
if err != nil {
return 0 , err
}
d .features [int (tag )] = val
case tStripOffsets ,
tStripByteCounts ,
tTileOffsets ,
tTileByteCounts :
var v [ifdLen ]byte
copy (v [:], p )
d .ifd [int (tag )] = v
case tColorMap :
const maxColors = 256
val , err := d .ifdUint (p , (3 *maxColors )+1 )
if err != nil {
return 0 , err
}
numcolors := len (val ) / 3
if len (val )%3 != 0 || numcolors <= 0 || numcolors > maxColors {
return 0 , 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 :
val , err := d .ifdUint (p , smallEntryMaxCount )
if err != nil {
return 0 , err
}
for _ , v := range val {
if v != 1 {
return 0 , UnsupportedError ("sample format" )
}
}
}
return int (tag ), nil
}
func (d *decoder ) readBits (n uint ) (v uint32 , ok bool ) {
for d .nbits < n {
d .v <<= 8
if d .off >= len (d .buf ) {
return 0 , false
}
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 , true
}
func (d *decoder ) flushBits () {
d .v = 0
d .nbits = 0
}
func minInt(a , b int ) int {
if a <= b {
return a
}
return b
}
func (d *decoder ) decode (dst image .Image , xmin , ymin , xmax , ymax int ) error {
d .off = 0
if d .firstVal (tPredictor ) == prHorizontal {
switch d .bpp {
case 16 :
var off int
n := 2 * len (d .features [tBitsPerSample ])
for y := ymin ; y < ymax ; y ++ {
off += n
for x := 0 ; x < (xmax -xmin -1 )*n ; x += 2 {
if off +2 > len (d .buf ) {
return errNoPixels
}
v0 := d .byteOrder .Uint16 (d .buf [off -n : off -n +2 ])
v1 := d .byteOrder .Uint16 (d .buf [off : off +2 ])
d .byteOrder .PutUint16 (d .buf [off :off +2 ], v1 +v0 )
off += 2
}
}
case 8 :
var off int
n := 1 * len (d .features [tBitsPerSample ])
for y := ymin ; y < ymax ; y ++ {
off += n
for x := 0 ; x < (xmax -xmin -1 )*n ; x ++ {
if off >= len (d .buf ) {
return errNoPixels
}
d .buf [off ] += d .buf [off -n ]
off ++
}
}
case 1 :
return UnsupportedError ("horizontal predictor with 1 BitsPerSample" )
}
}
calcRowBytes := func (xmin , xmax , numSamples int , bitsPerSample uint ) int {
return int ((int64 (xmax -xmin )*int64 (numSamples )*int64 (bitsPerSample ) + 7 ) / 8 )
}
calcRowOff := func (y , ymin , rowBytes int ) int {
return (y - ymin ) * rowBytes
}
rMaxX := minInt (xmax , dst .Bounds ().Max .X )
rMaxY := minInt (ymax , dst .Bounds ().Max .Y )
switch d .mode {
case mGray , mGrayInvert :
rowBytes := calcRowBytes (xmin , xmax , 1 , d .bpp )
if d .bpp == 16 {
img := dst .(*image .Gray16 )
for y := ymin ; y < rMaxY ; y ++ {
d .off = calcRowOff (y , ymin , rowBytes )
for x := xmin ; x < rMaxX ; x ++ {
if d .off +2 > len (d .buf ) {
return errNoPixels
}
v := d .byteOrder .Uint16 (d .buf [d .off : d .off +2 ])
d .off += 2
if d .mode == mGrayInvert {
v = 0xffff - v
}
img .SetGray16 (x , y , color .Gray16 {v })
}
}
} else {
img := dst .(*image .Gray )
max := uint32 ((1 << d .bpp ) - 1 )
for y := ymin ; y < rMaxY ; y ++ {
d .off = calcRowOff (y , ymin , rowBytes )
d .flushBits ()
for x := xmin ; x < rMaxX ; x ++ {
v , ok := d .readBits (d .bpp )
if !ok {
return errNoPixels
}
v = v * 0xff / max
if d .mode == mGrayInvert {
v = 0xff - v
}
img .SetGray (x , y , color .Gray {uint8 (v )})
}
}
}
case mPaletted :
img := dst .(*image .Paletted )
pLen := len (d .palette )
rowBytes := calcRowBytes (xmin , xmax , 1 , d .bpp )
for y := ymin ; y < rMaxY ; y ++ {
d .off = calcRowOff (y , ymin , rowBytes )
d .flushBits ()
for x := xmin ; x < rMaxX ; x ++ {
v , ok := d .readBits (d .bpp )
if !ok {
return errNoPixels
}
idx := uint8 (v )
if int (idx ) >= pLen {
return errInvalidColorIndex
}
img .SetColorIndex (x , y , idx )
}
}
case mRGB :
rowBytes := calcRowBytes (xmin , xmax , 3 , d .bpp )
if d .bpp == 16 {
img := dst .(*image .RGBA64 )
for y := ymin ; y < rMaxY ; y ++ {
d .off = calcRowOff (y , ymin , rowBytes )
for x := xmin ; x < rMaxX ; x ++ {
if d .off +6 > len (d .buf ) {
return errNoPixels
}
r := d .byteOrder .Uint16 (d .buf [d .off +0 : d .off +2 ])
g := d .byteOrder .Uint16 (d .buf [d .off +2 : d .off +4 ])
b := d .byteOrder .Uint16 (d .buf [d .off +4 : d .off +6 ])
d .off += 6
img .SetRGBA64 (x , y , color .RGBA64 {r , g , b , 0xffff })
}
}
} else {
img := dst .(*image .RGBA )
for y := ymin ; y < rMaxY ; y ++ {
d .off = calcRowOff (y , ymin , rowBytes )
min := img .PixOffset (xmin , y )
max := img .PixOffset (rMaxX , y )
off := (y - ymin ) * (xmax - xmin ) * 3
for i := min ; i < max ; i += 4 {
if off +3 > len (d .buf ) {
return errNoPixels
}
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 :
rowBytes := calcRowBytes (xmin , xmax , 4 , d .bpp )
if d .bpp == 16 {
img := dst .(*image .NRGBA64 )
for y := ymin ; y < rMaxY ; y ++ {
d .off = calcRowOff (y , ymin , rowBytes )
for x := xmin ; x < rMaxX ; x ++ {
if d .off +8 > len (d .buf ) {
return errNoPixels
}
r := d .byteOrder .Uint16 (d .buf [d .off +0 : d .off +2 ])
g := d .byteOrder .Uint16 (d .buf [d .off +2 : d .off +4 ])
b := d .byteOrder .Uint16 (d .buf [d .off +4 : d .off +6 ])
a := d .byteOrder .Uint16 (d .buf [d .off +6 : d .off +8 ])
d .off += 8
img .SetNRGBA64 (x , y , color .NRGBA64 {r , g , b , a })
}
}
} else {
img := dst .(*image .NRGBA )
for y := ymin ; y < rMaxY ; y ++ {
min := img .PixOffset (xmin , y )
max := img .PixOffset (rMaxX , y )
i0 , i1 := (y -ymin )*(xmax -xmin )*4 , (y -ymin +1 )*(xmax -xmin )*4
if i1 > len (d .buf ) {
return errNoPixels
}
copy (img .Pix [min :max ], d .buf [i0 :i1 ])
}
}
case mRGBA :
rowBytes := calcRowBytes (xmin , xmax , 4 , d .bpp )
if d .bpp == 16 {
img := dst .(*image .RGBA64 )
for y := ymin ; y < rMaxY ; y ++ {
d .off = calcRowOff (y , ymin , rowBytes )
for x := xmin ; x < rMaxX ; x ++ {
if d .off +8 > len (d .buf ) {
return errNoPixels
}
r := d .byteOrder .Uint16 (d .buf [d .off +0 : d .off +2 ])
g := d .byteOrder .Uint16 (d .buf [d .off +2 : d .off +4 ])
b := d .byteOrder .Uint16 (d .buf [d .off +4 : d .off +6 ])
a := d .byteOrder .Uint16 (d .buf [d .off +6 : d .off +8 ])
d .off += 8
img .SetRGBA64 (x , y , color .RGBA64 {r , g , b , a })
}
}
} else {
img := dst .(*image .RGBA )
for y := ymin ; y < rMaxY ; y ++ {
min := img .PixOffset (xmin , y )
max := img .PixOffset (rMaxX , y )
i0 , i1 := (y -ymin )*(xmax -xmin )*4 , (y -ymin +1 )*(xmax -xmin )*4
if i1 > len (d .buf ) {
return errNoPixels
}
copy (img .Pix [min :max ], d .buf [i0 :i1 ])
}
}
}
return nil
}
const maxBytesPerPixel = 8
func newDecoder(r io .Reader ) (*decoder , error ) {
d := &decoder {
r : newReaderAt (r ),
features : make (map [int ][]uint ),
ifd : make (map [int ][ifdLen ]byte ),
}
p := make ([]byte , 8 )
if _ , err := d .r .ReadAt (p , 0 ); err != nil {
if err == io .EOF {
err = io .ErrUnexpectedEOF
}
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 ]))
if _ , err := d .r .ReadAt (p [0 :2 ], ifdOffset ); err != nil {
return nil , err
}
numItems := int (d .byteOrder .Uint16 (p [0 :2 ]))
var err error
p , err = safeReadAt (d .r , uint64 (ifdLen *numItems ), ifdOffset +2 )
if err != nil {
return nil , err
}
prevTag := -1
for i := 0 ; i < len (p ); i += ifdLen {
tag , err := d .parseIFD (p [i : i +ifdLen ])
if err != nil {
return nil , err
}
if tag <= prevTag {
return nil , FormatError ("tags are not sorted in ascending order" )
}
prevTag = tag
}
if d .config .Width , err = d .firstIntVal (tImageWidth ); err != nil {
return nil , err
}
if d .config .Height , err = d .firstIntVal (tImageLength ); err != nil {
return nil , err
}
if d .config .Width == 0 || d .config .Height == 0 {
return nil , FormatError ("zero-size image" )
}
if _ , ok := safemath .Mul3 (d .config .Width , d .config .Height , maxBytesPerPixel ); !ok {
return nil , FormatError ("image too large" )
}
if _ , ok := d .features [tBitsPerSample ]; !ok {
d .features [tBitsPerSample ] = []uint {1 }
}
d .bpp = d .firstVal (tBitsPerSample )
switch d .bpp {
case 0 :
return nil , FormatError ("BitsPerSample must not be 0" )
case 1 , 8 , 16 :
default :
return nil , UnsupportedError (fmt .Sprintf ("BitsPerSample of %v" , d .bpp ))
}
switch d .firstVal (tPhotometricInterpretation ) {
case pRGB :
if d .bpp == 16 {
for _ , b := range d .features [tBitsPerSample ] {
if b != 16 {
return nil , FormatError ("wrong number of samples for 16bit RGB" )
}
}
} else {
for _ , b := range d .features [tBitsPerSample ] {
if b != 8 {
return nil , FormatError ("wrong number of samples for 8bit RGB" )
}
}
}
switch len (d .features [tBitsPerSample ]) {
case 3 :
d .mode = mRGB
if d .bpp == 16 {
d .config .ColorModel = color .RGBA64Model
} else {
d .config .ColorModel = color .RGBAModel
}
case 4 :
switch d .firstVal (tExtraSamples ) {
case 1 :
d .mode = mRGBA
if d .bpp == 16 {
d .config .ColorModel = color .RGBA64Model
} else {
d .config .ColorModel = color .RGBAModel
}
case 2 :
d .mode = mNRGBA
if d .bpp == 16 {
d .config .ColorModel = color .NRGBA64Model
} else {
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
if d .bpp == 16 {
d .config .ColorModel = color .Gray16Model
} else {
d .config .ColorModel = color .GrayModel
}
case pBlackIsZero :
d .mode = mGray
if d .bpp == 16 {
d .config .ColorModel = color .Gray16Model
} else {
d .config .ColorModel = color .GrayModel
}
default :
return nil , UnsupportedError ("color model" )
}
if d .firstVal (tPhotometricInterpretation ) != pRGB {
if len (d .features [tBitsPerSample ]) != 1 {
return nil , UnsupportedError ("extra samples" )
}
}
return d , nil
}
func DecodeConfig (r io .Reader ) (image .Config , error ) {
d , err := newDecoder (r )
if err != nil {
return image .Config {}, err
}
return d .config , nil
}
func ccittFillOrder(tiffFillOrder uint ) ccitt .Order {
if tiffFillOrder == 2 {
return ccitt .LSB
}
return ccitt .MSB
}
func Decode (r io .Reader ) (img image .Image , err error ) {
d , err := newDecoder (r )
if err != nil {
return
}
blockPadding := false
blockWidth := d .config .Width
blockHeight := d .config .Height
blocksAcross := 1
blocksDown := 1
if d .config .Width == 0 {
blocksAcross = 0
}
if d .config .Height == 0 {
blocksDown = 0
}
var blockOffsets , blockCounts []uint
if d .firstVal (tTileWidth ) != 0 {
blockPadding = true
if blockWidth , err = d .firstIntVal (tTileWidth ); err != nil {
return nil , err
}
if blockHeight , err = d .firstIntVal (tTileLength ); err != nil {
return nil , err
}
if blockWidth < 8 || blockHeight < 8 {
return nil , FormatError ("tile size is too small" )
}
if _ , ok := safemath .Mul3 (blockWidth , blockHeight , maxBytesPerPixel ); !ok {
return nil , FormatError ("tile size is too large" )
}
if blockWidth -d .config .Width > 16 || blockHeight -d .config .Height > 16 {
if blockWidth > 1024 || blockHeight > 1024 {
return nil , FormatError ("tile size exceeds image size" )
}
}
if blockWidth != 0 {
blocksAcross = (d .config .Width + blockWidth - 1 ) / blockWidth
}
if blockHeight != 0 {
blocksDown = (d .config .Height + blockHeight - 1 ) / blockHeight
}
blockOffsets , err = d .parseIFDOffsets (tTileOffsets , blocksAcross *blocksDown )
if err != nil {
return nil , err
}
blockCounts , err = d .parseIFDOffsets (tTileByteCounts , blocksAcross *blocksDown )
if err != nil {
return nil , err
}
} else {
if v := d .firstVal (tRowsPerStrip ); v > 0 && v < uint (blockHeight ) {
blockHeight = int (v )
}
if blockHeight != 0 {
blocksDown = (d .config .Height + blockHeight - 1 ) / blockHeight
}
blockOffsets , err = d .parseIFDOffsets (tStripOffsets , blocksDown )
if err != nil {
return nil , err
}
blockCounts , err = d .parseIFDOffsets (tStripByteCounts , blocksDown )
if err != nil {
return nil , err
}
}
if n := blocksAcross * blocksDown ; len (blockOffsets ) < n || len (blockCounts ) < n {
return nil , FormatError ("inconsistent header" )
}
imgRect := image .Rect (0 , 0 , d .config .Width , d .config .Height )
switch d .mode {
case mGray , mGrayInvert :
if d .bpp == 16 {
img = image .NewGray16 (imgRect )
} else {
img = image .NewGray (imgRect )
}
case mPaletted :
img = image .NewPaletted (imgRect , d .palette )
case mNRGBA :
if d .bpp == 16 {
img = image .NewNRGBA64 (imgRect )
} else {
img = image .NewNRGBA (imgRect )
}
case mRGB , mRGBA :
if d .bpp == 16 {
img = image .NewRGBA64 (imgRect )
} else {
img = image .NewRGBA (imgRect )
}
}
if blocksAcross == 0 || blocksDown == 0 {
return
}
blockMaxDataSize := int64 (blockWidth ) * int64 (blockHeight ) * 8
for i := 0 ; i < blocksAcross ; i ++ {
blkW := blockWidth
if !blockPadding && i == blocksAcross -1 && d .config .Width %blockWidth != 0 {
blkW = d .config .Width % blockWidth
}
for j := 0 ; j < blocksDown ; j ++ {
blkH := blockHeight
if !blockPadding && j == blocksDown -1 && d .config .Height %blockHeight != 0 {
blkH = d .config .Height % blockHeight
}
offset := int64 (blockOffsets [j *blocksAcross +i ])
n := int64 (blockCounts [j *blocksAcross +i ])
switch d .firstVal (tCompression ) {
case cNone , 0 :
if n > blockMaxDataSize {
return nil , FormatError ("block data size too large" )
}
if b , ok := d .r .(*buffer ); ok {
d .buf , err = b .Slice (offset , n )
} else {
d .buf , err = safeReadAt (d .r , uint64 (n ), offset )
}
case cG3 :
inv := d .firstVal (tPhotometricInterpretation ) == pWhiteIsZero
order := ccittFillOrder (d .firstVal (tFillOrder ))
r := ccitt .NewReader (io .NewSectionReader (d .r , offset , n ), order , ccitt .Group3 , blkW , blkH , &ccitt .Options {Invert : inv , Align : false })
d .buf , err = readBuf (r , d .buf , blockMaxDataSize )
case cG4 :
inv := d .firstVal (tPhotometricInterpretation ) == pWhiteIsZero
order := ccittFillOrder (d .firstVal (tFillOrder ))
r := ccitt .NewReader (io .NewSectionReader (d .r , offset , n ), order , ccitt .Group4 , blkW , blkH , &ccitt .Options {Invert : inv , Align : false })
d .buf , err = readBuf (r , d .buf , blockMaxDataSize )
case cLZW :
r := lzw .NewReader (io .NewSectionReader (d .r , offset , n ), lzw .MSB , 8 )
d .buf , err = readBuf (r , d .buf , blockMaxDataSize )
r .Close ()
case cDeflate , cDeflateOld :
var r io .ReadCloser
r , err = zlib .NewReader (io .NewSectionReader (d .r , offset , n ))
if err != nil {
return nil , err
}
d .buf , err = readBuf (r , d .buf , blockMaxDataSize )
r .Close ()
case cPackBits :
d .buf , err = unpackBits (io .NewSectionReader (d .r , offset , n ), blockMaxDataSize )
default :
err = UnsupportedError (fmt .Sprintf ("compression value %d" , d .firstVal (tCompression )))
}
if err != nil {
return nil , err
}
xmin := i * blockWidth
ymin := j * blockHeight
xmax := xmin + blkW
ymax := ymin + blkH
err = d .decode (img , xmin , ymin , xmax , ymax )
if err != nil {
return nil , err
}
}
}
return
}
func readBuf(r io .Reader , buf []byte , lim int64 ) ([]byte , error ) {
b := bytes .NewBuffer (buf [:0 ])
_ , err := b .ReadFrom (io .LimitReader (r , lim ))
return b .Bytes (), err
}
func init() {
image .RegisterFormat ("tiff" , leHeader , Decode , DecodeConfig )
image .RegisterFormat ("tiff" , beHeader , Decode , DecodeConfig )
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .