| // Copyright 2012 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 ( |
| "encoding/binary" |
| "image" |
| "io" |
| "sort" |
| ) |
| |
| // The TIFF format allows to choose the order of the different elements freely. |
| // The basic structure of a TIFF file written by this package is: |
| // |
| // 1. Header (8 bytes). |
| // 2. Image data. |
| // 3. Image File Directory (IFD). |
| // 4. "Pointer area" for larger entries in the IFD. |
| |
| // We only write little-endian TIFF files. |
| var enc = binary.LittleEndian |
| |
| // An ifdEntry is a single entry in an Image File Directory. |
| // A value of type dtRational is composed of two 32-bit values, |
| // thus data contains two uints (numerator and denominator) for a single number. |
| 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 ifd []ifdEntry |
| |
| func (d ifd) Len() int { |
| return len(d) |
| } |
| |
| func (d ifd) Less(i, j int) bool { |
| return d[i].tag < d[j].tag |
| } |
| |
| func (d ifd) Swap(i, j int) { |
| d[i], d[j] = d[j], d[i] |
| } |
| |
| type encoder struct { |
| ifd ifd |
| img image.Image |
| imageLen int // Length of the image in bytes. |
| } |
| |
| func newEncoder(m image.Image) *encoder { |
| width := m.Bounds().Dx() |
| height := m.Bounds().Dy() |
| imageLen := width * height * 4 |
| return &encoder{ |
| img: m, |
| // For uncompressed images, imageLen is known in advance. |
| // For compressed images, we would need to write the image |
| // data in a buffer here to get its length. |
| imageLen: imageLen, |
| ifd: ifd{ |
| {tImageWidth, dtShort, []uint32{uint32(width)}}, |
| {tImageLength, dtShort, []uint32{uint32(height)}}, |
| {tBitsPerSample, dtShort, []uint32{8, 8, 8, 8}}, |
| {tCompression, dtShort, []uint32{cNone}}, |
| {tPhotometricInterpretation, dtShort, []uint32{pRGB}}, |
| {tStripOffsets, dtLong, []uint32{8}}, |
| {tSamplesPerPixel, dtShort, []uint32{4}}, |
| {tRowsPerStrip, dtShort, []uint32{uint32(height)}}, |
| {tStripByteCounts, dtLong, []uint32{uint32(imageLen)}}, |
| // There is currently no support for storing the image |
| // resolution, so give a bogus value of 72x72 dpi. |
| {tXResolution, dtRational, []uint32{72, 1}}, |
| {tYResolution, dtRational, []uint32{72, 1}}, |
| {tResolutionUnit, dtShort, []uint32{resPerInch}}, |
| {tExtraSamples, dtShort, []uint32{1}}, // RGBA. |
| }, |
| } |
| } |
| |
| func (e *encoder) writeImgData(w io.Writer) error { |
| b := e.img.Bounds() |
| buf := make([]byte, 4*b.Dx()) |
| for y := b.Min.Y; y < b.Max.Y; y++ { |
| i := 0 |
| for x := b.Min.X; x < b.Max.X; x++ { |
| r, g, b, a := e.img.At(x, y).RGBA() |
| buf[i+0] = uint8(r >> 8) |
| buf[i+1] = uint8(g >> 8) |
| buf[i+2] = uint8(b >> 8) |
| buf[i+3] = uint8(a >> 8) |
| i += 4 |
| } |
| if _, err := w.Write(buf); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func (e *encoder) writeIFD(w io.Writer) error { |
| var buf [ifdLen]byte |
| // Make space for "pointer area" containing IFD entry data |
| // longer than 4 bytes. |
| parea := make([]byte, 1024) |
| pstart := int(e.imageLen) + 8 + (ifdLen * len(e.ifd)) + 6 |
| var o int // Current offset in parea. |
| |
| // The IFD has to be written with the tags in ascending order. |
| sort.Sort(e.ifd) |
| |
| // Write the number of entries in this IFD. |
| if err := binary.Write(w, enc, uint16(len(e.ifd))); err != nil { |
| return err |
| } |
| for _, ent := range e.ifd { |
| 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 |
| } |
| } |
| // The IFD ends with the offset of the next IFD in the file, |
| // or zero if it is the last one (page 14). |
| if err := binary.Write(w, enc, uint32(0)); err != nil { |
| return err |
| } |
| _, err := w.Write(parea[:o]) |
| return err |
| } |
| |
| func (e *encoder) encode(w io.Writer) error { |
| _, err := io.WriteString(w, leHeader) |
| if err != nil { |
| return err |
| } |
| |
| ifdOffset := e.imageLen + 8 // 8 bytes for TIFF header. |
| err = binary.Write(w, enc, uint32(ifdOffset)) |
| if err != nil { |
| return err |
| } |
| err = e.writeImgData(w) |
| if err != nil { |
| return err |
| } |
| return e.writeIFD(w) |
| } |
| |
| // Encode writes the image m to w in uncompressed RGBA format. |
| func Encode(w io.Writer, m image.Image) error { |
| return newEncoder(m).encode(w) |
| } |