/* * 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.sanselan.formats.tiff.write; //import java.awt.image.BufferedImage; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.apache.sanselan.ImageWriteException; import org.apache.sanselan.common.BinaryConstants; import org.apache.sanselan.common.BinaryOutputStream; import org.apache.sanselan.common.PackBits; import org.apache.sanselan.common.mylzw.MyLZWCompressor; import org.apache.sanselan.formats.tiff.TiffElement; //import org.apache.sanselan.formats.tiff.TiffImageData; import org.apache.sanselan.formats.tiff.constants.TiffConstants; public abstract class TiffImageWriterBase implements TiffConstants, BinaryConstants { protected final int byteOrder; public TiffImageWriterBase() { this.byteOrder = DEFAULT_TIFF_BYTE_ORDER; } public TiffImageWriterBase(int byteOrder) { this.byteOrder = byteOrder; } protected final static int imageDataPaddingLength(int dataLength) { return (4 - (dataLength % 4)) % 4; } public abstract void write(OutputStream os, TiffOutputSet outputSet) throws IOException, ImageWriteException; protected TiffOutputSummary validateDirectories(TiffOutputSet outputSet) throws ImageWriteException { List directories = outputSet.getDirectories(); if (1 > directories.size()) throw new ImageWriteException("No directories."); TiffOutputDirectory exifDirectory = null; TiffOutputDirectory gpsDirectory = null; TiffOutputDirectory interoperabilityDirectory = null; TiffOutputField exifDirectoryOffsetField = null; TiffOutputField gpsDirectoryOffsetField = null; TiffOutputField interoperabilityDirectoryOffsetField = null; ArrayList directoryIndices = new ArrayList(); Map directoryTypeMap = new HashMap(); for (int i = 0; i < directories.size(); i++) { TiffOutputDirectory directory = (TiffOutputDirectory) directories .get(i); int dirType = directory.type; Integer key = new Integer(dirType); directoryTypeMap.put(key, directory); // Debug.debug("validating dirType", dirType + " (" // + directory.getFields().size() + " fields)"); if (dirType < 0) { switch (dirType) { case DIRECTORY_TYPE_EXIF: if (exifDirectory != null) throw new ImageWriteException( "More than one EXIF directory."); exifDirectory = directory; break; case DIRECTORY_TYPE_GPS: if (gpsDirectory != null) throw new ImageWriteException( "More than one GPS directory."); gpsDirectory = directory; break; case DIRECTORY_TYPE_INTEROPERABILITY: if (interoperabilityDirectory != null) throw new ImageWriteException( "More than one Interoperability directory."); interoperabilityDirectory = directory; break; default: throw new ImageWriteException("Unknown directory: " + dirType); } } else { if (directoryIndices.contains(key)) throw new ImageWriteException( "More than one directory with index: " + dirType + "."); directoryIndices.add(new Integer(dirType)); // dirMap.put(arg0, arg1) } HashSet fieldTags = new HashSet(); ArrayList fields = directory.getFields(); for (int j = 0; j < fields.size(); j++) { TiffOutputField field = (TiffOutputField) fields.get(j); Integer fieldKey = new Integer(field.tag); if (fieldTags.contains(fieldKey)) throw new ImageWriteException("Tag (" + field.tagInfo.getDescription() + ") appears twice in directory."); fieldTags.add(fieldKey); if (field.tag == EXIF_TAG_EXIF_OFFSET.tag) { if (exifDirectoryOffsetField != null) throw new ImageWriteException( "More than one Exif directory offset field."); exifDirectoryOffsetField = field; } else if (field.tag == EXIF_TAG_INTEROP_OFFSET.tag) { if (interoperabilityDirectoryOffsetField != null) throw new ImageWriteException( "More than one Interoperability directory offset field."); interoperabilityDirectoryOffsetField = field; } else if (field.tag == EXIF_TAG_GPSINFO.tag) { if (gpsDirectoryOffsetField != null) throw new ImageWriteException( "More than one GPS directory offset field."); gpsDirectoryOffsetField = field; } } // directory. } if (directoryIndices.size() < 1) throw new ImageWriteException("Missing root directory."); // "normal" TIFF directories should have continous indices starting with // 0, ie. 0, 1, 2... Collections.sort(directoryIndices); TiffOutputDirectory previousDirectory = null; for (int i = 0; i < directoryIndices.size(); i++) { Integer index = (Integer) directoryIndices.get(i); if (index.intValue() != i) throw new ImageWriteException("Missing directory: " + i + "."); // set up chain of directory references for "normal" directories. TiffOutputDirectory directory = (TiffOutputDirectory) directoryTypeMap .get(index); if (null != previousDirectory) previousDirectory.setNextDirectory(directory); previousDirectory = directory; } TiffOutputDirectory rootDirectory = (TiffOutputDirectory) directoryTypeMap .get(new Integer(DIRECTORY_TYPE_ROOT)); // prepare results TiffOutputSummary result = new TiffOutputSummary(byteOrder, rootDirectory, directoryTypeMap); if (interoperabilityDirectory == null && interoperabilityDirectoryOffsetField != null) { // perhaps we should just discard field? throw new ImageWriteException( "Output set has Interoperability Directory Offset field, but no Interoperability Directory"); } else if (interoperabilityDirectory != null) { if (exifDirectory == null) { exifDirectory = outputSet.addExifDirectory(); } if (interoperabilityDirectoryOffsetField == null) { interoperabilityDirectoryOffsetField = TiffOutputField .createOffsetField(EXIF_TAG_INTEROP_OFFSET, byteOrder); exifDirectory.add(interoperabilityDirectoryOffsetField); } result.add(interoperabilityDirectory, interoperabilityDirectoryOffsetField); } // make sure offset fields and offset'd directories correspond. if (exifDirectory == null && exifDirectoryOffsetField != null) { // perhaps we should just discard field? throw new ImageWriteException( "Output set has Exif Directory Offset field, but no Exif Directory"); } else if (exifDirectory != null) { if (exifDirectoryOffsetField == null) { exifDirectoryOffsetField = TiffOutputField.createOffsetField( EXIF_TAG_EXIF_OFFSET, byteOrder); rootDirectory.add(exifDirectoryOffsetField); } result.add(exifDirectory, exifDirectoryOffsetField); } if (gpsDirectory == null && gpsDirectoryOffsetField != null) { // perhaps we should just discard field? throw new ImageWriteException( "Output set has GPS Directory Offset field, but no GPS Directory"); } else if (gpsDirectory != null) { if (gpsDirectoryOffsetField == null) { gpsDirectoryOffsetField = TiffOutputField.createOffsetField( EXIF_TAG_GPSINFO, byteOrder); rootDirectory.add(gpsDirectoryOffsetField); } result.add(gpsDirectory, gpsDirectoryOffsetField); } return result; // Debug.debug(); } // public void writeImage(BufferedImage src, OutputStream os, Map params) // throws ImageWriteException, IOException // { // // writeImageNew(src, os, params); // // } // // // // public void writeImageNew(BufferedImage src, OutputStream os, Map // // params) // // throws ImageWriteException, IOException // // { // // // make copy of params; we'll clear keys as we consume them. // params = new HashMap(params); // // // clear format key. // if (params.containsKey(PARAM_KEY_FORMAT)) // params.remove(PARAM_KEY_FORMAT); // // String xmpXml = null; // if (params.containsKey(PARAM_KEY_XMP_XML)) // { // xmpXml = (String) params.get(PARAM_KEY_XMP_XML); // params.remove(PARAM_KEY_XMP_XML); // } // // int width = src.getWidth(); // int height = src.getHeight(); // // // BinaryOutputStream bos = new BinaryOutputStream(os, // // WRITE_BYTE_ORDER); // // // // writeImageFileHeader(bos, WRITE_BYTE_ORDER); // // // ArrayList directoryFields = new ArrayList(); // // final int photometricInterpretation = 2; // TODO: // // int compression = TIFF_COMPRESSION_LZW; // LZW is default // if (params.containsKey(PARAM_KEY_COMPRESSION)) // { // Object value = params.get(PARAM_KEY_COMPRESSION); // if (value != null) // { // if (!(value instanceof Number)) // throw new ImageWriteException( // "Invalid compression parameter: " + value); // compression = ((Number) value).intValue(); // } // params.remove(PARAM_KEY_COMPRESSION); // } // // final int samplesPerPixel = 3; // TODO: // final int bitsPerSample = 8; // TODO: // // // int fRowsPerStrip; // TODO: // int rowsPerStrip = 8000 / (width * samplesPerPixel); // TODO: // rowsPerStrip = Math.max(1, rowsPerStrip); // must have at least one. // // byte strips[][] = getStrips(src, samplesPerPixel, bitsPerSample, // rowsPerStrip); // // // int stripCount = (height + fRowsPerStrip - 1) / fRowsPerStrip; // // int stripCount = strips.length; // // if (params.size() > 0) // { // Object firstKey = params.keySet().iterator().next(); // throw new ImageWriteException("Unknown parameter: " + firstKey); // } // // // System.out.println("width: " + width); // // System.out.println("height: " + height); // // System.out.println("fRowsPerStrip: " + fRowsPerStrip); // // System.out.println("fSamplesPerPixel: " + fSamplesPerPixel); // // System.out.println("stripCount: " + stripCount); // // if (compression == TIFF_COMPRESSION_PACKBITS) // { // for (int i = 0; i < strips.length; i++) // strips[i] = new PackBits().compress(strips[i]); // } else if (compression == TIFF_COMPRESSION_LZW) // { // for (int i = 0; i < strips.length; i++) // { // byte uncompressed[] = strips[i]; // // int LZW_MINIMUM_CODE_SIZE = 8; // // MyLZWCompressor compressor = new MyLZWCompressor( // LZW_MINIMUM_CODE_SIZE, BYTE_ORDER_MSB, true); // byte compressed[] = compressor.compress(uncompressed); // // strips[i] = compressed; // } // } else if (compression == TIFF_COMPRESSION_UNCOMPRESSED) // { // // do nothing. // } else // throw new ImageWriteException( // "Invalid compression parameter (Only LZW, Packbits and uncompressed supported)."); // // TiffElement.DataElement imageData[] = new TiffElement.DataElement[strips.length]; // for (int i = 0; i < strips.length; i++) // imageData[i] = new TiffImageData.Data(0, strips[i].length, // strips[i]); // // // int stripOffsets[] = new int[stripCount]; // // int stripByteCounts[] = new int[stripCount]; // // // // for (int i = 0; i < strips.length; i++) // // stripByteCounts[i] = strips[i].length; // // TiffOutputSet outputSet = new TiffOutputSet(byteOrder); // TiffOutputDirectory directory = outputSet.addRootDirectory(); // // // WriteField stripOffsetsField; // // { // { // TiffOutputField field = new TiffOutputField( // TIFF_TAG_IMAGE_WIDTH, FIELD_TYPE_LONG, 1, // FIELD_TYPE_LONG.writeData(new int[] { width, }, // byteOrder)); // directory.add(field); // } // { // TiffOutputField field = new TiffOutputField( // TIFF_TAG_IMAGE_LENGTH, FIELD_TYPE_LONG, 1, // FIELD_TYPE_LONG.writeData(new int[] { height, }, // byteOrder)); // directory.add(field); // } // { // TiffOutputField field = new TiffOutputField( // TIFF_TAG_PHOTOMETRIC_INTERPRETATION, FIELD_TYPE_SHORT, // 1, FIELD_TYPE_SHORT.writeData( // new int[] { photometricInterpretation, }, // byteOrder)); // directory.add(field); // } // { // TiffOutputField field = new TiffOutputField( // TIFF_TAG_COMPRESSION, FIELD_TYPE_SHORT, 1, // FIELD_TYPE_SHORT.writeData(new int[] { compression, }, // byteOrder)); // directory.add(field); // } // { // TiffOutputField field = new TiffOutputField( // TIFF_TAG_SAMPLES_PER_PIXEL, FIELD_TYPE_SHORT, 1, // FIELD_TYPE_SHORT.writeData( // new int[] { samplesPerPixel, }, byteOrder)); // directory.add(field); // } // { // TiffOutputField field = new TiffOutputField( // TIFF_TAG_BITS_PER_SAMPLE, FIELD_TYPE_SHORT, 3, // FIELD_TYPE_SHORT.writeData(new int[] { bitsPerSample, // bitsPerSample, bitsPerSample, }, byteOrder)); // directory.add(field); // } // // { // // stripOffsetsField = new WriteField(TIFF_TAG_STRIP_OFFSETS, // // FIELD_TYPE_LONG, stripOffsets.length, FIELD_TYPE_LONG // // .writeData(stripOffsets, byteOrder)); // // directory.add(stripOffsetsField); // // } // // { // // WriteField field = new WriteField(TIFF_TAG_STRIP_BYTE_COUNTS, // // FIELD_TYPE_LONG, stripByteCounts.length, // // FIELD_TYPE_LONG.writeData(stripByteCounts, // // WRITE_BYTE_ORDER)); // // directory.add(field); // // } // { // TiffOutputField field = new TiffOutputField( // TIFF_TAG_ROWS_PER_STRIP, FIELD_TYPE_LONG, 1, // FIELD_TYPE_LONG.writeData(new int[] { rowsPerStrip, }, // byteOrder)); // directory.add(field); // } // // { // int resolutionUnit = 2;// inches. // TiffOutputField field = new TiffOutputField( // TIFF_TAG_RESOLUTION_UNIT, FIELD_TYPE_SHORT, 1, // FIELD_TYPE_SHORT.writeData( // new int[] { resolutionUnit, }, byteOrder)); // directory.add(field); // } // // { // int xResolution = 72; // TiffOutputField field = new TiffOutputField( // TIFF_TAG_XRESOLUTION, FIELD_TYPE_RATIONAL, 1, // FIELD_TYPE_RATIONAL // .writeData(xResolution, 1, byteOrder)); // directory.add(field); // } // // { // int yResolution = 72; // TiffOutputField field = new TiffOutputField( // TIFF_TAG_YRESOLUTION, FIELD_TYPE_RATIONAL, 1, // FIELD_TYPE_RATIONAL // .writeData(yResolution, 1, byteOrder)); // directory.add(field); // } // // if (null != xmpXml) // { // byte xmpXmlBytes[] = xmpXml.getBytes("utf-8"); // // TiffOutputField field = new TiffOutputField(TIFF_TAG_XMP, // FIELD_TYPE_BYTE, xmpXmlBytes.length, xmpXmlBytes); // directory.add(field); // } // // } // // TiffImageData tiffImageData = new TiffImageData.Strips(imageData, // rowsPerStrip); // directory.setTiffImageData(tiffImageData); // // write(os, outputSet); // } // // private byte[][] getStrips(BufferedImage src, int samplesPerPixel, // int bitsPerSample, int rowsPerStrip) // { // int width = src.getWidth(); // int height = src.getHeight(); // // int stripCount = (height + rowsPerStrip - 1) / rowsPerStrip; // // byte result[][] = null; // { // Write Strips // result = new byte[stripCount][]; // // int remaining_rows = height; // // for (int i = 0; i < stripCount; i++) // { // int rowsInStrip = Math.min(rowsPerStrip, remaining_rows); // remaining_rows -= rowsInStrip; // // int bitsInStrip = bitsPerSample * rowsInStrip * width // * samplesPerPixel; // int bytesInStrip = (bitsInStrip + 7) / 8; // // byte uncompressed[] = new byte[bytesInStrip]; // // int counter = 0; // int y = i * rowsPerStrip; // int stop = i * rowsPerStrip + rowsPerStrip; // // for (; (y < height) && (y < stop); y++) // { // for (int x = 0; x < width; x++) // { // int rgb = src.getRGB(x, y); // int red = 0xff & (rgb >> 16); // int green = 0xff & (rgb >> 8); // int blue = 0xff & (rgb >> 0); // // uncompressed[counter++] = (byte) red; // uncompressed[counter++] = (byte) green; // uncompressed[counter++] = (byte) blue; // } // } // // result[i] = uncompressed; // } // // } // // return result; // } protected void writeImageFileHeader(BinaryOutputStream bos) throws IOException, ImageWriteException { int offsetToFirstIFD = TIFF_HEADER_SIZE; writeImageFileHeader(bos, offsetToFirstIFD); } protected void writeImageFileHeader(BinaryOutputStream bos, int offsetToFirstIFD) throws IOException, ImageWriteException { bos.write(byteOrder); bos.write(byteOrder); bos.write2Bytes(42); // tiffVersion bos.write4Bytes(offsetToFirstIFD); } }