package tiff
import (
"bytes"
"compress/zlib"
"encoding/binary"
"errors"
"image"
"io"
"sort"
)
var enc = binary .LittleEndian
type ifdEntry struct {
tag int
datatype int
data []uint32
}
func (e ifdEntry ) putData (p []byte ) {
for _ , d := range e .data {
switch e .datatype {
case dtByte , dtASCII :
p [0 ] = byte (d )
p = p [1 :]
case dtShort :
enc .PutUint16 (p , uint16 (d ))
p = p [2 :]
case dtLong , dtRational :
enc .PutUint32 (p , uint32 (d ))
p = p [4 :]
}
}
}
type byTag []ifdEntry
func (d byTag ) Len () int { return len (d ) }
func (d byTag ) Less (i , j int ) bool { return d [i ].tag < d [j ].tag }
func (d byTag ) Swap (i , j int ) { d [i ], d [j ] = d [j ], d [i ] }
func encodeGray(w io .Writer , pix []uint8 , dx , dy , stride int , predictor bool ) error {
if !predictor {
return writePix (w , pix , dy , dx , stride )
}
buf := make ([]byte , dx )
for y := 0 ; y < dy ; y ++ {
min := y *stride + 0
max := y *stride + dx
off := 0
var v0 uint8
for i := min ; i < max ; i ++ {
v1 := pix [i ]
buf [off ] = v1 - v0
v0 = v1
off ++
}
if _ , err := w .Write (buf ); err != nil {
return err
}
}
return nil
}
func encodeGray16(w io .Writer , pix []uint8 , dx , dy , stride int , predictor bool ) error {
buf := make ([]byte , dx *2 )
for y := 0 ; y < dy ; y ++ {
min := y *stride + 0
max := y *stride + dx *2
off := 0
var v0 uint16
for i := min ; i < max ; i += 2 {
v1 := uint16 (pix [i ])<<8 | uint16 (pix [i +1 ])
if predictor {
v0 , v1 = v1 , v1 -v0
}
buf [off +0 ] = byte (v1 )
buf [off +1 ] = byte (v1 >> 8 )
off += 2
}
if _ , err := w .Write (buf ); err != nil {
return err
}
}
return nil
}
func encodeRGBA(w io .Writer , pix []uint8 , dx , dy , stride int , predictor bool ) error {
if !predictor {
return writePix (w , pix , dy , dx *4 , stride )
}
buf := make ([]byte , dx *4 )
for y := 0 ; y < dy ; y ++ {
min := y *stride + 0
max := y *stride + dx *4
off := 0
var r0 , g0 , b0 , a0 uint8
for i := min ; i < max ; i += 4 {
r1 , g1 , b1 , a1 := pix [i +0 ], pix [i +1 ], pix [i +2 ], pix [i +3 ]
buf [off +0 ] = r1 - r0
buf [off +1 ] = g1 - g0
buf [off +2 ] = b1 - b0
buf [off +3 ] = a1 - a0
off += 4
r0 , g0 , b0 , a0 = r1 , g1 , b1 , a1
}
if _ , err := w .Write (buf ); err != nil {
return err
}
}
return nil
}
func encodeRGBA64(w io .Writer , pix []uint8 , dx , dy , stride int , predictor bool ) error {
buf := make ([]byte , dx *8 )
for y := 0 ; y < dy ; y ++ {
min := y *stride + 0
max := y *stride + dx *8
off := 0
var r0 , g0 , b0 , a0 uint16
for i := min ; i < max ; i += 8 {
r1 := uint16 (pix [i +0 ])<<8 | uint16 (pix [i +1 ])
g1 := uint16 (pix [i +2 ])<<8 | uint16 (pix [i +3 ])
b1 := uint16 (pix [i +4 ])<<8 | uint16 (pix [i +5 ])
a1 := uint16 (pix [i +6 ])<<8 | uint16 (pix [i +7 ])
if predictor {
r0 , r1 = r1 , r1 -r0
g0 , g1 = g1 , g1 -g0
b0 , b1 = b1 , b1 -b0
a0 , a1 = a1 , a1 -a0
}
buf [off +0 ] = byte (r1 )
buf [off +1 ] = byte (r1 >> 8 )
buf [off +2 ] = byte (g1 )
buf [off +3 ] = byte (g1 >> 8 )
buf [off +4 ] = byte (b1 )
buf [off +5 ] = byte (b1 >> 8 )
buf [off +6 ] = byte (a1 )
buf [off +7 ] = byte (a1 >> 8 )
off += 8
}
if _ , err := w .Write (buf ); err != nil {
return err
}
}
return nil
}
func encode(w io .Writer , m image .Image , predictor bool ) error {
bounds := m .Bounds ()
buf := make ([]byte , 4 *bounds .Dx ())
for y := bounds .Min .Y ; y < bounds .Max .Y ; y ++ {
off := 0
if predictor {
var r0 , g0 , b0 , a0 uint8
for x := bounds .Min .X ; x < bounds .Max .X ; x ++ {
r , g , b , a := m .At (x , y ).RGBA ()
r1 := uint8 (r >> 8 )
g1 := uint8 (g >> 8 )
b1 := uint8 (b >> 8 )
a1 := uint8 (a >> 8 )
buf [off +0 ] = r1 - r0
buf [off +1 ] = g1 - g0
buf [off +2 ] = b1 - b0
buf [off +3 ] = a1 - a0
off += 4
r0 , g0 , b0 , a0 = r1 , g1 , b1 , a1
}
} else {
for x := bounds .Min .X ; x < bounds .Max .X ; x ++ {
r , g , b , a := m .At (x , y ).RGBA ()
buf [off +0 ] = uint8 (r >> 8 )
buf [off +1 ] = uint8 (g >> 8 )
buf [off +2 ] = uint8 (b >> 8 )
buf [off +3 ] = uint8 (a >> 8 )
off += 4
}
}
if _ , err := w .Write (buf ); err != nil {
return err
}
}
return nil
}
func writePix(w io .Writer , pix []byte , nrows , length , stride int ) error {
if length == stride {
_ , err := w .Write (pix [:nrows *length ])
return err
}
for ; nrows > 0 ; nrows -- {
if _ , err := w .Write (pix [:length ]); err != nil {
return err
}
pix = pix [stride :]
}
return nil
}
func writeIFD(w io .Writer , ifdOffset int , d []ifdEntry ) error {
var buf [ifdLen ]byte
parea := make ([]byte , 1024 )
pstart := ifdOffset + ifdLen *len (d ) + 6
var o int
sort .Sort (byTag (d ))
if err := binary .Write (w , enc , uint16 (len (d ))); err != nil {
return err
}
for _ , ent := range d {
enc .PutUint16 (buf [0 :2 ], uint16 (ent .tag ))
enc .PutUint16 (buf [2 :4 ], uint16 (ent .datatype ))
count := uint32 (len (ent .data ))
if ent .datatype == dtRational {
count /= 2
}
enc .PutUint32 (buf [4 :8 ], count )
datalen := int (count * lengths [ent .datatype ])
if datalen <= 4 {
ent .putData (buf [8 :12 ])
} else {
if (o + datalen ) > len (parea ) {
newlen := len (parea ) + 1024
for (o + datalen ) > newlen {
newlen += 1024
}
newarea := make ([]byte , newlen )
copy (newarea , parea )
parea = newarea
}
ent .putData (parea [o : o +datalen ])
enc .PutUint32 (buf [8 :12 ], uint32 (pstart +o ))
o += datalen
}
if _ , err := w .Write (buf [:]); err != nil {
return err
}
}
if err := binary .Write (w , enc , uint32 (0 )); err != nil {
return err
}
_ , err := w .Write (parea [:o ])
return err
}
type Options struct {
Compression CompressionType
Predictor bool
}
func Encode (w io .Writer , m image .Image , opt *Options ) error {
d := m .Bounds ().Size ()
if d .X == 0 || d .Y == 0 {
return errors .New ("tiff: zero-size image" )
}
compression := uint32 (cNone )
predictor := false
if opt != nil {
compression = opt .Compression .specValue ()
predictor = opt .Predictor && compression == cLZW
}
_ , err := io .WriteString (w , leHeader )
if err != nil {
return err
}
var buf bytes .Buffer
var dst io .Writer
var imageLen int
switch compression {
case cNone :
dst = w
switch m .(type ) {
case *image .Paletted :
imageLen = d .X * d .Y * 1
case *image .Gray :
imageLen = d .X * d .Y * 1
case *image .Gray16 :
imageLen = d .X * d .Y * 2
case *image .RGBA64 :
imageLen = d .X * d .Y * 8
case *image .NRGBA64 :
imageLen = d .X * d .Y * 8
default :
imageLen = d .X * d .Y * 4
}
err = binary .Write (w , enc , uint32 (imageLen +8 ))
if err != nil {
return err
}
case cDeflate :
dst = zlib .NewWriter (&buf )
default :
return errors .New ("tiff: unsupported compression" )
}
pr := uint32 (prNone )
photometricInterpretation := uint32 (pRGB )
samplesPerPixel := uint32 (4 )
bitsPerSample := []uint32 {8 , 8 , 8 , 8 }
extraSamples := uint32 (0 )
colorMap := []uint32 {}
if predictor {
pr = prHorizontal
}
switch m := m .(type ) {
case *image .Paletted :
photometricInterpretation = pPaletted
samplesPerPixel = 1
bitsPerSample = []uint32 {8 }
colorMap = make ([]uint32 , 256 *3 )
for i := 0 ; i < 256 && i < len (m .Palette ); i ++ {
r , g , b , _ := m .Palette [i ].RGBA ()
colorMap [i +0 *256 ] = uint32 (r )
colorMap [i +1 *256 ] = uint32 (g )
colorMap [i +2 *256 ] = uint32 (b )
}
err = encodeGray (dst , m .Pix , d .X , d .Y , m .Stride , predictor )
case *image .Gray :
photometricInterpretation = pBlackIsZero
samplesPerPixel = 1
bitsPerSample = []uint32 {8 }
err = encodeGray (dst , m .Pix , d .X , d .Y , m .Stride , predictor )
case *image .Gray16 :
photometricInterpretation = pBlackIsZero
samplesPerPixel = 1
bitsPerSample = []uint32 {16 }
err = encodeGray16 (dst , m .Pix , d .X , d .Y , m .Stride , predictor )
case *image .NRGBA :
extraSamples = 2
err = encodeRGBA (dst , m .Pix , d .X , d .Y , m .Stride , predictor )
case *image .NRGBA64 :
extraSamples = 2
bitsPerSample = []uint32 {16 , 16 , 16 , 16 }
err = encodeRGBA64 (dst , m .Pix , d .X , d .Y , m .Stride , predictor )
case *image .RGBA :
extraSamples = 1
err = encodeRGBA (dst , m .Pix , d .X , d .Y , m .Stride , predictor )
case *image .RGBA64 :
extraSamples = 1
bitsPerSample = []uint32 {16 , 16 , 16 , 16 }
err = encodeRGBA64 (dst , m .Pix , d .X , d .Y , m .Stride , predictor )
default :
extraSamples = 1
err = encode (dst , m , predictor )
}
if err != nil {
return err
}
if compression != cNone {
if err = dst .(io .Closer ).Close (); err != nil {
return err
}
imageLen = buf .Len ()
if err = binary .Write (w , enc , uint32 (imageLen +8 )); err != nil {
return err
}
if _, err = buf .WriteTo (w ); err != nil {
return err
}
}
ifd := []ifdEntry {
{tImageWidth , dtShort , []uint32 {uint32 (d .X )}},
{tImageLength , dtShort , []uint32 {uint32 (d .Y )}},
{tBitsPerSample , dtShort , bitsPerSample },
{tCompression , dtShort , []uint32 {compression }},
{tPhotometricInterpretation , dtShort , []uint32 {photometricInterpretation }},
{tStripOffsets , dtLong , []uint32 {8 }},
{tSamplesPerPixel , dtShort , []uint32 {samplesPerPixel }},
{tRowsPerStrip , dtShort , []uint32 {uint32 (d .Y )}},
{tStripByteCounts , dtLong , []uint32 {uint32 (imageLen )}},
{tXResolution , dtRational , []uint32 {72 , 1 }},
{tYResolution , dtRational , []uint32 {72 , 1 }},
{tResolutionUnit , dtShort , []uint32 {resPerInch }},
}
if pr != prNone {
ifd = append (ifd , ifdEntry {tPredictor , dtShort , []uint32 {pr }})
}
if len (colorMap ) != 0 {
ifd = append (ifd , ifdEntry {tColorMap , dtShort , colorMap })
}
if extraSamples > 0 {
ifd = append (ifd , ifdEntry {tExtraSamples , dtShort , []uint32 {extraSamples }})
}
return writeIFD (w , imageLen +8 , ifd )
}
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 .