You are viewing a plain text version of this content. The canonical link for it is here.
Posted to sanselan-commits@incubator.apache.org by cm...@apache.org on 2008/07/27 21:34:37 UTC

svn commit: r680193 - in /incubator/sanselan/trunk: ./ src/main/java/org/apache/sanselan/ src/main/java/org/apache/sanselan/common/ src/main/java/org/apache/sanselan/formats/gif/ src/main/java/org/apache/sanselan/formats/png/ src/main/java/org/apache/s...

Author: cmchen
Date: Sun Jul 27 14:34:36 2008
New Revision: 680193

URL: http://svn.apache.org/viewvc?rev=680193&view=rev
Log:
* Added ability to embed XMP XML when writing the following formats: GIF, PNG, TIFF.

Added:
    incubator/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xmp/XmpUpdateTest.java   (with props)
Modified:
    incubator/sanselan/trunk/RELEASE_NOTES
    incubator/sanselan/trunk/src/main/java/org/apache/sanselan/SanselanConstants.java
    incubator/sanselan/trunk/src/main/java/org/apache/sanselan/common/ZLibInflater.java
    incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/gif/GenericGIFBlock.java
    incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/gif/GifImageParser.java
    incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/PngConstants.java
    incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/PngWriter.java
    incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkiCCP.java
    incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkiTXt.java
    incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkzTXt.java
    incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/tiff/write/TiffImageWriterBase.java

Modified: incubator/sanselan/trunk/RELEASE_NOTES
URL: http://svn.apache.org/viewvc/incubator/sanselan/trunk/RELEASE_NOTES?rev=680193&r1=680192&r2=680193&view=diff
==============================================================================
--- incubator/sanselan/trunk/RELEASE_NOTES (original)
+++ incubator/sanselan/trunk/RELEASE_NOTES Sun Jul 27 14:34:36 2008
@@ -15,6 +15,7 @@
 Release 0.95
 ------------
 
+ * Added ability to embed XMP XML when writing the following formats: GIF, PNG, TIFF.
  * Improved handling of tEXt and zTXt PNG text blocks.
  * Added XMP XML extraction for the following formats: GIF (untested), JPEG, TIFF, PNG, PSD.
  * Added RELEASE_NOTES file (this file).

Modified: incubator/sanselan/trunk/src/main/java/org/apache/sanselan/SanselanConstants.java
URL: http://svn.apache.org/viewvc/incubator/sanselan/trunk/src/main/java/org/apache/sanselan/SanselanConstants.java?rev=680193&r1=680192&r2=680193&view=diff
==============================================================================
--- incubator/sanselan/trunk/src/main/java/org/apache/sanselan/SanselanConstants.java (original)
+++ incubator/sanselan/trunk/src/main/java/org/apache/sanselan/SanselanConstants.java Sun Jul 27 14:34:36 2008
@@ -22,67 +22,87 @@
 
 public interface SanselanConstants
 {
-	/** 
-	 * Parameter key.  Applies to read and write operations.
+	/**
+	 * Parameter key. Applies to read and write operations.
 	 * <p>
 	 * Valid values: Boolean.TRUE and Boolean.FALSE.
 	 */
 	public static final String PARAM_KEY_VERBOSE = "VERBOSE";
 
-	/** 
-	 * Parameter key.  Used to hint the filename when reading from a byte array or InputStream.
-	 * The filename hint can help disambiguate what file the image format.
+	/**
+	 * Parameter key. Used to hint the filename when reading from a byte array
+	 * or InputStream. The filename hint can help disambiguate what file the
+	 * image format.
 	 * <p>
 	 * Applies to read operations.
 	 * <p>
 	 * Valid values: filename as string
 	 * <p>
+	 * 
 	 * @see InputStream
 	 */
 	public static final String PARAM_KEY_FILENAME = "FILENAME";
 
-	/** 
-	 * Parameter key.  Used in write operations to indicate desired image format.
+	/**
+	 * Parameter key. Used in write operations to indicate desired image format.
 	 * <p>
-	 * Valid values: Any format defined in ImageFormat, such as ImageFormat.IMAGE_FORMAT_PNG.
+	 * Valid values: Any format defined in ImageFormat, such as
+	 * ImageFormat.IMAGE_FORMAT_PNG.
 	 * <p>
+	 * 
 	 * @see ImageFormat
 	 */
 	public static final String PARAM_KEY_FORMAT = "FORMAT";
 
-	/** 
-	 * Parameter key.  Used in write operations to indicate desired compression algorithm.  
+	/**
+	 * Parameter key. Used in write operations to indicate desired compression
+	 * algorithm.
 	 * <p>
 	 * Currently only applies to writing TIFF image files.
 	 * <p>
-	 * Valid values: TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED, TiffConstants.TIFF_COMPRESSION_LZW, TiffConstants.TIFF_COMPRESSION_PACKBITS.
+	 * Valid values: TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED,
+	 * TiffConstants.TIFF_COMPRESSION_LZW,
+	 * TiffConstants.TIFF_COMPRESSION_PACKBITS.
 	 * <p>
+	 * 
 	 * @see TiffConstants
 	 */
 	public static final String PARAM_KEY_COMPRESSION = "COMPRESSION";
 
 	public static final String BUFFERED_IMAGE_FACTORY = "BUFFERED_IMAGE_FACTORY";
 
-	/** 
-	 * Parameter key.  Indicates whether to read embedded thumbnails.  
+	/**
+	 * Parameter key. Indicates whether to read embedded thumbnails.
 	 * <p>
 	 * Only applies to read EXIF metadata from JPEG/JFIF files.
 	 * <p>
 	 * Valid values: Boolean.TRUE and Boolean.FALSE.
 	 * <p>
+	 * 
 	 * @see TiffConstants
 	 */
 	public static final String PARAM_KEY_READ_THUMBNAILS = "READ_THUMBNAILS";
 
-	/** 
-	 * Parameter key.  Indicates whether to throw exceptions when parsing
-	 * invalid files, or whether to tolerate small problems.  
+	/**
+	 * Parameter key. Indicates whether to throw exceptions when parsing invalid
+	 * files, or whether to tolerate small problems.
 	 * <p>
-	 * Valid values: Boolean.TRUE and Boolean.FALSE.
-	 * Default value: Boolean.FALSE.
+	 * Valid values: Boolean.TRUE and Boolean.FALSE. Default value:
+	 * Boolean.FALSE.
 	 * <p>
+	 * 
 	 * @see TiffConstants
 	 */
 	public static final String PARAM_KEY_STRICT = "STRICT";
 
+	/**
+	 * Parameter key. 
+	 * 
+	 * Only used when writing images.
+	 * <p>
+	 * Valid values: String of XMP XML.
+	 * <p>
+	 */
+	public static final String PARAM_KEY_XMP_XML = "XMP_XML";
+
 }

