| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.sanselan.formats.tiff.write; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.commons.sanselan.FormatCompliance; |
| import org.apache.commons.sanselan.ImageReadException; |
| import org.apache.commons.sanselan.ImageWriteException; |
| import org.apache.commons.sanselan.common.BinaryFileFunctions; |
| import org.apache.commons.sanselan.common.BinaryOutputStream; |
| import org.apache.commons.sanselan.common.bytesource.ByteSource; |
| import org.apache.commons.sanselan.common.bytesource.ByteSourceArray; |
| import org.apache.commons.sanselan.formats.tiff.JpegImageData; |
| import org.apache.commons.sanselan.formats.tiff.TiffContents; |
| import org.apache.commons.sanselan.formats.tiff.TiffDirectory; |
| import org.apache.commons.sanselan.formats.tiff.TiffElement; |
| import org.apache.commons.sanselan.formats.tiff.TiffField; |
| import org.apache.commons.sanselan.formats.tiff.TiffImageData; |
| import org.apache.commons.sanselan.formats.tiff.TiffReader; |
| import org.apache.commons.sanselan.formats.tiff.constants.ExifTagConstants; |
| import org.apache.commons.sanselan.util.Debug; |
| |
| public class TiffImageWriterLossless extends TiffImageWriterBase |
| { |
| private final byte exifBytes[]; |
| |
| public TiffImageWriterLossless(byte exifBytes[]) |
| { |
| this.exifBytes = exifBytes; |
| } |
| |
| public TiffImageWriterLossless(int byteOrder, byte exifBytes[]) |
| { |
| super(byteOrder); |
| this.exifBytes = exifBytes; |
| } |
| |
| // private static class TiffPiece |
| // { |
| // public final int offset; |
| // public final int length; |
| // |
| // public TiffPiece(final int offset, final int length) |
| // { |
| // this.offset = offset; |
| // this.length = length; |
| // } |
| // } |
| |
| private void dumpElements(List<TiffElement> elements) throws IOException |
| { |
| // try |
| // { |
| ByteSource byteSource = new ByteSourceArray(exifBytes); |
| |
| dumpElements(byteSource, elements); |
| // } |
| // catch (ImageReadException e) |
| // { |
| // throw new ImageWriteException(e.getMessage(), e); |
| // } |
| } |
| |
| private void dumpElements(ByteSource byteSource, List<TiffElement> elements) |
| throws IOException |
| { |
| int last = TIFF_HEADER_SIZE; |
| for (int i = 0; i < elements.size(); i++) |
| { |
| TiffElement element = elements.get(i); |
| if (element.offset > last) |
| { |
| final int SLICE_SIZE = 32; |
| int gepLength = element.offset - last; |
| Debug.debug("gap of " + gepLength + " bytes."); |
| byte bytes[] = byteSource.getBlock(last, gepLength); |
| if (bytes.length > 2 * SLICE_SIZE) |
| { |
| Debug.debug("\t" + "head", BinaryFileFunctions.head(bytes, |
| SLICE_SIZE)); |
| Debug.debug("\t" + "tail", BinaryFileFunctions.tail(bytes, |
| SLICE_SIZE)); |
| } |
| else |
| Debug.debug("\t" + "bytes", bytes); |
| } |
| |
| Debug.debug("element[" + i + "]:" + element.getElementDescription() |
| + " (" + element.offset + " + " + element.length + " = " |
| + (element.offset + element.length) + ")"); |
| if (element instanceof TiffDirectory) |
| { |
| TiffDirectory dir = (TiffDirectory) element; |
| Debug.debug("\t" + "next Directory Offset: " |
| + dir.nextDirectoryOffset); |
| } |
| last = element.offset + element.length; |
| } |
| Debug.debug(); |
| } |
| |
| private List<TiffElement> analyzeOldTiff() throws ImageWriteException, IOException |
| { |
| try |
| { |
| ByteSource byteSource = new ByteSourceArray(exifBytes); |
| Map params = null; |
| FormatCompliance formatCompliance = FormatCompliance.getDefault(); |
| TiffContents contents = new TiffReader(false).readContents(byteSource, |
| params, formatCompliance); |
| |
| List<TiffElement> elements = new ArrayList<TiffElement>(); |
| // result.add(contents.header); // ? |
| |
| List<TiffDirectory> directories = contents.directories; |
| for (int d = 0; d < directories.size(); d++) |
| { |
| TiffDirectory directory = directories.get(d); |
| elements.add(directory); |
| |
| List<TiffField> fields = directory.getDirectoryEntrys(); |
| for (int f = 0; f < fields.size(); f++) |
| { |
| TiffField field = fields.get(f); |
| if (field.tag == ExifTagConstants.EXIF_TAG_MAKER_NOTE.tag) { |
| // Some maker notes reference values stored |
| // inside the maker note itself |
| // using addresses relative to the beginning |
| // of the TIFF file, making it impossible |
| // to move the note to a different location. |
| // To avoid corrupting these maker notes, |
| // pretend all maker notes are a gap in the file |
| // that must be preserved, so the copy that |
| // will be written later will reference |
| // the old copy's values. Happy days. |
| continue; |
| } |
| TiffElement oversizeValue = field.getOversizeValueElement(); |
| if (oversizeValue != null) |
| elements.add(oversizeValue); |
| |
| } |
| |
| JpegImageData jpegImageData = directory.getJpegImageData(); |
| if (jpegImageData != null) |
| elements.add(jpegImageData); |
| |
| TiffImageData tiffImageData = directory.getTiffImageData(); |
| if (tiffImageData != null) |
| { |
| TiffElement.DataElement data[] = tiffImageData |
| .getImageData(); |
| for (int i = 0; i < data.length; i++) |
| elements.add(data[i]); |
| } |
| } |
| |
| Collections.sort(elements, TiffElement.COMPARATOR); |
| |
| // dumpElements(byteSource, elements); |
| |
| List<TiffElement> result = new ArrayList<TiffElement>(); |
| { |
| final int TOLERANCE = 3; |
| // int last = TIFF_HEADER_SIZE; |
| TiffElement start = null; |
| int index = -1; |
| for (int i = 0; i < elements.size(); i++) |
| { |
| TiffElement element = elements.get(i); |
| int lastElementByte = element.offset + element.length; |
| if (start == null) |
| { |
| start = element; |
| index = lastElementByte; |
| } |
| else if (element.offset - index > TOLERANCE) |
| { |
| result.add(new TiffElement.Stub(start.offset, index |
| - start.offset)); |
| start = element; |
| index = lastElementByte; |
| } |
| else |
| { |
| index = lastElementByte; |
| } |
| } |
| if (null != start) |
| result.add(new TiffElement.Stub(start.offset, index |
| - start.offset)); |
| } |
| |
| // dumpElements(byteSource, result); |
| |
| return result; |
| } |
| catch (ImageReadException e) |
| { |
| throw new ImageWriteException(e.getMessage(), e); |
| } |
| } |
| |
| @Override |
| public void write(OutputStream os, TiffOutputSet outputSet) |
| throws IOException, ImageWriteException |
| { |
| List<TiffElement> analysis = analyzeOldTiff(); |
| int oldLength = exifBytes.length; |
| if (analysis.size() < 1) |
| throw new ImageWriteException("Couldn't analyze old tiff data."); |
| else if (analysis.size() == 1) |
| { |
| TiffElement onlyElement = analysis.get(0); |
| // Debug.debug("onlyElement", onlyElement.getElementDescription()); |
| if (onlyElement.offset == TIFF_HEADER_SIZE |
| && onlyElement.offset + onlyElement.length |
| + TIFF_HEADER_SIZE == oldLength) |
| { |
| // no gaps in old data, safe to complete overwrite. |
| new TiffImageWriterLossy(byteOrder).write(os, outputSet); |
| return; |
| } |
| } |
| |
| // if (true) |
| // throw new ImageWriteException("hahah"); |
| |
| // List directories = outputSet.getDirectories(); |
| |
| TiffOutputSummary outputSummary = validateDirectories(outputSet); |
| |
| List<TiffOutputItem> outputItems = outputSet.getOutputItems(outputSummary); |
| |
| int outputLength = updateOffsetsStep(analysis, outputItems); |
| // Debug.debug("outputLength", outputLength); |
| |
| outputSummary.updateOffsets(byteOrder); |
| |
| writeStep(os, outputSet, analysis, outputItems, outputLength); |
| |
| } |
| |
| private static final Comparator<TiffElement> ELEMENT_SIZE_COMPARATOR = new Comparator<TiffElement>() |
| { |
| public int compare(TiffElement e1, TiffElement e2) |
| { |
| return e1.length - e2.length; |
| } |
| }; |
| |
| private static final Comparator<TiffOutputItem> ITEM_SIZE_COMPARATOR = new Comparator<TiffOutputItem>() |
| { |
| public int compare(TiffOutputItem e1, TiffOutputItem e2) |
| { |
| return e1.getItemLength() - e2.getItemLength(); |
| } |
| }; |
| |
| private int updateOffsetsStep(List<TiffElement> analysis, List<TiffOutputItem> outputItems) |
| { |
| // items we cannot fit into a gap, we shall append to tail. |
| int overflowIndex = exifBytes.length; |
| |
| // make copy. |
| List<TiffElement> unusedElements = new ArrayList<TiffElement>(analysis); |
| |
| // should already be in order of offset, but make sure. |
| Collections.sort(unusedElements, TiffElement.COMPARATOR); |
| Collections.reverse(unusedElements); |
| // any items that represent a gap at the end of the exif segment, can be discarded. |
| while (unusedElements.size() > 0) |
| { |
| TiffElement element = unusedElements.get(0); |
| int elementEnd = element.offset + element.length; |
| if (elementEnd == overflowIndex) |
| { |
| // discarding a tail element. should only happen once. |
| overflowIndex -= element.length; |
| unusedElements.remove(0); |
| } |
| else |
| break; |
| } |
| |
| Collections.sort(unusedElements, ELEMENT_SIZE_COMPARATOR); |
| Collections.reverse(unusedElements); |
| |
| // Debug.debug("unusedElements"); |
| // dumpElements(unusedElements); |
| |
| // make copy. |
| List<TiffOutputItem> unplacedItems = new ArrayList<TiffOutputItem>(outputItems); |
| Collections.sort(unplacedItems, ITEM_SIZE_COMPARATOR); |
| Collections.reverse(unplacedItems); |
| |
| while (unplacedItems.size() > 0) |
| { |
| // pop off largest unplaced item. |
| TiffOutputItem outputItem = unplacedItems.remove(0); |
| int outputItemLength = outputItem.getItemLength(); |
| // Debug.debug("largest unplaced item: " |
| // + outputItem.getItemDescription() + " (" + outputItemLength |
| // + ")"); |
| |
| // search for the smallest possible element large enough to hold the item. |
| TiffElement bestFit = null; |
| for (int i = 0; i < unusedElements.size(); i++) |
| { |
| TiffElement element = unusedElements.get(i); |
| if (element.length >= outputItemLength) |
| bestFit = element; |
| else |
| break; |
| } |
| if (null == bestFit) |
| { |
| // we couldn't place this item. overflow. |
| outputItem.setOffset(overflowIndex); |
| overflowIndex += outputItemLength; |
| } |
| else |
| { |
| outputItem.setOffset(bestFit.offset); |
| unusedElements.remove(bestFit); |
| |
| if (bestFit.length > outputItemLength) |
| { |
| // not a perfect fit. |
| int excessOffset = bestFit.offset + outputItemLength; |
| int excessLength = bestFit.length - outputItemLength; |
| unusedElements.add(new TiffElement.Stub(excessOffset, |
| excessLength)); |
| // make sure the new element is in the correct order. |
| Collections.sort(unusedElements, ELEMENT_SIZE_COMPARATOR); |
| Collections.reverse(unusedElements); |
| } |
| } |
| } |
| |
| return overflowIndex; |
| // |
| // if (true) |
| // throw new IOException("mew"); |
| // |
| // // int offset = TIFF_HEADER_SIZE; |
| // int offset = exifBytes.length; |
| // |
| // for (int i = 0; i < outputItems.size(); i++) |
| // { |
| // TiffOutputItem outputItem = (TiffOutputItem) outputItems.get(i); |
| // |
| // outputItem.setOffset(offset); |
| // int itemLength = outputItem.getItemLength(); |
| // offset += itemLength; |
| // |
| // int remainder = imageDataPaddingLength(itemLength); |
| // offset += remainder; |
| // } |
| } |
| private static class BufferOutputStream extends OutputStream |
| { |
| private final byte buffer[]; |
| private int index; |
| |
| public BufferOutputStream(final byte[] buffer, final int index) |
| { |
| this.buffer = buffer; |
| this.index = index; |
| } |
| |
| @Override |
| public void write(int b) throws IOException |
| { |
| if (index >= buffer.length) |
| throw new IOException("Buffer overflow."); |
| |
| buffer[index++] = (byte) b; |
| } |
| |
| @Override |
| public void write(byte b[], int off, int len) throws IOException |
| { |
| if (index + len > buffer.length) |
| throw new IOException("Buffer overflow."); |
| System.arraycopy(b, off, buffer, index, len); |
| index += len; |
| } |
| } |
| |
| private void writeStep(OutputStream os, TiffOutputSet outputSet, |
| List<TiffElement> analysis, List<TiffOutputItem> outputItems, int outputLength) |
| throws IOException, ImageWriteException |
| { |
| TiffOutputDirectory rootDirectory = outputSet.getRootDirectory(); |
| |
| byte output[] = new byte[outputLength]; |
| |
| // copy old data (including maker notes, etc.) |
| System.arraycopy(exifBytes, 0, output, 0, Math.min(exifBytes.length, |
| output.length)); |
| |
| // bos.write(exifBytes, TIFF_HEADER_SIZE, exifBytes.length |
| // - TIFF_HEADER_SIZE); |
| |
| { |
| BufferOutputStream tos = new BufferOutputStream(output, 0); |
| BinaryOutputStream bos = new BinaryOutputStream(tos, byteOrder); |
| writeImageFileHeader(bos, rootDirectory.getOffset()); |
| } |
| |
| // zero out the parsed pieces of old exif segment, in case we don't overwrite them. |
| for (int i = 0; i < analysis.size(); i++) |
| { |
| TiffElement element = analysis.get(i); |
| for (int j = 0; j < element.length; j++) |
| { |
| int index = element.offset + j; |
| if (index < output.length) |
| output[index] = 0; |
| } |
| } |
| |
| // write in the new items |
| for (int i = 0; i < outputItems.size(); i++) |
| { |
| TiffOutputItem outputItem = outputItems.get(i); |
| |
| BufferOutputStream tos = new BufferOutputStream(output, outputItem |
| .getOffset()); |
| BinaryOutputStream bos = new BinaryOutputStream(tos, byteOrder); |
| outputItem.writeItem(bos); |
| } |
| |
| os.write(output); |
| } |
| |
| } |