001/*
002 * $Id: TiffWriter.java 4784 2011-03-15 08:33:00Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf.codec;
045
046import java.io.OutputStream;
047import java.io.IOException;
048import java.util.TreeMap;
049/**
050 * Exports images as TIFF.
051 * @since 5.0.3
052 */
053public class TiffWriter {
054    private TreeMap<Integer,FieldBase> ifd = new TreeMap<Integer,FieldBase>();
055
056    public void addField(FieldBase field) {
057        ifd.put(Integer.valueOf(field.getTag()), field);
058    }
059
060    public int getIfdSize() {
061        return 6 + ifd.size() * 12;
062    }
063
064    public void writeFile(OutputStream stream) throws IOException {
065        stream.write(0x4d);
066        stream.write(0x4d);
067        stream.write(0);
068        stream.write(42);
069        writeLong(8, stream);
070        writeShort(ifd.size(), stream);
071        int offset = 8 + getIfdSize();
072        for (FieldBase field : ifd.values()) {
073            int size = field.getValueSize();
074            if (size > 4) {
075                field.setOffset(offset);
076                offset += size;
077            }
078            field.writeField(stream);
079        }
080        writeLong(0, stream);
081        for (FieldBase field : ifd.values()) {
082            field.writeValue(stream);
083        }
084    }
085
086    /**
087     * Inner class class containing information about a field.
088     * @since 5.0.3
089     */
090    public abstract static class FieldBase {
091        private int tag;
092        private int fieldType;
093        private int count;
094        protected byte[] data;
095        private int offset;
096
097        protected FieldBase(int tag, int fieldType, int count) {
098            this.tag = tag;
099            this.fieldType = fieldType;
100            this.count = count;
101        }
102
103        public int getValueSize() {
104            return (data.length + 1) & 0xfffffffe;
105        }
106
107        public int getTag() {
108            return tag;
109        }
110
111        public void setOffset(int offset) {
112            this.offset = offset;
113        }
114
115        public void writeField(OutputStream stream) throws IOException {
116            writeShort(tag, stream);
117            writeShort(fieldType, stream);
118            writeLong(count, stream);
119            if (data.length <= 4) {
120                stream.write(data);
121                for (int k = data.length; k < 4; ++k) {
122                    stream.write(0);
123                }
124            }
125            else {
126                writeLong(offset, stream);
127            }
128        }
129
130        public void writeValue(OutputStream stream) throws IOException {
131            if (data.length <= 4)
132                return;
133            stream.write(data);
134            if ((data.length & 1) == 1)
135                stream.write(0);
136        }
137    }
138
139    /**
140     * Inner class containing info about a field.
141     * @since 5.0.3
142     */
143    public static class FieldShort extends FieldBase {
144        public FieldShort(int tag, int value) {
145            super(tag, 3, 1);
146            data = new byte[2];
147            data[0] = (byte)(value >> 8);
148            data[1] = (byte)value;
149        }
150
151        public FieldShort(int tag, int[] values) {
152            super(tag, 3, values.length);
153            data = new byte[values.length * 2];
154            int ptr = 0;
155            for (int value : values) {
156                data[ptr++] = (byte)(value >> 8);
157                data[ptr++] = (byte)value;
158            }
159        }
160    }
161
162    /**
163     * Inner class containing info about a field.
164     * @since 5.0.3
165     */
166    public static class FieldLong extends FieldBase {
167        public FieldLong(int tag, int value) {
168            super(tag, 4, 1);
169            data = new byte[4];
170            data[0] = (byte)(value >> 24);
171            data[1] = (byte)(value >> 16);
172            data[2] = (byte)(value >> 8);
173            data[3] = (byte)value;
174        }
175
176        public FieldLong(int tag, int[] values) {
177            super(tag, 4, values.length);
178            data = new byte[values.length * 4];
179            int ptr = 0;
180            for (int value : values) {
181                data[ptr++] = (byte)(value >> 24);
182                data[ptr++] = (byte)(value >> 16);
183                data[ptr++] = (byte)(value >> 8);
184                data[ptr++] = (byte)value;
185            }
186        }
187    }
188
189    /**
190     * Inner class containing info about a field.
191     * @since 5.0.3
192     */
193    public static class FieldRational extends FieldBase {
194        public FieldRational(int tag, int[] value) {
195            this(tag, new int[][]{value});
196        }
197
198        public FieldRational(int tag, int[][] values) {
199            super(tag, 5, values.length);
200            data = new byte[values.length * 8];
201            int ptr = 0;
202            for (int value[] : values) {
203                data[ptr++] = (byte)(value[0] >> 24);
204                data[ptr++] = (byte)(value[0] >> 16);
205                data[ptr++] = (byte)(value[0] >> 8);
206                data[ptr++] = (byte)value[0];
207                data[ptr++] = (byte)(value[1] >> 24);
208                data[ptr++] = (byte)(value[1] >> 16);
209                data[ptr++] = (byte)(value[1] >> 8);
210                data[ptr++] = (byte)value[1];
211            }
212        }
213    }
214
215    /**
216     * Inner class containing info about a field.
217     * @since 5.0.3
218     */
219    public static class FieldByte extends FieldBase {
220        public FieldByte(int tag, byte[] values) {
221            super(tag, 1, values.length);
222            data = values;
223        }
224    }
225
226    /**
227     * Inner class containing info about a field.
228     * @since 5.0.3
229     */
230    public static class FieldUndefined extends FieldBase {
231        public FieldUndefined(int tag, byte[] values) {
232            super(tag, 7, values.length);
233            data = values;
234        }
235    }
236
237    /**
238     * Inner class containing info about a field.
239     * @since 5.0.3
240     */
241    public static class FieldImage extends FieldBase {
242        public FieldImage(byte[] values) {
243            super(TIFFConstants.TIFFTAG_STRIPOFFSETS, 4, 1);
244            data = values;
245        }
246    }
247
248    /**
249     * Inner class containing info about an ASCII field.
250     * @since 5.0.3
251     */
252    public static class FieldAscii extends FieldBase {
253        public FieldAscii(int tag, String values) {
254            super(tag, 2, values.getBytes().length + 1);
255            byte[] b = values.getBytes();
256            data = new byte[b.length + 1];
257            System.arraycopy(b, 0, data, 0, b.length);
258        }
259    }
260
261    public static void writeShort(int v, OutputStream stream) throws IOException {
262        stream.write((v >> 8) & 0xff);
263        stream.write(v & 0xff);
264    }
265
266    public static void writeLong(int v, OutputStream stream) throws IOException {
267        stream.write((v >> 24) & 0xff);
268        stream.write((v >> 16) & 0xff);
269        stream.write((v >> 8) & 0xff);
270        stream.write(v & 0xff);
271    }
272
273    public static void compressLZW(OutputStream stream, int predictor, byte[] b, int height, int samplesPerPixel, int stride) throws IOException {
274
275        LZWCompressor lzwCompressor = new LZWCompressor(stream, 8, true);
276        boolean usePredictor = predictor == TIFFConstants.PREDICTOR_HORIZONTAL_DIFFERENCING;
277
278        if (!usePredictor) {
279            lzwCompressor.compress(b, 0, b.length);
280        } else {
281            int off = 0;
282            byte[] rowBuf = usePredictor ? new byte[stride] : null;
283            for (int i = 0; i < height; i++) {
284                System.arraycopy(b, off, rowBuf, 0, stride);
285                for (int j = stride - 1; j >= samplesPerPixel; j--) {
286                    rowBuf[j] -= rowBuf[j - samplesPerPixel];
287                }
288                lzwCompressor.compress(rowBuf, 0, stride);
289                off += stride;
290            }
291        }
292
293        lzwCompressor.flush();
294    }
295}