1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.imaging.formats.tiff.write;
18
19 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_HEADER_SIZE;
20
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.nio.ByteOrder;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31
32 import org.apache.commons.imaging.FormatCompliance;
33 import org.apache.commons.imaging.ImagingException;
34 import org.apache.commons.imaging.bytesource.ByteSource;
35 import org.apache.commons.imaging.common.Allocator;
36 import org.apache.commons.imaging.common.BinaryOutputStream;
37 import org.apache.commons.imaging.formats.tiff.AbstractTiffElement;
38 import org.apache.commons.imaging.formats.tiff.AbstractTiffElement.DataElement;
39 import org.apache.commons.imaging.formats.tiff.AbstractTiffImageData;
40 import org.apache.commons.imaging.formats.tiff.JpegImageData;
41 import org.apache.commons.imaging.formats.tiff.TiffContents;
42 import org.apache.commons.imaging.formats.tiff.TiffDirectory;
43 import org.apache.commons.imaging.formats.tiff.TiffField;
44 import org.apache.commons.imaging.formats.tiff.TiffImagingParameters;
45 import org.apache.commons.imaging.formats.tiff.TiffReader;
46 import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants;
47
48
49
50
51 public class TiffImageWriterLossless extends AbstractTiffImageWriter {
52 private static final class BufferOutputStream extends OutputStream {
53 private final byte[] buffer;
54 private int index;
55
56 BufferOutputStream(final byte[] buffer, final int index) {
57 this.buffer = buffer;
58 this.index = index;
59 }
60
61 @Override
62 public void write(final byte[] b, final int off, final int len) throws IOException {
63 if (index + len > buffer.length) {
64 throw new ImagingException("Buffer overflow.");
65 }
66 System.arraycopy(b, off, buffer, index, len);
67 index += len;
68 }
69
70 @Override
71 public void write(final int b) throws IOException {
72 if (index >= buffer.length) {
73 throw new ImagingException("Buffer overflow.");
74 }
75
76 buffer[index++] = (byte) b;
77 }
78 }
79
80 private static final Comparator<AbstractTiffElement> ELEMENT_SIZE_COMPARATOR = Comparator.comparingInt(e -> e.length);
81 private static final Comparator<AbstractTiffOutputItem> ITEM_SIZE_COMPARATOR = Comparator.comparingInt(AbstractTiffOutputItem::getItemLength);
82
83 private final byte[] exifBytes;
84
85 public TiffImageWriterLossless(final byte[] exifBytes) {
86 this.exifBytes = exifBytes;
87 }
88
89 public TiffImageWriterLossless(final ByteOrder byteOrder, final byte[] exifBytes) {
90 super(byteOrder);
91 this.exifBytes = exifBytes;
92 }
93
94 private List<AbstractTiffElement> analyzeOldTiff(final Map<Integer, TiffOutputField> frozenFields) throws ImagingException, IOException {
95 try {
96 final ByteSource byteSource = ByteSource.array(exifBytes);
97 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
98 final TiffContents contents = new TiffReader(false).readContents(byteSource, new TiffImagingParameters(), formatCompliance);
99
100 final List<AbstractTiffElement> elements = new ArrayList<>();
101
102 final List<TiffDirectory> directories = contents.directories;
103 for (final TiffDirectory directory : directories) {
104 elements.add(directory);
105
106 for (final TiffField field : directory.getDirectoryEntries()) {
107 final AbstractTiffElement oversizeValue = field.getOversizeValueElement();
108 if (oversizeValue != null) {
109 final TiffOutputField frozenField = frozenFields.get(field.getTag());
110 if (frozenField != null && frozenField.getSeperateValue() != null && Arrays.equals(frozenField.getData(), field.getByteArrayValue())) {
111 frozenField.getSeperateValue().setOffset(field.getOffset());
112 } else {
113 elements.add(oversizeValue);
114 }
115 }
116 }
117
118 final JpegImageData jpegImageData = directory.getJpegImageData();
119 if (jpegImageData != null) {
120 elements.add(jpegImageData);
121 }
122
123 final AbstractTiffImageData abstractTiffImageData = directory.getTiffImageData();
124 if (abstractTiffImageData != null) {
125 final DataElement[] data = abstractTiffImageData.getImageData();
126 Collections.addAll(elements, data);
127 }
128 }
129
130 elements.sort(AbstractTiffElement.COMPARATOR);
131
132 final List<AbstractTiffElement> rewritableElements = new ArrayList<>();
133 final int tolerance = 3;
134 AbstractTiffElement start = null;
135 long index = -1;
136 for (final AbstractTiffElement element : elements) {
137 final long lastElementByte = element.offset + element.length;
138 if (start == null) {
139 start = element;
140 } else if (element.offset - index > tolerance) {
141 rewritableElements.add(new AbstractTiffElement.Stub(start.offset, (int) (index - start.offset)));
142 start = element;
143 }
144 index = lastElementByte;
145 }
146 if (null != start) {
147 rewritableElements.add(new AbstractTiffElement.Stub(start.offset, (int) (index - start.offset)));
148 }
149
150 return rewritableElements;
151 } catch (final ImagingException e) {
152 throw new ImagingException(e.getMessage(), e);
153 }
154 }
155
156 private long updateOffsetsStep(final List<AbstractTiffElement> analysis, final List<AbstractTiffOutputItem> outputItems) {
157
158 long overflowIndex = exifBytes.length;
159
160
161 final List<AbstractTiffElement> unusedElements = new ArrayList<>(analysis);
162
163
164 unusedElements.sort(AbstractTiffElement.COMPARATOR);
165 Collections.reverse(unusedElements);
166
167
168 while (!unusedElements.isEmpty()) {
169 final AbstractTiffElement element = unusedElements.get(0);
170 final long elementEnd = element.offset + element.length;
171 if (elementEnd != overflowIndex) {
172 break;
173 }
174
175 overflowIndex -= element.length;
176 unusedElements.remove(0);
177 }
178
179 unusedElements.sort(ELEMENT_SIZE_COMPARATOR);
180 Collections.reverse(unusedElements);
181
182
183 final List<AbstractTiffOutputItem> unplacedItems = new ArrayList<>(outputItems);
184 unplacedItems.sort(ITEM_SIZE_COMPARATOR);
185 Collections.reverse(unplacedItems);
186
187 while (!unplacedItems.isEmpty()) {
188
189 final AbstractTiffOutputItem outputItem = unplacedItems.remove(0);
190 final int outputItemLength = outputItem.getItemLength();
191
192
193 AbstractTiffElement bestFit = null;
194 for (final AbstractTiffElement element : unusedElements) {
195 if (element.length < outputItemLength) {
196 break;
197 }
198 bestFit = element;
199 }
200 if (null == bestFit) {
201
202 if ((overflowIndex & 1L) != 0) {
203 overflowIndex += 1;
204 }
205 outputItem.setOffset(overflowIndex);
206 overflowIndex += outputItemLength;
207 } else {
208 long offset = bestFit.offset;
209 int length = bestFit.length;
210 if ((offset & 1L) != 0) {
211
212 offset += 1;
213 length -= 1;
214 }
215 outputItem.setOffset(offset);
216 unusedElements.remove(bestFit);
217
218 if (length > outputItemLength) {
219
220 final long excessOffset = offset + outputItemLength;
221 final int excessLength = length - outputItemLength;
222 unusedElements.add(new AbstractTiffElement.Stub(excessOffset, excessLength));
223
224 unusedElements.sort(ELEMENT_SIZE_COMPARATOR);
225 Collections.reverse(unusedElements);
226 }
227 }
228 }
229
230 return overflowIndex;
231 }
232
233 @Override
234 public void write(final OutputStream os, final TiffOutputSet outputSet) throws IOException, ImagingException {
235
236
237 final Map<Integer, TiffOutputField> frozenFields = new HashMap<>();
238 final TiffOutputField makerNoteField = outputSet.findField(ExifTagConstants.EXIF_TAG_MAKER_NOTE);
239 if (makerNoteField != null && makerNoteField.getSeperateValue() != null) {
240 frozenFields.put(ExifTagConstants.EXIF_TAG_MAKER_NOTE.tag, makerNoteField);
241 }
242 final List<AbstractTiffElement> analysis = analyzeOldTiff(frozenFields);
243 final int oldLength = exifBytes.length;
244 if (analysis.isEmpty()) {
245 throw new ImagingException("Couldn't analyze old tiff data.");
246 }
247 if (analysis.size() == 1) {
248 final AbstractTiffElement onlyElement = analysis.get(0);
249 if (onlyElement.offset == TIFF_HEADER_SIZE && onlyElement.offset + onlyElement.length + TIFF_HEADER_SIZE == oldLength) {
250
251 new TiffImageWriterLossy(byteOrder).write(os, outputSet);
252 return;
253 }
254 }
255 final Map<Long, TiffOutputField> frozenFieldOffsets = new HashMap<>();
256 for (final Map.Entry<Integer, TiffOutputField> entry : frozenFields.entrySet()) {
257 final TiffOutputField frozenField = entry.getValue();
258 if (frozenField.getSeperateValue().getOffset() != AbstractTiffOutputItem.UNDEFINED_VALUE) {
259 frozenFieldOffsets.put(frozenField.getSeperateValue().getOffset(), frozenField);
260 }
261 }
262
263 final TiffOutputSummary outputSummary = validateDirectories(outputSet);
264
265 final List<AbstractTiffOutputItem> allOutputItems = outputSet.getOutputItems(outputSummary);
266 final List<AbstractTiffOutputItem> outputItems = new ArrayList<>();
267 for (final AbstractTiffOutputItem outputItem : allOutputItems) {
268 if (!frozenFieldOffsets.containsKey(outputItem.getOffset())) {
269 outputItems.add(outputItem);
270 }
271 }
272
273 final long outputLength = updateOffsetsStep(analysis, outputItems);
274
275 outputSummary.updateOffsets(byteOrder);
276
277 writeStep(os, outputSet, analysis, outputItems, outputLength);
278
279 }
280
281 private void writeStep(final OutputStream os, final TiffOutputSet outputSet, final List<AbstractTiffElement> analysis,
282 final List<AbstractTiffOutputItem> outputItems, final long outputLength) throws IOException, ImagingException {
283 final TiffOutputDirectory rootDirectory = outputSet.getRootDirectory();
284
285 final byte[] output = Allocator.byteArray(outputLength);
286
287
288 System.arraycopy(exifBytes, 0, output, 0, Math.min(exifBytes.length, output.length));
289
290 try (BufferOutputStream headerStream = new BufferOutputStream(output, 0);
291 BinaryOutputStream headerBinaryStream = BinaryOutputStream.create(headerStream, byteOrder)) {
292 writeImageFileHeader(headerBinaryStream, rootDirectory.getOffset());
293 }
294
295
296
297 for (final AbstractTiffElement element : analysis) {
298 Arrays.fill(output, (int) element.offset, (int) Math.min(element.offset + element.length, output.length), (byte) 0);
299 }
300
301
302 for (final AbstractTiffOutputItem outputItem : outputItems) {
303 try (BinaryOutputStream bos = BinaryOutputStream.create(new BufferOutputStream(output, (int) outputItem.getOffset()), byteOrder)) {
304 outputItem.writeItem(bos);
305 }
306 }
307
308 os.write(output);
309 }
310
311 }