blob: 6eedbf3ccc8d094725362bdef0961da1cf944285 [file] [log] [blame]
/*
* 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.commons.imaging.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.commons.imaging.ImageFormat;
import org.apache.commons.imaging.ImageInfo;
import org.apache.commons.imaging.ImageParser;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.ImageWriteException;
import org.apache.commons.imaging.common.BasicCParser;
import org.apache.commons.imaging.common.IImageMetadata;
import org.apache.commons.imaging.common.bytesource.ByteSource;
import org.apache.commons.imaging.palette.PaletteFactory;
import org.apache.commons.imaging.palette.SimplePalette;
import org.apache.commons.imaging.util.Debug;
public class XpmImageParser extends ImageParser
{
private static Map<String, Integer> 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<String, Integer> colors = new HashMap<String, Integer>();
String line;
while ((line = reader.readLine()) != null)
{
if (line.startsWith("!"))
continue;
try
{
int red = Integer.parseInt(line.substring(0, 3).trim());
int green = Integer.parseInt(line.substring(4, 7).trim());
int blue = Integer.parseInt(line.substring(8, 11).trim());
String colorName = line.substring(11).trim();
colors.put(colorName, 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)
{
}
}
}
@Override
public String getName()
{
return "Xpm-Custom";
}
@Override
public String getDefaultExtension()
{
return DEFAULT_EXTENSION;
}
private static final String DEFAULT_EXTENSION = ".xpm";
private static final String ACCEPTED_EXTENSIONS[] =
{
".xpm",
};
@Override
protected String[] getAcceptedExtensions()
{
return ACCEPTED_EXTENSIONS;
}
@Override
protected ImageFormat[] getAcceptedTypes()
{
return new ImageFormat[]
{
ImageFormat.IMAGE_FORMAT_XPM, //
};
}
@Override
public boolean embedICCProfile(File src, File dst, byte profile[])
{
return false;
}
@Override
public IImageMetadata getMetadata(ByteSource byteSource, Map params)
throws ImageReadException, IOException
{
return null;
}
@Override
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<Map.Entry<Object,PaletteEntry>> it = xpmHeader.palette.entrySet().iterator(); it.hasNext();)
{
Map.Entry<Object, PaletteEntry> entry = it.next();
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<String>(), 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);
}
@Override
public Dimension getImageSize(ByteSource byteSource,
Map params)
throws ImageReadException, IOException
{
XpmHeader xpmHeader = readXpmHeader(byteSource);
return new Dimension(xpmHeader.width, xpmHeader.height);
}
@Override
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<Object, PaletteEntry> palette = new HashMap<Object, PaletteEntry>();
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 (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;
paletteEntry.colorArgb = parseColor(color);
paletteEntry.haveColor = true;
}
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;
paletteEntry.colorArgb = parseColor(color);
paletteEntry.haveColor = true;
}
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<Map.Entry<Object,PaletteEntry>> it = xpmHeader.palette.entrySet().iterator(); it.hasNext();)
{
Map.Entry<Object, PaletteEntry> entry = it.next();
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<Map.Entry<Object,PaletteEntry>> it = xpmHeader.palette.entrySet().iterator(); it.hasNext();)
{
Map.Entry<Object, PaletteEntry> entry = it.next();
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 = 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;
}
@Override
public boolean dumpImageFile(PrintWriter pw, ByteSource byteSource)
throws ImageReadException, IOException
{
readXpmHeader(byteSource).dump(pw);
return true;
}
@Override
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;
}
@Override
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.
*/
@Override
public String getXmpXml(ByteSource byteSource, Map params)
throws ImageReadException, IOException
{
return null;
}
}