Modified: incubator/sanselan/trunk/src/main/java/org/apache/sanselan/common/ZLibInflater.java
URL: http://svn.apache.org/viewvc/incubator/sanselan/trunk/src/main/java/org/apache/sanselan/common/ZLibInflater.java?rev=680193&r1=680192&r2=680193&view=diff
==============================================================================
--- incubator/sanselan/trunk/src/main/java/org/apache/sanselan/common/ZLibInflater.java (original)
+++ incubator/sanselan/trunk/src/main/java/org/apache/sanselan/common/ZLibInflater.java Sun Jul 27 14:34:36 2008
@@ -18,14 +18,14 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.util.zip.DeflaterInputStream;
 import java.util.zip.InflaterInputStream;
 
 import org.apache.sanselan.ImageReadException;
 
 public class ZLibInflater extends BinaryFileFunctions
 {
-	public final byte[] zlibInflate(byte bytes[]) throws ImageReadException,
-			IOException
+	public final byte[] inflate(byte bytes[]) throws IOException
 	// slow, probably.
 	{
 		ByteArrayInputStream in = new ByteArrayInputStream(bytes);
@@ -33,4 +33,11 @@
 		return getStreamBytes(zIn);
 	}
 
+	public final byte[] deflate(byte bytes[]) throws IOException
+	{
+		ByteArrayInputStream in = new ByteArrayInputStream(bytes);
+		DeflaterInputStream zIn = new DeflaterInputStream(in);
+		return getStreamBytes(zIn);
+	}
+
 }

