Author: damjan
Date: Sat Apr 9 14:20:38 2011
New Revision: 1090599
URL: http://svn.apache.org/viewvc?rev=1090599&view=rev
Log:
Add support for reading and writing XPM images, and some tests.
Support for reading rgb.txt colours is provided, but the necessary
rgb.txt file is not yet included because it's under a different
license.
Added:
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/xpm/XpmImageParser.java
(with props)
commons/proper/sanselan/trunk/src/test/data/images/xpm/
commons/proper/sanselan/trunk/src/test/data/images/xpm/1/
commons/proper/sanselan/trunk/src/test/data/images/xpm/1/Oregon Scientific
DS6639 - DSC_0307 - small.xpm (with props)
commons/proper/sanselan/trunk/src/test/data/images/xpm/1/info.txt (with
props)
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/XpmBaseTest.java
(with props)
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/XpmReadTest.java
(with props)
Modified:
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageFormat.java
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageParser.java
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/common/byteSources/ByteSourceImageTest.java
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java
Modified:
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageFormat.java
URL:
http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageFormat.java?rev=1090599&r1=1090598&r2=1090599&view=diff
==============================================================================
---
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageFormat.java
(original)
+++
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageFormat.java
Sat Apr 9 14:20:38 2011
@@ -77,6 +77,7 @@ public class ImageFormat
public static final ImageFormat IMAGE_FORMAT_PCX = new ImageFormat("PCX");
public static final ImageFormat IMAGE_FORMAT_DCX = new ImageFormat("DCX");
public static final ImageFormat IMAGE_FORMAT_XBM = new ImageFormat("XBM");
+ public static final ImageFormat IMAGE_FORMAT_XPM = new ImageFormat("XPM");
public static final ImageFormat[] getAllFormats()
{
@@ -87,6 +88,7 @@ public class ImageFormat
IMAGE_FORMAT_PPM, IMAGE_FORMAT_PNM, IMAGE_FORMAT_TGA,
IMAGE_FORMAT_JBIG2, IMAGE_FORMAT_ICNS, IMAGE_FORMAT_WBMP,
IMAGE_FORMAT_PCX, IMAGE_FORMAT_DCX, IMAGE_FORMAT_XBM,
+ IMAGE_FORMAT_XPM,
};
return result;
Modified:
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageParser.java
URL:
http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageParser.java?rev=1090599&r1=1090598&r2=1090599&view=diff
==============================================================================
---
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageParser.java
(original)
+++
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageParser.java
Sat Apr 9 14:20:38 2011
@@ -46,6 +46,7 @@ import org.apache.sanselan.formats.psd.P
import org.apache.sanselan.formats.tiff.TiffImageParser;
import org.apache.sanselan.formats.wbmp.WbmpImageParser;
import org.apache.sanselan.formats.xbm.XbmImageParser;
+import org.apache.sanselan.formats.xpm.XpmImageParser;
import org.apache.sanselan.util.Debug;
public abstract class ImageParser extends BinaryFileParser implements
@@ -60,7 +61,7 @@ public abstract class ImageParser extend
new PNMImageParser(), new IcoImageParser(),
new IcnsImageParser(), new WbmpImageParser(),
new PcxImageParser(), new DcxImageParser(),
- new XbmImageParser(),
+ new XbmImageParser(), new XpmImageParser(),
// new JBig2ImageParser(),
// new TgaImageParser(),
};
Added:
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/xpm/XpmImageParser.java
URL:
http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/xpm/XpmImageParser.java?rev=1090599&view=auto
==============================================================================
---
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/xpm/XpmImageParser.java
(added)
+++
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/xpm/XpmImageParser.java
Sat Apr 9 14:20:38 2011
@@ -0,0 +1,919 @@
+/*
+ * Licensed 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.
+ * under the License.
+ */
+
+package org.apache.sanselan.formats.xpm;
+
+import java.awt.Dimension;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DirectColorModel;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+import org.apache.sanselan.ImageFormat;
+import org.apache.sanselan.ImageInfo;
+import org.apache.sanselan.ImageParser;
+import org.apache.sanselan.ImageReadException;
+import org.apache.sanselan.ImageWriteException;
+import org.apache.sanselan.common.BasicCParser;
+import org.apache.sanselan.common.IImageMetadata;
+import org.apache.sanselan.common.byteSources.ByteSource;
+import org.apache.sanselan.palette.PaletteFactory;
+import org.apache.sanselan.palette.SimplePalette;
+import org.apache.sanselan.util.Debug;
+
+public class XpmImageParser extends ImageParser
+{
+ private static Map colorNames = null;
+
+ public XpmImageParser()
+ {
+ }
+
+ private synchronized static boolean loadColorNames()
+ {
+ if (colorNames != null)
+ return true;
+
+ BufferedReader reader = null;
+ try
+ {
+ InputStream rgbTxtStream =
XpmImageParser.class.getResourceAsStream(
+ "/rgb.txt");
+ if (rgbTxtStream == null) {
+ return false;
+ }
+ reader = new BufferedReader(new InputStreamReader(rgbTxtStream,
+ "US-ASCII"));
+ Map colors = new HashMap();
+ String line;
+ while ((line = reader.readLine()) != null)
+ {
+ if (line.startsWith("!"))
+ continue;
+ try
+ {
+ int red = Integer.parseInt(line.substring(0, 3));
+ int green = Integer.parseInt(line.substring(4, 7));
+ int blue = Integer.parseInt(line.substring(8, 11));
+ String colorName = line.substring(11).trim();
+ colors.put(colorName, new Integer(0xff000000 |
+ (red << 16) | (green << 8) | blue));
+ }
+ catch (NumberFormatException nfe)
+ {
+ }
+ }
+ colorNames = colors;
+ return true;
+ }
+ catch (IOException ioException)
+ {
+ Debug.debug(ioException);
+ return false;
+ }
+ finally
+ {
+ try
+ {
+ if (reader != null)
+ reader.close();
+ }
+ catch (IOException ignored)
+ {
+ }
+ }
+ }
+
+ public String getName()
+ {
+ return "Xpm-Custom";
+ }
+
+ public String getDefaultExtension()
+ {
+ return DEFAULT_EXTENSION;
+ }
+ private static final String DEFAULT_EXTENSION = ".xpm";
+ private static final String ACCEPTED_EXTENSIONS[] =
+ {
+ ".xpm",
+ };
+
+ protected String[] getAcceptedExtensions()
+ {
+ return ACCEPTED_EXTENSIONS;
+ }
+
+ protected ImageFormat[] getAcceptedTypes()
+ {
+ return new ImageFormat[]
+ {
+ ImageFormat.IMAGE_FORMAT_XPM, //
+ };
+ }
+
+ public boolean embedICCProfile(File src, File dst, byte profile[])
+ {
+ return false;
+ }
+
+ public IImageMetadata getMetadata(ByteSource byteSource, Map params)
+ throws ImageReadException, IOException
+ {
+ return null;
+ }
+
+ public ImageInfo getImageInfo(ByteSource byteSource, Map params)
+ throws ImageReadException, IOException
+ {
+ XpmHeader xpmHeader = readXpmHeader(byteSource);
+ boolean isTransparent = false;
+ int colorType = ImageInfo.COLOR_TYPE_BW;
+ for (Iterator it = xpmHeader.palette.entrySet().iterator();
it.hasNext();)
+ {
+ Map.Entry entry = (Map.Entry)it.next();
+ PaletteEntry paletteEntry = (PaletteEntry)entry.getValue();
+ if ((paletteEntry.getBestARGB() & 0xff000000) != 0xff000000)
+ isTransparent = true;
+ if (paletteEntry.haveColor)
+ colorType = ImageInfo.COLOR_TYPE_RGB;
+ else if (colorType != ImageInfo.COLOR_TYPE_RGB &&
+ (paletteEntry.haveGray || paletteEntry.haveGray4Level))
+ colorType = ImageInfo.COLOR_TYPE_GRAYSCALE;
+ }
+ return new ImageInfo("XPM version 3", xpmHeader.numCharsPerPixel * 8,
+ new ArrayList(), ImageFormat.IMAGE_FORMAT_XPM,
+ "X PixMap",
+ xpmHeader.height, "image/x-xpixmap", 1,
+ 0, 0, 0, 0,
+ xpmHeader.width, false, isTransparent, true,
+ colorType,
+ ImageInfo.COMPRESSION_ALGORITHM_NONE);
+ }
+
+ public Dimension getImageSize(ByteSource byteSource,
+ Map params)
+ throws ImageReadException, IOException
+ {
+ XpmHeader xpmHeader = readXpmHeader(byteSource);
+ return new Dimension(xpmHeader.width, xpmHeader.height);
+ }
+
+ public byte[] getICCProfileBytes(ByteSource byteSource,
+ Map params)
+ throws ImageReadException, IOException
+ {
+ return null;
+ }
+
+ private static class XpmHeader
+ {
+ int width;
+ int height;
+ int numColors;
+ int numCharsPerPixel;
+ int xHotSpot = -1;
+ int yHotSpot = -1;
+ boolean xpmExt;
+
+ Map palette = new HashMap();
+
+ public XpmHeader(int width, int height, int numColors,
+ int numCharsPerPixel, int xHotSpot, int yHotSpot,
+ boolean xpmExt)
+ {
+ this.width = width;
+ this.height = height;
+ this.numColors = numColors;
+ this.numCharsPerPixel = numCharsPerPixel;
+ this.xHotSpot = xHotSpot;
+ this.yHotSpot = yHotSpot;
+ this.xpmExt = xpmExt;
+ }
+
+ public void dump(PrintWriter pw)
+ {
+ pw.println("XpmHeader");
+ pw.println("Width: " + width);
+ pw.println("Height: " + height);
+ pw.println("NumColors: " + numColors);
+ pw.println("NumCharsPerPixel: " + numCharsPerPixel);
+ if (xHotSpot != -1 && yHotSpot != -1)
+ {
+ pw.println("X hotspot: " + xHotSpot);
+ pw.println("Y hotspot: " + yHotSpot);
+ }
+ pw.println("XpmExt: " + xpmExt);
+ }
+ }
+
+ private static class PaletteEntry
+ {
+ int index;
+ boolean haveColor = false;
+ int colorArgb;
+ boolean haveGray = false;
+ int grayArgb;
+ boolean haveGray4Level = false;
+ int gray4LevelArgb;
+ boolean haveMono = false;
+ int monoArgb;
+ String symbolicName = null;
+
+ int getBestARGB()
+ {
+ if (haveColor)
+ return colorArgb;
+ else if (haveGray)
+ return grayArgb;
+ else if (haveGray4Level)
+ return gray4LevelArgb;
+ else if (haveMono)
+ return monoArgb;
+ else
+ return 0x00000000;
+ }
+ }
+
+ private static class XpmParseResult
+ {
+ XpmHeader xpmHeader;
+ BasicCParser cParser;
+ }
+
+ private XpmHeader readXpmHeader(ByteSource byteSource)
+ throws ImageReadException, IOException
+ {
+ XpmParseResult result = parseXpmHeader(byteSource);
+ return result.xpmHeader;
+ }
+
+ private XpmParseResult parseXpmHeader(ByteSource byteSource)
+ throws ImageReadException, IOException
+ {
+ InputStream is = null;
+ try
+ {
+ is = byteSource.getInputStream();
+ StringBuilder firstComment = new StringBuilder();
+ ByteArrayOutputStream preprocessedFile = BasicCParser.preprocess(
+ is, firstComment, null);
+ if (!firstComment.toString().trim().equals("XPM"))
+ throw new ImageReadException("Parsing XPM file failed, " +
+ "signature isn't '/* XPM */'");
+
+ XpmParseResult xpmParseResult = new XpmParseResult();
+ xpmParseResult.cParser = new BasicCParser(
+ new ByteArrayInputStream(preprocessedFile.toByteArray()));
+ xpmParseResult.xpmHeader = parseXpmHeader(xpmParseResult.cParser);
+ return xpmParseResult;
+ }
+ finally
+ {
+ try
+ {
+ if (is != null)
+ is.close();
+ }
+ catch (IOException ignored)
+ {
+ }
+ }
+ }
+
+ private boolean parseNextString(BasicCParser cParser, StringBuilder
stringBuilder)
+ throws IOException, ImageReadException
+ {
+ stringBuilder.setLength(0);
+ String token = cParser.nextToken();
+ if (token.charAt(0) != '"')
+ throw new ImageReadException("Parsing XPM file failed, " +
+ "no string found where expected");
+ BasicCParser.unescapeString(stringBuilder, token);
+ for (token = cParser.nextToken(); token.charAt(0) == '"'; token =
cParser.nextToken())
+ {
+ BasicCParser.unescapeString(stringBuilder, token);
+ }
+ if (token.equals(","))
+ return true;
+ else if (token.equals("}"))
+ return false;
+ else
+ throw new ImageReadException("Parsing XPM file failed, " +
+ "no ',' or '}' found where expected");
+ }
+
+ private XpmHeader parseXpmValuesSection(String row)
+ throws ImageReadException
+ {
+ String[] tokens = BasicCParser.tokenizeRow(row);
+ if (tokens.length < 4 && tokens.length > 7)
+ throw new ImageReadException("Parsing XPM file failed, " +
+ "<Values> section has incorrect tokens");
+ try
+ {
+ int width = Integer.parseInt(tokens[0]);
+ int height = Integer.parseInt(tokens[1]);
+ int numColors = Integer.parseInt(tokens[2]);
+ int numCharsPerPixel = Integer.parseInt(tokens[3]);
+ int xHotSpot = -1;
+ int yHotSpot = -1;
+ boolean xpmExt = false;
+ if (tokens.length >= 6)
+ {
+ xHotSpot = Integer.parseInt(tokens[4]);
+ yHotSpot = Integer.parseInt(tokens[5]);
+ }
+ if (tokens.length == 5 || tokens.length == 7)
+ {
+ if (tokens[tokens.length-1].equals("XPMEXT"))
+ xpmExt = true;
+ else
+ throw new ImageReadException("Parsing XPM file failed, " +
+ "can't parse <Values> section XPMEXT");
+ }
+ return new XpmHeader(width, height, numColors, numCharsPerPixel,
+ xHotSpot, yHotSpot, xpmExt);
+ }
+ catch (NumberFormatException nfe)
+ {
+ throw new ImageReadException("Parsing XPM file failed, " +
+ "error parsing <Values> section", nfe);
+ }
+ }
+
+ private int parseColor(String color)
+ throws ImageReadException
+ {
+ if (color.charAt(0) == '#')
+ {
+ color = color.substring(1);
+ if (color.length() == 3)
+ {
+ int red = Integer.parseInt(color.substring(0, 1), 16);
+ int green = Integer.parseInt(color.substring(1, 2), 16);
+ int blue = Integer.parseInt(color.substring(2, 3), 16);
+ return 0xff000000 | (red << 20) | (green << 12) | (blue << 4);
+ }
+ else if (color.length() == 6)
+ return 0xff000000 | Integer.parseInt(color, 16);
+ else if (color.length() == 9)
+ {
+ int red = Integer.parseInt(color.substring(0, 1), 16);
+ int green = Integer.parseInt(color.substring(3, 4), 16);
+ int blue = Integer.parseInt(color.substring(6, 7), 16);
+ return 0xff000000 | (red << 16) | (green << 8) | blue;
+ }
+ else if (color.length() == 12)
+ {
+ int red = Integer.parseInt(color.substring(0, 1), 16);
+ int green = Integer.parseInt(color.substring(4, 5), 16);
+ int blue = Integer.parseInt(color.substring(8, 9), 16);
+ return 0xff000000 | (red << 16) | (green << 8) | blue;
+ }
+ else
+ return 0x00000000;
+ }
+ else if (color.charAt(0) == '%')
+ {
+ throw new ImageReadException("HSV colors are not implemented " +
+ "even in the XPM specification!");
+ }
+ else if (color.equals("None"))
+ return 0x00000000;
+ else
+ {
+ if (!loadColorNames())
+ return 0x00000000;
+ if (colorNames.containsKey(color))
+ return ((Integer)colorNames.get(color)).intValue();
+ else
+ return 0x00000000;
+ }
+ }
+
+ private void parsePaletteEntries(XpmHeader xpmHeader, BasicCParser cParser)
+ throws IOException, ImageReadException
+ {
+ StringBuilder row = new StringBuilder();
+ for (int i = 0; i < xpmHeader.numColors; i++)
+ {
+ row.setLength(0);
+ boolean hasMore = parseNextString(cParser, row);
+ if (!hasMore)
+ throw new ImageReadException("Parsing XPM file failed, " +
+ "file ended while reading palette");
+ String name = row.substring(0, xpmHeader.numCharsPerPixel);
+ String[] tokens = BasicCParser.tokenizeRow(
+ row.substring(xpmHeader.numCharsPerPixel));
+ PaletteEntry paletteEntry = new PaletteEntry();
+ paletteEntry.index = i;
+ int previousKeyIndex = Integer.MIN_VALUE;
+ StringBuilder colorBuffer = new StringBuilder();
+ for (int j = 0; j < tokens.length; j++)
+ {
+ String token = tokens[j];
+ boolean isKey = false;
+ if (previousKeyIndex < (j - 1))
+ {
+ if (token.equals("m") || token.equals("g4") ||
+ token.equals("g") || token.equals("c") ||
+ token.equals("s"))
+ isKey = true;
+ }
+ if (isKey)
+ {
+ if (previousKeyIndex >= 0)
+ {
+ String key = tokens[previousKeyIndex];
+ String color = colorBuffer.toString();
+ colorBuffer.setLength(0);
+ if (key.equals("m"))
+ {
+ paletteEntry.monoArgb = parseColor(color);
+ paletteEntry.haveMono = true;
+ }
+ else if (key.equals("g4"))
+ {
+ paletteEntry.gray4LevelArgb = parseColor(color);
+ paletteEntry.haveGray4Level = true;
+ }
+ else if (key.equals("g"))
+ {
+ paletteEntry.grayArgb = parseColor(color);
+ paletteEntry.haveGray = true;
+ }
+ else if (key.equals("s"))
+ {
+ paletteEntry.symbolicName = color;
+ }
+ else if (key.equals("c"))
+ {
+ paletteEntry.colorArgb = parseColor(color);
+ paletteEntry.haveColor = true;
+ }
+ }
+ previousKeyIndex = j;
+ }
+ else
+ {
+ if (previousKeyIndex < 0)
+ break;
+ if (colorBuffer.length() > 0)
+ colorBuffer.append(' ');
+ colorBuffer.append(token);
+ }
+ }
+ if (previousKeyIndex >= 0 && colorBuffer.length() > 0)
+ {
+ String key = tokens[previousKeyIndex];
+ String color = colorBuffer.toString();
+ colorBuffer.setLength(0);
+ if (key.equals("m"))
+ {
+ paletteEntry.monoArgb = parseColor(color);
+ paletteEntry.haveMono = true;
+ }
+ else if (key.equals("g4"))
+ {
+ paletteEntry.gray4LevelArgb = parseColor(color);
+ paletteEntry.haveGray4Level = true;
+ }
+ else if (key.equals("g"))
+ {
+ paletteEntry.grayArgb = parseColor(color);
+ paletteEntry.haveGray = true;
+ }
+ else if (key.equals("s"))
+ {
+ paletteEntry.symbolicName = color;
+ }
+ else if (key.equals("c"))
+ {
+ paletteEntry.colorArgb = parseColor(color);
+ paletteEntry.haveColor = true;
+ }
+ }
+ xpmHeader.palette.put(name, paletteEntry);
+ }
+ }
+
+ private XpmHeader parseXpmHeader(BasicCParser cParser)
+ throws ImageReadException, IOException
+ {
+ String name;
+ String token;
+ token = cParser.nextToken();
+ if (token == null || !token.equals("static"))
+ throw new ImageReadException("Parsing XPM file failed, no 'static'
token");
+ token = cParser.nextToken();
+ if (token == null || !token.equals("char"))
+ throw new ImageReadException("Parsing XPM file failed, no 'char'
token");
+ token = cParser.nextToken();
+ if (token == null || !token.equals("*"))
+ throw new ImageReadException("Parsing XPM file failed, no '*'
token");
+ name = cParser.nextToken();
+ if (name == null)
+ throw new ImageReadException("Parsing XPM file failed, no variable
name");
+ if (name.charAt(0) != '_' && !Character.isLetter(name.charAt(0)))
+ throw new ImageReadException("Parsing XPM file failed, variable
name " +
+ "doesn't start with letter or underscore");
+ for (int i = 0; i < name.length(); i++)
+ {
+ char c = name.charAt(i);
+ if (!Character.isLetterOrDigit(c) && c != '_')
+ throw new ImageReadException("Parsing XPM file failed,
variable name " +
+ "contains non-letter non-digit non-underscore");
+ }
+ token = cParser.nextToken();
+ if (token == null || !token.equals("["))
+ throw new ImageReadException("Parsing XPM file failed, no '['
token");
+ token = cParser.nextToken();
+ if (token == null || !token.equals("]"))
+ throw new ImageReadException("Parsing XPM file failed, no ']'
token");
+ token = cParser.nextToken();
+ if (token == null || !token.equals("="))
+ throw new ImageReadException("Parsing XPM file failed, no '='
token");
+ token = cParser.nextToken();
+ if (token == null || !token.equals("{"))
+ throw new ImageReadException("Parsing XPM file failed, no '{'
token");
+
+ StringBuilder row = new StringBuilder();
+ boolean hasMore = parseNextString(cParser, row);
+ if (!hasMore)
+ throw new ImageReadException("Parsing XPM file failed, " +
+ "file too short");
+ XpmHeader xpmHeader = parseXpmValuesSection(row.toString());
+ parsePaletteEntries(xpmHeader, cParser);
+ return xpmHeader;
+ }
+
+ private BufferedImage readXpmImage(XpmHeader xpmHeader, BasicCParser
cParser)
+ throws ImageReadException, IOException
+ {
+ ColorModel colorModel;
+ WritableRaster raster;
+ int bpp;
+ if (xpmHeader.palette.size() <= (1 << 8))
+ {
+ int[] palette = new int[xpmHeader.palette.size()];
+ for (Iterator it = xpmHeader.palette.entrySet().iterator();
it.hasNext();)
+ {
+ Map.Entry entry = (Map.Entry)it.next();
+ PaletteEntry paletteEntry = (PaletteEntry)entry.getValue();
+ palette[paletteEntry.index] = paletteEntry.getBestARGB();
+ }
+ colorModel = new IndexColorModel(8, xpmHeader.palette.size(),
+ palette, 0, true, -1, DataBuffer.TYPE_BYTE);
+ raster =
WritableRaster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
+ xpmHeader.width, xpmHeader.height, 1, null);
+ bpp = 8;
+ }
+ else if (xpmHeader.palette.size() <= (1 << 16))
+ {
+ int[] palette = new int[xpmHeader.palette.size()];
+ for (Iterator it = xpmHeader.palette.entrySet().iterator();
it.hasNext();)
+ {
+ Map.Entry entry = (Map.Entry)it.next();
+ PaletteEntry paletteEntry = (PaletteEntry)entry.getValue();
+ palette[paletteEntry.index] = paletteEntry.getBestARGB();
+ }
+ colorModel = new IndexColorModel(16, xpmHeader.palette.size(),
+ palette, 0, true, -1, DataBuffer.TYPE_USHORT);
+ raster =
WritableRaster.createInterleavedRaster(DataBuffer.TYPE_USHORT,
+ xpmHeader.width, xpmHeader.height, 1, null);
+ bpp = 16;
+ }
+ else
+ {
+ colorModel = new DirectColorModel(32, 0x00ff0000, 0x0000ff00,
+ 0x000000ff, 0xff000000);
+ raster = WritableRaster.createPackedRaster(DataBuffer.TYPE_INT,
+ xpmHeader.width, xpmHeader.height,
+ new int[]{0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000},
null);
+ bpp = 32;
+ }
+
+ BufferedImage image = new BufferedImage(colorModel, raster,
+ colorModel.isAlphaPremultiplied(), new Properties());
+ DataBuffer dataBuffer = raster.getDataBuffer();
+ StringBuilder row = new StringBuilder();
+ boolean hasMore = true;
+ for (int y = 0; y < xpmHeader.height; y++)
+ {
+ row.setLength(0);
+ hasMore = parseNextString(cParser, row);
+ if (y < (xpmHeader.height - 1) && !hasMore)
+ throw new ImageReadException("Parsing XPM file failed, " +
+ "insufficient image rows in file");
+ int rowOffset = y*xpmHeader.width;
+ for (int x = 0; x < xpmHeader.width; x++)
+ {
+ String index = row.substring(x*xpmHeader.numCharsPerPixel,
+ (x + 1)*xpmHeader.numCharsPerPixel);
+ PaletteEntry paletteEntry = (PaletteEntry)
xpmHeader.palette.get(index);
+ if (paletteEntry == null) {
+ throw new ImageReadException("No palette entry was defined
" +
+ "for " + index);
+ }
+ if (bpp <= 16)
+ dataBuffer.setElem(rowOffset + x, paletteEntry.index);
+ else
+ dataBuffer.setElem(rowOffset + x,
paletteEntry.getBestARGB());
+ }
+ }
+
+ while (hasMore)
+ {
+ row.setLength(0);
+ hasMore = parseNextString(cParser, row);
+ }
+
+ String token = cParser.nextToken();
+ if (!token.equals(";"))
+ throw new ImageReadException("Last token wasn't ';'");
+
+ return image;
+ }
+
+ public boolean dumpImageFile(PrintWriter pw, ByteSource byteSource)
+ throws ImageReadException, IOException
+ {
+ readXpmHeader(byteSource).dump(pw);
+ return true;
+ }
+
+ public final BufferedImage getBufferedImage(ByteSource byteSource,
+ Map params) throws ImageReadException, IOException
+ {
+ XpmParseResult result = parseXpmHeader(byteSource);
+ return readXpmImage(result.xpmHeader, result.cParser);
+ }
+
+ private String randomName()
+ {
+ UUID uuid = UUID.randomUUID();
+ StringBuilder stringBuilder = new StringBuilder("a");
+ long bits = uuid.getMostSignificantBits();
+ // Long.toHexString() breaks for very big numbers
+ for (int i = 64 - 8; i >= 0; i -= 8)
+ stringBuilder.append(Integer.toHexString((int)((bits >> i) &
0xff)));
+ bits = uuid.getLeastSignificantBits();
+ for (int i = 64 - 8; i >= 0; i -= 8)
+ stringBuilder.append(Integer.toHexString((int)((bits >> i) &
0xff)));
+ return stringBuilder.toString();
+ }
+
+ private String pixelsForIndex(int index, int charsPerPixel)
+ {
+ StringBuilder stringBuilder = new StringBuilder();
+ int highestPower = 1;
+ for (int i = 1; i < charsPerPixel; i++)
+ highestPower *= writePalette.length;
+ for (int i = 0; i < charsPerPixel; i++)
+ {
+ int multiple = index / highestPower;
+ index -= (multiple * highestPower);
+ highestPower /= writePalette.length;
+ stringBuilder.append(writePalette[multiple]);
+ }
+ return stringBuilder.toString();
+ }
+
+ private String toColor(int color)
+ {
+ String hex = Integer.toHexString(color);
+ if (hex.length() < 6)
+ {
+ char zeroes[] = new char[6 - hex.length()];
+ Arrays.fill(zeroes, '0');
+ return "#" + new String(zeroes) + hex;
+ }
+ else
+ return "#" + hex;
+ }
+
+ public void writeImage(BufferedImage src, OutputStream os, Map params)
+ throws ImageWriteException, IOException
+ {
+ // make copy of params; we'll clear keys as we consume them.
+ params = (params == null) ? new HashMap() : new HashMap(params);
+
+ // clear format key.
+ if (params.containsKey(PARAM_KEY_FORMAT))
+ params.remove(PARAM_KEY_FORMAT);
+
+ if (params.size() > 0)
+ {
+ Object firstKey = params.keySet().iterator().next();
+ throw new ImageWriteException("Unknown parameter: " + firstKey);
+ }
+
+ PaletteFactory paletteFactory = new PaletteFactory();
+ boolean hasTransparency = false;
+ if (paletteFactory.hasTransparency(src, 1))
+ hasTransparency = true;
+ SimplePalette palette = null;
+ int maxColors = writePalette.length;
+ int charsPerPixel = 1;
+ for (; palette == null; )
+ {
+ palette = paletteFactory.makePaletteSimple(src,
+ hasTransparency ? maxColors - 1 : maxColors);
+ if (palette == null)
+ {
+ maxColors *= writePalette.length;
+ charsPerPixel++;
+ }
+ }
+ int colors = palette.length();
+ if (hasTransparency)
+ ++colors;
+
+ String line = "/* XPM */\n";
+ os.write(line.getBytes("US-ASCII"));
+ line = "static char *" + randomName() + "[] = {\n";
+ os.write(line.getBytes("US-ASCII"));
+ line = "\"" + src.getWidth() +
+ " " + src.getHeight() +
+ " " + colors +
+ " " + charsPerPixel + "\",\n";
+ os.write(line.getBytes("US-ASCII"));
+
+ for (int i = 0; i < colors; i++)
+ {
+ String color;
+ if (i < palette.length())
+ color = toColor(palette.getEntry(i));
+ else
+ color = "None";
+ line = "\"" + pixelsForIndex(i, charsPerPixel) +
+ " c " + color + "\",\n";
+ os.write(line.getBytes("US-ASCII"));
+ }
+
+ String separator = "";
+ for (int y = 0; y < src.getHeight(); y++)
+ {
+ os.write(separator.getBytes("US-ASCII"));
+ separator = ",\n";
+ line = "\"";
+ os.write(line.getBytes("US-ASCII"));
+ for (int x = 0; x < src.getWidth(); x++)
+ {
+ int argb = src.getRGB(x, y);
+ if ((argb & 0xff000000) == 0)
+ line = pixelsForIndex(palette.length(), charsPerPixel);
+ else
+ line = pixelsForIndex(palette.getPaletteIndex(
+ 0xffffff & argb), charsPerPixel);
+ os.write(line.getBytes("US-ASCII"));
+ }
+ line = "\"";
+ os.write(line.getBytes("US-ASCII"));
+ }
+
+ line = "\n};\n";
+ os.write(line.getBytes("US-ASCII"));
+ }
+
+ private static final char writePalette[] = {
+ ' ',
+ '.',
+ 'X',
+ 'o',
+ 'O',
+ '+',
+ '@',
+ '#',
+ '$',
+ '%',
+ '&',
+ '*',
+ '=',
+ '-',
+ ';',
+ ':',
+ '>',
+ ',',
+ '<',
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ '0',
+ 'q',
+ 'w',
+ 'e',
+ 'r',
+ 't',
+ 'y',
+ 'u',
+ 'i',
+ 'p',
+ 'a',
+ 's',
+ 'd',
+ 'f',
+ 'g',
+ 'h',
+ 'j',
+ 'k',
+ 'l',
+ 'z',
+ 'x',
+ 'c',
+ 'v',
+ 'b',
+ 'n',
+ 'm',
+ 'M',
+ 'N',
+ 'B',
+ 'V',
+ 'C',
+ 'Z',
+ 'A',
+ 'S',
+ 'D',
+ 'F',
+ 'G',
+ 'H',
+ 'J',
+ 'K',
+ 'L',
+ 'P',
+ 'I',
+ 'U',
+ 'Y',
+ 'T',
+ 'R',
+ 'E',
+ 'W',
+ 'Q',
+ '!',
+ '~',
+ '^',
+ '/',
+ '(',
+ ')',
+ '_',
+ '`',
+ '\'',
+ ']',
+ '[',
+ '{',
+ '}',
+ '|',
+ };
+
+ /**
+ * Extracts embedded XML metadata as XML string.
+ * <p>
+ *
+ * @param byteSource
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in SanselanConstants.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ public String getXmpXml(ByteSource byteSource, Map params)
+ throws ImageReadException, IOException
+ {
+ return null;
+ }
+}
Propchange:
commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/xpm/XpmImageParser.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: commons/proper/sanselan/trunk/src/test/data/images/xpm/1/Oregon
Scientific DS6639 - DSC_0307 - small.xpm
URL:
http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/xpm/1/Oregon%20Scientific%20DS6639%20-%20DSC_0307%20-%20small.xpm?rev=1090599&view=auto
==============================================================================
Binary file - no diff available.
Propchange: commons/proper/sanselan/trunk/src/test/data/images/xpm/1/Oregon
Scientific DS6639 - DSC_0307 - small.xpm
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: commons/proper/sanselan/trunk/src/test/data/images/xpm/1/info.txt
URL:
http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/xpm/1/info.txt?rev=1090599&view=auto
==============================================================================
--- commons/proper/sanselan/trunk/src/test/data/images/xpm/1/info.txt (added)
+++ commons/proper/sanselan/trunk/src/test/data/images/xpm/1/info.txt Sat Apr
9 14:20:38 2011
@@ -0,0 +1,2 @@
+Contributed to the project by Damjan Jovanovic.
+Oregon Scientific DS6639 - DSC_0307 - small.xpm was converted from
../../bmp/1/Oregon Scientific DS6639 - DSC_0307 - small.bmp
Propchange: commons/proper/sanselan/trunk/src/test/data/images/xpm/1/info.txt
------------------------------------------------------------------------------
svn:eol-style = native
Modified:
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/common/byteSources/ByteSourceImageTest.java
URL:
http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/common/byteSources/ByteSourceImageTest.java?rev=1090599&r1=1090598&r2=1090599&view=diff
==============================================================================
---
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/common/byteSources/ByteSourceImageTest.java
(original)
+++
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/common/byteSources/ByteSourceImageTest.java
Sat Apr 9 14:20:38 2011
@@ -63,7 +63,8 @@ public class ByteSourceImageTest extends
|| imageFile.getName().toLowerCase().endsWith(".dcx")
|| imageFile.getName().toLowerCase().endsWith(".psd")
|| imageFile.getName().toLowerCase().endsWith(".wbmp")
- || imageFile.getName().toLowerCase().endsWith(".xbm"))
+ || imageFile.getName().toLowerCase().endsWith(".xbm")
+ || imageFile.getName().toLowerCase().endsWith(".xpm"))
{
// these formats can't be parsed without a filename hint.
// they have ambiguous "magic number" signatures.
Added:
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/XpmBaseTest.java
URL:
http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/XpmBaseTest.java?rev=1090599&view=auto
==============================================================================
---
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/XpmBaseTest.java
(added)
+++
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/XpmBaseTest.java
Sat Apr 9 14:20:38 2011
@@ -0,0 +1,43 @@
+/*
+ * Licensed 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.
+ * under the License.
+ */
+
+package org.apache.sanselan.formats.xpm;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import org.apache.sanselan.ImageReadException;
+import org.apache.sanselan.SanselanTest;
+
+public abstract class XpmBaseTest extends SanselanTest
+{
+
+ private static boolean isXpm(File file) throws IOException,
ImageReadException
+ {
+ return file.getName().toLowerCase().endsWith(".xpm");
+ }
+
+ private static final ImageFilter IMAGE_FILTER = new ImageFilter() {
+ public boolean accept(File file) throws IOException, ImageReadException
+ {
+ return isXpm(file);
+ }
+ };
+
+ protected List getXpmImages() throws IOException, ImageReadException
+ {
+ return getTestImages(IMAGE_FILTER);
+ }
+}
Propchange:
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/XpmBaseTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/XpmReadTest.java
URL:
http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/XpmReadTest.java?rev=1090599&view=auto
==============================================================================
---
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/XpmReadTest.java
(added)
+++
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/XpmReadTest.java
Sat Apr 9 14:20:38 2011
@@ -0,0 +1,58 @@
+/*
+ * Licensed 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.
+ * under the License.
+ */
+
+package org.apache.sanselan.formats.xpm;
+
+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.ImageInfo;
+import org.apache.sanselan.ImageReadException;
+import org.apache.sanselan.Sanselan;
+import org.apache.sanselan.common.IImageMetadata;
+import org.apache.sanselan.util.Debug;
+
+public class XpmReadTest extends XpmBaseTest
+{
+
+ public void test() throws IOException, ImageReadException
+ {
+ Debug.debug("start");
+
+ List images = getXpmImages();
+ for (int i = 0; i < images.size(); i++)
+ {
+ if (i % 10 == 0)
+ Debug.purgeMemory();
+
+ File imageFile = (File) images.get(i);
+ Debug.debug("imageFile", imageFile);
+
+ IImageMetadata metadata = Sanselan.getMetadata(imageFile);
+ // assertNotNull(metadata);
+
+ Map params = new HashMap();
+ ImageInfo imageInfo = Sanselan.getImageInfo(imageFile, params);
+ assertNotNull(imageInfo);
+
+ BufferedImage image = Sanselan.getBufferedImage(imageFile);
+ assertNotNull(image);
+ }
+ }
+
+}
Propchange:
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/xpm/XpmReadTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified:
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java
URL:
http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java?rev=1090599&r1=1090598&r2=1090599&view=diff
==============================================================================
---
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java
(original)
+++
commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java
Sat Apr 9 14:20:38 2011
@@ -94,6 +94,8 @@ public class RoundtripTest extends Sanse
COLOR_FULL_RGB, true), //
new FormatInfo(ImageFormat.IMAGE_FORMAT_XBM, true, true,
COLOR_BITMAP, false), //
+ new FormatInfo(ImageFormat.IMAGE_FORMAT_XPM, true, true,
+ COLOR_FULL_RGB, false), //
};
private BufferedImage createArgbBitmapImage(int width, int height)