Modified: incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/gif/GenericGIFBlock.java
URL: http://svn.apache.org/viewvc/incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/gif/GenericGIFBlock.java?rev=680193&r1=680192&r2=680193&view=diff
==============================================================================
--- incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/gif/GenericGIFBlock.java (original)
+++ incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/gif/GenericGIFBlock.java Sun Jul 27 14:34:36 2008
@@ -34,11 +34,18 @@
 
 	public byte[] appendSubBlocks() throws IOException
 	{
+		return appendSubBlocks(false);
+	}
+
+	public byte[] appendSubBlocks(boolean includeLengths) throws IOException
+	{
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 
 		for (int i = 0; i < subblocks.size(); i++)
 		{
 			byte subblock[] = (byte[]) subblocks.get(i);
+			if(includeLengths && i>0)
+				out.write(subblock.length);
 			out.write(subblock);
 		}
 

Modified: incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/gif/GifImageParser.java
URL: http://svn.apache.org/viewvc/incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/gif/GifImageParser.java?rev=680193&r1=680192&r2=680193&view=diff
==============================================================================
--- incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/gif/GifImageParser.java (original)
+++ incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/gif/GifImageParser.java Sun Jul 27 14:34:36 2008
@@ -42,6 +42,7 @@
 import org.apache.sanselan.common.byteSources.ByteSource;
 import org.apache.sanselan.common.mylzw.MyLZWCompressor;
 import org.apache.sanselan.common.mylzw.MyLZWDecompressor;
+import org.apache.sanselan.formats.tiff.write.TiffOutputField;
 import org.apache.sanselan.palette.Palette;
 import org.apache.sanselan.palette.PaletteFactory;
 import org.apache.sanselan.util.Debug;
@@ -203,7 +204,6 @@
 	protected GenericGIFBlock readGenericGIFBlock(InputStream is, int code,
 			byte first[]) throws ImageReadException, IOException
 	{
-		byte bytes[] = null;
 		ArrayList subblocks = new ArrayList();
 
 		if (first != null)
@@ -211,7 +211,7 @@
 
 		while (true)
 		{
-			bytes = readSubBlock(is);
+			byte bytes[] = readSubBlock(is);
 			if (bytes.length < 1)
 				break;
 			subblocks.add(bytes);
@@ -220,12 +220,16 @@
 		return new GenericGIFBlock(code, subblocks);
 	}
 
+	private final static int EXTENSION_CODE = 0x21;
 	private final static int IMAGE_SEPARATOR = 0x2C;
-	private final static int GRAPHIC_CONTROL_EXTENSION = (0x2100 | 0xf9);
+	private final static int GRAPHIC_CONTROL_EXTENSION = (EXTENSION_CODE << 8) | 0xf9;
 	private final static int COMMENT_EXTENSION = 0xfe;
 	private final static int PLAIN_TEXT_EXTENSION = 0x01;
 	private final static int XMP_EXTENSION = 0xff;
 	private final static int TERMINATOR_BYTE = 0x3b;
+	private final static int APPLICATION_EXTENSION_LABEL = 0xff;
+	private final static int XMP_COMPLETE_CODE = (EXTENSION_CODE << 8)
+			| XMP_EXTENSION;
 
 	private ArrayList readBlocks(GIFHeaderInfo ghi, InputStream is,
 			boolean stopBeforeImageData, FormatCompliance formatCompliance)
@@ -236,7 +240,7 @@
 		while (true)
 		{
 			int code = is.read();
-//			 this.debugNumber("code: ", code);
+			// this.debugNumber("code: ", code);
 
 			switch (code)
 			{
@@ -247,43 +251,43 @@
 				ImageDescriptor id = readImageDescriptor(ghi, code, is,
 						stopBeforeImageData, formatCompliance);
 				result.add(id);
-//				if(stopBeforeImageData)
-//					return result;
+				// if(stopBeforeImageData)
+				// return result;
 
 				break;
 
-			case 0x21: // extension
+			case EXTENSION_CODE: // extension
 			{
-				int extension_code = is.read();
+				int extensionCode = is.read();
 				// this.debugNumber("extension_code: ", extension_code);
-				int complete_code = ((0xff & code) << 8)
-						| (0xff & extension_code);
+				int completeCode = ((0xff & code) << 8)
+						| (0xff & extensionCode);
 
-				switch (extension_code)
+				switch (extensionCode)
 				{
 				case 0xf9:
 					GraphicControlExtension gce = readGraphicControlExtension(
-							complete_code, is);
+							completeCode, is);
 					result.add(gce);
 					break;
 
 				case COMMENT_EXTENSION:
 				case PLAIN_TEXT_EXTENSION: {
 					GenericGIFBlock block = readGenericGIFBlock(is,
-							complete_code);
+							completeCode);
 					result.add(block);
 					break;
 				}
 
-				case 0xff: // 255 (hex 0xFF) Application Extension Label
+				case APPLICATION_EXTENSION_LABEL: // 255 (hex 0xFF) Application
+					// Extension Label
 				{
 					byte label[] = readSubBlock(is);
 
 					if (formatCompliance != null)
-						formatCompliance.addComment(
-								"Unknown Application Extension ("
-										+ new String(label) + ")",
-								complete_code);
+						formatCompliance
+								.addComment("Unknown Application Extension ("
+										+ new String(label) + ")", completeCode);
 
 					// if (label == new String("ICCRGBG1"))
 					{
@@ -293,7 +297,7 @@
 					if ((label != null) && (label.length > 0))
 					{
 						GenericGIFBlock block = readGenericGIFBlock(is,
-								complete_code, label);
+								completeCode, label);
 						byte bytes[] = block.appendSubBlocks();
 
 						result.add(block);
@@ -305,10 +309,10 @@
 
 					if (formatCompliance != null)
 						formatCompliance.addComment("Unknown block",
-								complete_code);
+								completeCode);
 
 					GenericGIFBlock block = readGenericGIFBlock(is,
-							complete_code);
+							completeCode);
 					result.add(block);
 					break;
 				}
@@ -839,6 +843,13 @@
 		if (params.containsKey(PARAM_KEY_VERBOSE))
 			params.remove(PARAM_KEY_VERBOSE);
 
+		String xmpXml = null;
+		if (params.containsKey(PARAM_KEY_XMP_XML))
+		{
+			xmpXml = (String) params.get(PARAM_KEY_XMP_XML);
+			params.remove(PARAM_KEY_XMP_XML);
+		}
+
 		if (params.size() > 0)
 		{
 			Object firstKey = params.keySet().iterator().next();
@@ -926,7 +937,7 @@
 
 			if (hasAlpha)
 			{ // write GraphicControlExtension
-				bos.write((byte) 0x21);
+				bos.write(EXTENSION_CODE);
 				bos.write((byte) 0xf9);
 				// bos.write(0xff & (kGraphicControlExtension >> 8));
 				// bos.write(0xff & (kGraphicControlExtension >> 0));
@@ -942,6 +953,25 @@
 				bos.write((byte) 0); // terminator
 			}
 
+			if (null != xmpXml)
+			{
+				bos.write(EXTENSION_CODE);
+				bos.write(APPLICATION_EXTENSION_LABEL);
+
+				bos.write(XMP_APPLICATION_ID_AND_AUTH_CODE.length); // 0x0B
+				bos.write(XMP_APPLICATION_ID_AND_AUTH_CODE);
+
+				byte xmpXmlBytes[] = xmpXml.getBytes("utf-8");
+				bos.write(xmpXmlBytes);
+
+				// write "magic trailer"
+				for (int magic = 0; magic <= 0xff; magic++)
+					bos.write(0xff - magic);
+
+				bos.write((byte) 0); // terminator
+
+			}
+
 			{ // Image Descriptor.
 				bos.write(IMAGE_SEPARATOR);
 				bos.write2Bytes(0); // Image Left Position
@@ -1079,58 +1109,66 @@
 			FormatCompliance formatCompliance = null;
 			GIFHeaderInfo ghi = readHeader(is, formatCompliance);
 
-
 			byte globalColorTable[] = null;
 			if (ghi.globalColorTableFlag)
 				globalColorTable = readColorTable(is,
 						ghi.sizeOfGlobalColorTable, formatCompliance);
-			
+
 			ArrayList blocks = readBlocks(ghi, is, true, formatCompliance);
 
 			List result = new ArrayList();
 			for (int i = 0; i < blocks.size(); i++)
 			{
 				GIFBlock block = (GIFBlock) blocks.get(i);
-				if (block.blockCode != XMP_EXTENSION)
+				if (block.blockCode != XMP_COMPLETE_CODE)
 					continue;
 
 				GenericGIFBlock genericBlock = (GenericGIFBlock) block;
 
-				byte blockBytes[] = genericBlock.appendSubBlocks();
+				byte blockBytes[] = genericBlock.appendSubBlocks(true);
 				if (blockBytes.length < XMP_APPLICATION_ID_AND_AUTH_CODE.length)
 					continue;
+
+				// this.debugByteArray("blockBytes", blockBytes);
+
 				if (!compareByteArrays(blockBytes, 0,
 						XMP_APPLICATION_ID_AND_AUTH_CODE, 0,
 						XMP_APPLICATION_ID_AND_AUTH_CODE.length))
 					continue;
-				
-//				this.debugByteArray("xmp block bytes", blockBytes);
+
+				// this.debugByteArray("xmp block bytes", blockBytes);
 				byte GIF_MAGIC_TRAILER[] = new byte[256];
-				for(int magic=0;magic<=0xff;magic++)
-					GIF_MAGIC_TRAILER[magic] = (byte) (0xff-magic);
-				
-				if (blockBytes.length < XMP_APPLICATION_ID_AND_AUTH_CODE.length + GIF_MAGIC_TRAILER.length)
+				for (int magic = 0; magic <= 0xff; magic++)
+					GIF_MAGIC_TRAILER[magic] = (byte) (0xff - magic);
+
+				if (blockBytes.length < XMP_APPLICATION_ID_AND_AUTH_CODE.length
+						+ GIF_MAGIC_TRAILER.length)
 					continue;
-				if (!compareByteArrays(blockBytes, blockBytes.length-GIF_MAGIC_TRAILER.length,
-						GIF_MAGIC_TRAILER, 0,
+				if (!compareByteArrays(blockBytes, blockBytes.length
+						- GIF_MAGIC_TRAILER.length, GIF_MAGIC_TRAILER, 0,
 						GIF_MAGIC_TRAILER.length))
-					throw new ImageReadException("XMP block in GIF missing magic trailer.");
-				
+					throw new ImageReadException(
+							"XMP block in GIF missing magic trailer.");
+
 				try
 				{
 					// XMP is UTF-8 encoded xml.
-					String xml = new String(blockBytes, XMP_APPLICATION_ID_AND_AUTH_CODE.length, blockBytes.length
-							- (XMP_APPLICATION_ID_AND_AUTH_CODE.length + GIF_MAGIC_TRAILER.length), "utf-8");
+					String xml = new String(
+							blockBytes,
+							XMP_APPLICATION_ID_AND_AUTH_CODE.length,
+							blockBytes.length
+									- (XMP_APPLICATION_ID_AND_AUTH_CODE.length + GIF_MAGIC_TRAILER.length),
+							"utf-8");
 					result.add(xml);
 				} catch (UnsupportedEncodingException e)
 				{
 					throw new ImageReadException("Invalid XMP Block in GIF.");
 				}
 			}
-			
-			if(result.size()<1)
+
+			if (result.size() < 1)
 				return null;
-			if(result.size()>1)
+			if (result.size() > 1)
 				throw new ImageReadException("More than one XMP Block in GIF.");
 			return (String) result.get(0);
 

Modified: incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/PngConstants.java
URL: http://svn.apache.org/viewvc/incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/PngConstants.java?rev=680193&r1=680192&r2=680193&view=diff
==============================================================================
--- incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/PngConstants.java (original)
+++ incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/PngConstants.java Sun Jul 27 14:34:36 2008
@@ -23,18 +23,11 @@
 
 	public static final int COMPRESSION_DEFLATE_INFLATE = 0;
 
-	public final static byte[] IHDR_CHUNK_TYPE = new byte[]{
-			73, 72, 68, 82
-	};
-	public final static byte[] PLTE_CHUNK_TYPE = new byte[]{
-			80, 76, 84, 69
-	};
-	public final static byte[] IEND_CHUNK_TYPE = new byte[]{
-			73, 69, 78, 68
-	};
-	public final static byte[] IDAT_CHUNK_TYPE = new byte[]{
-			73, 68, 65, 84
-	};
+	public final static byte[] IHDR_CHUNK_TYPE = new byte[] { 73, 72, 68, 82 };
+	public final static byte[] PLTE_CHUNK_TYPE = new byte[] { 80, 76, 84, 69 };
+	public final static byte[] IEND_CHUNK_TYPE = new byte[] { 73, 69, 78, 68 };
+	public final static byte[] IDAT_CHUNK_TYPE = new byte[] { 73, 68, 65, 84 };
+	public final static byte[] iTXt_CHUNK_TYPE = new byte[] { 105, 84, 88, 116 };
 
 	public final static int IEND = PngImageParser.CharsToQuad('I', 'E', 'N',
 			'D');
@@ -57,22 +50,21 @@
 	public final static int gAMA = PngImageParser.CharsToQuad('g', 'A', 'M',
 			'A');
 	public final static int sRGB = PngImageParser.CharsToQuad('s', 'R', 'G',
-	'B');
-	
+			'B');
+
 	// XMP chunk type.
 	public final static int iTXt = PngImageParser.CharsToQuad('i', 'T', 'X',
-	't');
+			't');
 
-	public static final byte PNG_Signature[] = {
-			(byte) 137, 80, 78, 71, 13, 10, 26, 10,
-	};
+	public static final byte PNG_Signature[] = { (byte) 137, 80, 78, 71, 13,
+			10, 26, 10, };
 
 	public static final String PARAM_KEY_PNG_BIT_DEPTH = "PNG_BIT_DEPTH";
 	public static final String PARAM_KEY_PNG_FORCE_INDEXED_COLOR = "PNG_FORCE_INDEXED_COLOR";
 	public static final String PARAM_KEY_PNG_FORCE_TRUE_COLOR = "PNG_FORCE_TRUE_COLOR";
 
-	//	public static final Object PARAM_KEY_PNG_BIT_DEPTH_YES = "YES";
-	//	public static final Object PARAM_KEY_PNG_BIT_DEPTH_NO = "NO";
+	// public static final Object PARAM_KEY_PNG_BIT_DEPTH_YES = "YES";
+	// public static final Object PARAM_KEY_PNG_BIT_DEPTH_NO = "NO";
 
 	public static final int COLOR_TYPE_GREYSCALE = 0;
 	public static final int COLOR_TYPE_TRUE_COLOR = 2;
@@ -93,19 +85,27 @@
 	public static final byte FILTER_TYPE_PAETH = 4;
 
 	/*
-	 Background colour 	Solid background colour to be used when presenting the image if no better option is available.
-	 Gamma and chromaticity 	Gamma characteristic of the image with respect to the desired output intensity, and chromaticity characteristics of the RGB values used in the image.
-	 ICC profile 	Description of the colour space (in the form of an International Color Consortium (ICC) profile) to which the samples in the image conform.
-	 Image histogram 	Estimates of how frequently the image uses each palette entry.
-	 Physical pixel dimensions 	Intended pixel size and aspect ratio to be used in presenting the PNG image.
-	 Significant bits 	The number of bits that are significant in the samples.
-	 sRGB colour space 	A rendering intent (as defined by the International Color Consortium) and an indication that the image samples conform to this colour space.
-	 Suggested palette 	A reduced palette that may be used when the display device is not capable of displaying the full range of colours in the image.
-	 Textual data 	Textual information (which may be compressed) associated with the image.
-	 Time 	The time when the PNG image was last modified.
-	 Transparency 	Alpha information that allows the reference image to be reconstructed when the alpha channel is not retained in the PNG image.
+	 * Background colour Solid background colour to be used when presenting the
+	 * image if no better option is available. Gamma and chromaticity Gamma
+	 * characteristic of the image with respect to the desired output intensity,
+	 * and chromaticity characteristics of the RGB values used in the image. ICC
+	 * profile Description of the colour space (in the form of an International
+	 * Color Consortium (ICC) profile) to which the samples in the image
+	 * conform. Image histogram Estimates of how frequently the image uses each
+	 * palette entry. Physical pixel dimensions Intended pixel size and aspect
+	 * ratio to be used in presenting the PNG image. Significant bits The number
+	 * of bits that are significant in the samples. sRGB colour space A
+	 * rendering intent (as defined by the International Color Consortium) and
+	 * an indication that the image samples conform to this colour space.
+	 * Suggested palette A reduced palette that may be used when the display
+	 * device is not capable of displaying the full range of colours in the
+	 * image. Textual data Textual information (which may be compressed)
+	 * associated with the image. Time The time when the PNG image was last
+	 * modified. Transparency Alpha information that allows the reference image
+	 * to be reconstructed when the alpha channel is not retained in the PNG
+	 * image.
 	 */
-	
-	public final String XMP_KEYWORD = 	"XML:com.adobe.xmp";
+
+	public final String XMP_KEYWORD = "XML:com.adobe.xmp";
 
 }
\ No newline at end of file

Modified: incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/PngWriter.java
URL: http://svn.apache.org/viewvc/incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/PngWriter.java?rev=680193&r1=680192&r2=680193&view=diff
==============================================================================
--- incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/PngWriter.java (original)
+++ incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/PngWriter.java Sun Jul 27 14:34:36 2008
@@ -25,20 +25,24 @@
 import java.util.zip.DeflaterOutputStream;
 
 import org.apache.sanselan.ImageWriteException;
+import org.apache.sanselan.common.ZLibInflater;
 import org.apache.sanselan.palette.MedianCutQuantizer;
 import org.apache.sanselan.palette.Palette;
 import org.apache.sanselan.palette.PaletteFactory;
 import org.apache.sanselan.util.Debug;
 import org.apache.sanselan.util.ParamMap;
 
-public class PngWriter implements PngConstants {
+public class PngWriter implements PngConstants
+{
 	private final boolean verbose;
 
-	public PngWriter(boolean verbose) {
+	public PngWriter(boolean verbose)
+	{
 		this.verbose = verbose;
 	}
 
-	public PngWriter(Map params) {
+	public PngWriter(Map params)
+	{
 		this.verbose = ParamMap.getParamBoolean(params, PARAM_KEY_VERBOSE,
 				false);
 	}
@@ -60,7 +64,8 @@
 	 * tIME (see 11.3.6: Time stamp information).
 	 */
 
-	private final void writeInt(OutputStream os, int value) throws IOException {
+	private final void writeInt(OutputStream os, int value) throws IOException
+	{
 		os.write(0xff & (value >> 24));
 		os.write(0xff & (value >> 16));
 		os.write(0xff & (value >> 8));
@@ -68,7 +73,8 @@
 	}
 
 	private final void writeChunk(OutputStream os, byte chunk_type[],
-			byte data[]) throws IOException {
+			byte data[]) throws IOException
+	{
 		int data_length = data == null ? 0 : data.length;
 		writeInt(os, data_length);
 		os.write(chunk_type);
@@ -98,7 +104,8 @@
 		}
 	}
 
-	private static class ImageHeader {
+	private static class ImageHeader
+	{
 		public final int width;
 		public final int height;
 		public final byte bit_depth;
@@ -109,7 +116,8 @@
 
 		public ImageHeader(int width, int height, byte bit_depth,
 				byte colorType, byte compression_method, byte filter_method,
-				byte interlace_method) {
+				byte interlace_method)
+		{
 			this.width = width;
 			this.height = height;
 			this.bit_depth = bit_depth;
@@ -122,7 +130,8 @@
 	}
 
 	private void writeChunkIHDR(OutputStream os, ImageHeader value)
-			throws IOException {
+			throws IOException
+	{
 		ByteArrayOutputStream baos = new ByteArrayOutputStream();
 		writeInt(baos, value.width);
 		writeInt(baos, value.height);
@@ -137,13 +146,39 @@
 		writeChunk(os, IHDR_CHUNK_TYPE, baos.toByteArray());
 	}
 
+	private void writeChunkiTXt(OutputStream os, String xmpXml)
+			throws IOException
+	{
+
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+		// keyword
+		baos.write(XMP_KEYWORD.getBytes("ISO-8859-1"));
+		baos.write(0);
+
+		baos.write(1); // compressed flag, true
+		baos.write(COMPRESSION_DEFLATE_INFLATE); // compression method
+
+		baos.write(0); // language tag (ignore). TODO
+
+		// translated keyword
+		baos.write(XMP_KEYWORD.getBytes("utf-8"));
+		baos.write(0);
+
+		baos.write(new ZLibInflater().deflate(xmpXml.getBytes("utf-8")));
+
+		writeChunk(os, iTXt_CHUNK_TYPE, baos.toByteArray());
+	}
+
 	private void writeChunkPLTE(OutputStream os, Palette palette)
-			throws IOException {
+			throws IOException
+	{
 		int length = palette.length();
 		byte bytes[] = new byte[length * 3];
 
 		// Debug.debug("length", length);
-		for (int i = 0; i < length; i++) {
+		for (int i = 0; i < length; i++)
+		{
 			int rgb = palette.getEntry(i);
 			int index = i * 3;
 			// Debug.debug("index", index);
@@ -155,23 +190,27 @@
 		writeChunk(os, PLTE_CHUNK_TYPE, bytes);
 	}
 
-	private void writeChunkIEND(OutputStream os) throws IOException {
+	private void writeChunkIEND(OutputStream os) throws IOException
+	{
 		writeChunk(os, IEND_CHUNK_TYPE, null);
 	}
 
 	private void writeChunkIDAT(OutputStream os, byte bytes[])
-			throws IOException {
+			throws IOException
+	{
 		writeChunk(os, IDAT_CHUNK_TYPE, bytes);
 	}
 
-	private byte getColourType(boolean hasAlpha, boolean isGrayscale) {
+	private byte getColourType(boolean hasAlpha, boolean isGrayscale)
+	{
 		byte result;
 
 		boolean index = false; // charles
 
 		if (index)
 			result = COLOR_TYPE_INDEXED_COLOR;
-		else if (isGrayscale) {
+		else if (isGrayscale)
+		{
 			if (hasAlpha)
 				result = COLOR_TYPE_GREYSCALE_WITH_ALPHA;
 			else
@@ -184,13 +223,16 @@
 		return result;
 	}
 
-	private byte getBitDepth(final byte colorType, Map params) {
+	private byte getBitDepth(final byte colorType, Map params)
+	{
 		byte result = 8;
 
 		Object o = params.get(PARAM_KEY_PNG_BIT_DEPTH);
-		if (o != null && o instanceof Number) {
+		if (o != null && o instanceof Number)
+		{
 			int value = ((Number) o).intValue();
-			switch (value) {
+			switch (value)
+			{
 			case 1:
 			case 2:
 			case 4:
@@ -199,7 +241,8 @@
 				result = (byte) value;
 			default:
 			}
-			switch (colorType) {
+			switch (colorType)
+			{
 			case COLOR_TYPE_GREYSCALE:
 				break;
 			case COLOR_TYPE_INDEXED_COLOR:
@@ -234,7 +277,8 @@
 	 */
 
 	public void writeImage(BufferedImage src, OutputStream os, Map params)
-			throws ImageWriteException, IOException {
+			throws ImageWriteException, IOException
+	{
 		// make copy of params; we'll clear keys as we consume them.
 		params = new HashMap(params);
 
@@ -252,7 +296,10 @@
 			params.remove(PARAM_KEY_PNG_FORCE_INDEXED_COLOR);
 		if (params.containsKey(PARAM_KEY_PNG_BIT_DEPTH))
 			params.remove(PARAM_KEY_PNG_BIT_DEPTH);
-		if (params.size() > 0) {
+		if (params.containsKey(PARAM_KEY_XMP_XML))
+			params.remove(PARAM_KEY_XMP_XML);
+		if (params.size() > 0)
+		{
 			Object firstKey = params.keySet().iterator().next();
 			throw new ImageWriteException("Unknown parameter: " + firstKey);
 		}
@@ -280,9 +327,11 @@
 			if (force_indexed_color && force_true_color)
 				throw new ImageWriteException(
 						"Params: Cannot force both indexed and true color modes");
-			else if (force_indexed_color) {
+			else if (force_indexed_color)
+			{
 				colorType = COLOR_TYPE_INDEXED_COLOR;
-			} else if (force_true_color) {
+			} else if (force_true_color)
+			{
 				colorType = (byte) (hasAlpha ? COLOR_TYPE_TRUE_COLOR_WITH_ALPHA
 						: COLOR_TYPE_TRUE_COLOR);
 			} else
@@ -328,7 +377,8 @@
 		}
 
 		Palette palette = null;
-		if (colorType == COLOR_TYPE_INDEXED_COLOR) {
+		if (colorType == COLOR_TYPE_INDEXED_COLOR)
+		{
 			// PLTE No Before first IDAT
 
 			int max_colors = hasAlpha ? 255 : 256;
@@ -343,6 +393,12 @@
 			writeChunkPLTE(os, palette);
 		}
 
+		if (params.containsKey(PARAM_KEY_XMP_XML))
+		{
+			String xmpXml = (String) params.get(PARAM_KEY_XMP_XML);
+			writeChunkiTXt(os, xmpXml);
+		}
+
 		{
 			// Debug.debug("writing IDAT");
 
@@ -356,25 +412,30 @@
 						|| colorType == COLOR_TYPE_TRUE_COLOR_WITH_ALPHA;
 
 				int row[] = new int[width];
-				for (int y = 0; y < height; y++) {
+				for (int y = 0; y < height; y++)
+				{
 					// Debug.debug("y", y + "/" + height);
 					src.getRGB(0, y, width, 1, row, 0, width);
 
 					byte filter_type = FILTER_TYPE_NONE;
 					baos.write(filter_type);
-					for (int x = 0; x < width; x++) {
+					for (int x = 0; x < width; x++)
+					{
 						int argb = row[x];
 
-						if (palette != null) {
+						if (palette != null)
+						{
 							int index = palette.getPaletteIndex(argb);
 							baos.write(0xff & index);
-						} else {
+						} else
+						{
 							int alpha = 0xff & (argb >> 24);
 							int red = 0xff & (argb >> 16);
 							int green = 0xff & (argb >> 8);
 							int blue = 0xff & (argb >> 0);
 
-							if (isGrayscale) {
+							if (isGrayscale)
+							{
 								int gray = (red + green + blue) / 3;
 								// if(y==0)
 								// {
@@ -387,7 +448,8 @@
 								// Debug.debug();
 								// }
 								baos.write(gray);
-							} else {
+							} else
+							{
 								baos.write(red);
 								baos.write(green);
 								baos.write(blue);
@@ -405,7 +467,8 @@
 			ByteArrayOutputStream baos = new ByteArrayOutputStream();
 			DeflaterOutputStream dos = new DeflaterOutputStream(baos);
 			int chunk_size = 256 * 1024;
-			for (int index = 0; index < uncompressed.length; index += chunk_size) {
+			for (int index = 0; index < uncompressed.length; index += chunk_size)
+			{
 				int end = Math.min(uncompressed.length, index + chunk_size);
 				int length = end - index;
 
@@ -415,7 +478,8 @@
 
 				byte compressed[] = baos.toByteArray();
 				baos.reset();
-				if (compressed.length > 0) {
+				if (compressed.length > 0)
+				{
 					// Debug.debug("compressed", compressed.length);
 					writeChunkIDAT(os, compressed);
 				}
@@ -424,7 +488,8 @@
 			{
 				dos.finish();
 				byte compressed[] = baos.toByteArray();
-				if (compressed.length > 0) {
+				if (compressed.length > 0)
+				{
 					// Debug.debug("compressed final", compressed.length);
 					writeChunkIDAT(os, compressed);
 				}

Modified: incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkiCCP.java
URL: http://svn.apache.org/viewvc/incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkiCCP.java?rev=680193&r1=680192&r2=680193&view=diff
==============================================================================
--- incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkiCCP.java (original)
+++ incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkiCCP.java Sun Jul 27 14:34:36 2008
@@ -64,7 +64,7 @@
 			}
 
 			UncompressedProfile = new ZLibInflater()
-					.zlibInflate(CompressedProfile);
+					.inflate(CompressedProfile);
 
 			if (getDebug())
 			{

Modified: incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkiTXt.java
URL: http://svn.apache.org/viewvc/incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkiTXt.java?rev=680193&r1=680192&r2=680193&view=diff
==============================================================================
--- incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkiTXt.java (original)
+++ incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkiTXt.java Sun Jul 27 14:34:36 2008
@@ -97,7 +97,7 @@
 						compressedTextLength);
 
 				text = new String(new ZLibInflater()
-						.zlibInflate(compressedText), "utf-8");
+						.inflate(compressedText), "utf-8");
 
 			} else
 				text = new String(bytes, index, bytes.length - index, "utf-8");

Modified: incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkzTXt.java
URL: http://svn.apache.org/viewvc/incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkzTXt.java?rev=680193&r1=680192&r2=680193&view=diff
==============================================================================
--- incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkzTXt.java (original)
+++ incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/png/chunks/PNGChunkzTXt.java Sun Jul 27 14:34:36 2008
@@ -51,7 +51,7 @@
 			System.arraycopy(bytes, index + 1 + 1, compressedText, 0,
 					compressedTextLength);
 
-			text = new String(new ZLibInflater().zlibInflate(compressedText),
+			text = new String(new ZLibInflater().inflate(compressedText),
 					"ISO-8859-1");
 		}
 	}

Modified: incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/tiff/write/TiffImageWriterBase.java
URL: http://svn.apache.org/viewvc/incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/tiff/write/TiffImageWriterBase.java?rev=680193&r1=680192&r2=680193&view=diff
==============================================================================
--- incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/tiff/write/TiffImageWriterBase.java (original)
+++ incubator/sanselan/trunk/src/main/java/org/apache/sanselan/formats/tiff/write/TiffImageWriterBase.java Sun Jul 27 14:34:36 2008
@@ -35,13 +35,10 @@
 import org.apache.sanselan.formats.tiff.TiffImageData;
 import org.apache.sanselan.formats.tiff.constants.TiffConstants;
 
-public abstract class TiffImageWriterBase
-		implements
-			TiffConstants,
-			BinaryConstants
+public abstract class TiffImageWriterBase implements TiffConstants,
+		BinaryConstants
 {
 
-
 	protected final int byteOrder;
 
 	public TiffImageWriterBase()
@@ -69,7 +66,7 @@
 
 		if (1 > directories.size())
 			throw new ImageWriteException("No directories.");
-		
+
 		TiffOutputDirectory exifDirectory = null;
 		TiffOutputDirectory gpsDirectory = null;
 		TiffOutputDirectory interoperabilityDirectory = null;
@@ -86,46 +83,45 @@
 			int dirType = directory.type;
 			Integer key = new Integer(dirType);
 			directoryTypeMap.put(key, directory);
-			//			Debug.debug("validating dirType", dirType + " ("
-			//					+ directory.getFields().size() + " fields)");
+			// 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);
+				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
+			} else
 			{
 				if (directoryIndices.contains(key))
 					throw new ImageWriteException(
 							"More than one directory with index: " + dirType
 									+ ".");
 				directoryIndices.add(new Integer(dirType));
-				//				dirMap.put(arg0, arg1)
+				// dirMap.put(arg0, arg1)
 			}
 
 			HashSet fieldTags = new HashSet();
@@ -147,15 +143,13 @@
 						throw new ImageWriteException(
 								"More than one Exif directory offset field.");
 					exifDirectoryOffsetField = field;
-				}
-				else if (field.tag == EXIF_TAG_INTEROP_OFFSET.tag)
+				} 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)
+				} else if (field.tag == EXIF_TAG_GPSINFO.tag)
 				{
 					if (gpsDirectoryOffsetField != null)
 						throw new ImageWriteException(
@@ -163,13 +157,14 @@
 					gpsDirectoryOffsetField = field;
 				}
 			}
-			//			directory.
+			// 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...
+		// "normal" TIFF directories should have continous indices starting with
+		// 0, ie. 0, 1, 2...
 		Collections.sort(directoryIndices);
 
 		TiffOutputDirectory previousDirectory = null;
@@ -200,8 +195,7 @@
 			// perhaps we should just discard field?
 			throw new ImageWriteException(
 					"Output set has Interoperability Directory Offset field, but no Interoperability Directory");
-		}
-		else if (interoperabilityDirectory != null)
+		} else if (interoperabilityDirectory != null)
 		{
 			if (exifDirectory == null)
 			{
@@ -225,8 +219,7 @@
 			// perhaps we should just discard field?
 			throw new ImageWriteException(
 					"Output set has Exif Directory Offset field, but no Exif Directory");
-		}
-		else if (exifDirectory != null)
+		} else if (exifDirectory != null)
 		{
 			if (exifDirectoryOffsetField == null)
 			{
@@ -243,8 +236,7 @@
 			// perhaps we should just discard field?
 			throw new ImageWriteException(
 					"Output set has GPS Directory Offset field, but no GPS Directory");
-		}
-		else if (gpsDirectory != null)
+		} else if (gpsDirectory != null)
 		{
 			if (gpsDirectoryOffsetField == null)
 			{
@@ -258,18 +250,19 @@
 
 		return result;
 
-		//		Debug.debug();
+		// Debug.debug();
 	}
 
 	public void writeImage(BufferedImage src, OutputStream os, Map params)
 			throws ImageWriteException, IOException
 	{
-		//        writeImageNew(src, os, params);
-		//    }
+		// writeImageNew(src, os, params);
+		// }
 		//
-		//    public void writeImageNew(BufferedImage src, OutputStream os, Map params)
-		//            throws ImageWriteException, IOException
-		//    {
+		// 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);
@@ -278,16 +271,24 @@
 		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);
+		// BinaryOutputStream bos = new BinaryOutputStream(os,
+		// WRITE_BYTE_ORDER);
 		//
-		//        writeImageFileHeader(bos, WRITE_BYTE_ORDER);
+		// writeImageFileHeader(bos, WRITE_BYTE_ORDER);
 
-		//        ArrayList directoryFields = new ArrayList();
+		// ArrayList directoryFields = new ArrayList();
 
-		final int photometricInterpretation = 2; // TODO: 
+		final int photometricInterpretation = 2; // TODO:
 
 		int compression = TIFF_COMPRESSION_LZW; // LZW is default
 		if (params.containsKey(PARAM_KEY_COMPRESSION))
@@ -303,18 +304,18 @@
 			params.remove(PARAM_KEY_COMPRESSION);
 		}
 
-		final int samplesPerPixel = 3; // TODO: 
-		final int bitsPerSample = 8; // TODO: 
+		final int samplesPerPixel = 3; // TODO:
+		final int bitsPerSample = 8; // TODO:
 
-		//        int fRowsPerStrip; // TODO: 
-		int rowsPerStrip = 8000 / (width * samplesPerPixel); // 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;
+		// int stripCount = (height + fRowsPerStrip - 1) / fRowsPerStrip;
+		// int stripCount = strips.length;
 
 		if (params.size() > 0)
 		{
@@ -322,18 +323,17 @@
 			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);
+		// 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)
+		} else if (compression == TIFF_COMPRESSION_LZW)
 		{
 			for (int i = 0; i < strips.length; i++)
 			{
@@ -347,12 +347,10 @@
 
 				strips[i] = compressed;
 			}
-		}
-		else if (compression == TIFF_COMPRESSION_UNCOMPRESSED)
+		} else if (compression == TIFF_COMPRESSION_UNCOMPRESSED)
 		{
 			// do nothing.
-		}
-		else
+		} else
 			throw new ImageWriteException(
 					"Invalid compression parameter (Only LZW, Packbits and uncompressed supported).");
 
@@ -361,85 +359,79 @@
 			imageData[i] = new TiffImageData.Data(0, strips[i].length,
 					strips[i]);
 
-		//        int stripOffsets[] = new int[stripCount];
-		//        int stripByteCounts[] = new int[stripCount];
+		// int stripOffsets[] = new int[stripCount];
+		// int stripByteCounts[] = new int[stripCount];
 		//
-		//        for (int i = 0; i < strips.length; i++)
-		//            stripByteCounts[i] = strips[i].length;
+		// for (int i = 0; i < strips.length; i++)
+		// stripByteCounts[i] = strips[i].length;
 
 		TiffOutputSet outputSet = new TiffOutputSet(byteOrder);
 		TiffOutputDirectory directory = outputSet.addRootDirectory();
 
-		//        WriteField stripOffsetsField;
+		// WriteField stripOffsetsField;
 
 		{
 			{
 				TiffOutputField field = new TiffOutputField(
 						TIFF_TAG_IMAGE_WIDTH, FIELD_TYPE_LONG, 1,
-						FIELD_TYPE_LONG.writeData(new int[]{
-							width,
-						}, byteOrder));
+						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));
+						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));
+						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));
+						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));
+						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);
-			//            }
+						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));
+						FIELD_TYPE_LONG.writeData(new int[] { rowsPerStrip, },
+								byteOrder));
 				directory.add(field);
 			}
 
@@ -447,9 +439,8 @@
 				int resolutionUnit = 2;// inches.
 				TiffOutputField field = new TiffOutputField(
 						TIFF_TAG_RESOLUTION_UNIT, FIELD_TYPE_SHORT, 1,
-						FIELD_TYPE_SHORT.writeData(new int[]{
-							resolutionUnit,
-						}, byteOrder));
+						FIELD_TYPE_SHORT.writeData(
+								new int[] { resolutionUnit, }, byteOrder));
 				directory.add(field);
 			}
 
@@ -471,6 +462,15 @@
 				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,

Added: incubator/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xmp/XmpUpdateTest.java
URL: http://svn.apache.org/viewvc/incubator/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xmp/XmpUpdateTest.java?rev=680193&view=auto
==============================================================================
--- incubator/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xmp/XmpUpdateTest.java (added)
+++ incubator/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xmp/XmpUpdateTest.java Sun Jul 27 14:34:36 2008
@@ -0,0 +1,103 @@
+/*
+ * 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.xmp;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sanselan.ImageFormat;
+import org.apache.sanselan.ImageReadException;
+import org.apache.sanselan.ImageWriteException;
+import org.apache.sanselan.Sanselan;
+import org.apache.sanselan.SanselanTest;
+import org.apache.sanselan.util.Debug;
+
+public class XmpUpdateTest extends SanselanTest
+{
+
+	public void test() throws IOException, ImageReadException,
+			ImageWriteException
+	{
+		List images = getTestImages();
+		for (int i = 0; i < images.size(); i++)
+		{
+			if (i % 10 == 0)
+				Debug.purgeMemory();
+
+			File imageFile = (File) images.get(i);
+			Debug.debug("imageFile", imageFile);
+			Debug.debug();
+
+			ImageFormat imageFormat = Sanselan.guessFormat(imageFile);
+
+			String xmpXml = Sanselan.getXmpXml(imageFile);
+			if (null == xmpXml
+					&& imageFormat.equals(ImageFormat.IMAGE_FORMAT_GIF))
+				xmpXml = "temporary test until I can locate a GIF with XMP in the wild.";
+			if (null == xmpXml)
+				continue;
+
+			assertNotNull(xmpXml);
+
+			if (imageFormat.equals(ImageFormat.IMAGE_FORMAT_PNG))
+				;
+			else if (imageFormat.equals(ImageFormat.IMAGE_FORMAT_TIFF))
+				;
+			else if (imageFormat.equals(ImageFormat.IMAGE_FORMAT_GIF))
+				;
+			else
+				continue;
+
+			File tempFile = this.createTempFile(imageFile.getName() + ".", "."
+					+ imageFormat.extension);
+			BufferedImage image = Sanselan.getBufferedImage(imageFile);
+
+			// ----
+
+			String testSrc = "<test>hi</test>";
+			Map params = new HashMap();
+			params.put(PARAM_KEY_XMP_XML, testSrc);
+			Sanselan.writeImage(image, tempFile, imageFormat, params);
+
+			String testDst = Sanselan.getXmpXml(tempFile);
+
+			assertNotNull(testDst);
+
+			assertEquals(testDst, testSrc);
+
+			// ----
+
+			params.put(PARAM_KEY_XMP_XML, xmpXml);
+			Sanselan.writeImage(image, tempFile, imageFormat, params);
+
+			String xmpXmlOut = Sanselan.getXmpXml(tempFile);
+
+			assertNotNull(xmpXmlOut);
+
+			assertEquals(xmpXmlOut, xmpXml);
+
+//			Debug.debug("xmpXmlOut", xmpXmlOut.length());
+			// Debug.debug("xmpXml", xmpXml);
+			// Debug.debug();
+		}
+	}
+}

Propchange: incubator/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xmp/XmpUpdateTest.java
------------------------------------------------------------------------------
    svn:eol-style = native