blob: 71cdd177091db861941668752ebc9220995d9526 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.imaging.formats.pnm;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.commons.imaging.ImageFormat;
import org.apache.commons.imaging.ImageFormats;
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.ByteOrder;
import org.apache.commons.imaging.common.IImageMetadata;
import org.apache.commons.imaging.common.ImageBuilder;
import org.apache.commons.imaging.common.bytesource.ByteSource;
import org.apache.commons.imaging.palette.PaletteFactory;
import org.apache.commons.imaging.util.IoUtils;
public class PnmImageParser extends ImageParser implements PnmConstants {
public PnmImageParser() {
super.setByteOrder(ByteOrder.LITTLE_ENDIAN);
// setDebug(true);
}
@Override
public String getName() {
return "Pbm-Custom";
}
@Override
public String getDefaultExtension() {
return DEFAULT_EXTENSION;
}
private static final String DEFAULT_EXTENSION = ".pnm";
private static final String ACCEPTED_EXTENSIONS[] = { ".pbm", ".pgm",
".ppm", ".pnm", ".pam" };
@Override
protected String[] getAcceptedExtensions() {
return ACCEPTED_EXTENSIONS;
}
@Override
protected ImageFormat[] getAcceptedTypes() {
return new ImageFormat[] {
ImageFormats.PBM, //
ImageFormats.PGM, //
ImageFormats.PPM, //
ImageFormats.PNM,
ImageFormats.PAM
};
}
private FileInfo readHeader(final InputStream is) throws ImageReadException,
IOException {
final byte identifier1 = readByte("Identifier1", is, "Not a Valid PNM File");
final byte identifier2 = readByte("Identifier2", is, "Not a Valid PNM File");
if (identifier1 != PNM_PREFIX_BYTE) {
throw new ImageReadException("PNM file has invalid prefix byte 1");
}
final WhiteSpaceReader wsr = new WhiteSpaceReader(is);
if (identifier2 == PBM_TEXT_CODE ||
identifier2 == PBM_RAW_CODE ||
identifier2 == PGM_TEXT_CODE ||
identifier2 == PGM_RAW_CODE ||
identifier2 == PPM_TEXT_CODE ||
identifier2 == PPM_RAW_CODE) {
final int width = Integer.parseInt(wsr.readtoWhiteSpace());
final int height = Integer.parseInt(wsr.readtoWhiteSpace());
if (identifier2 == PBM_TEXT_CODE) {
return new PbmFileInfo(width, height, false);
} else if (identifier2 == PBM_RAW_CODE) {
return new PbmFileInfo(width, height, true);
} else if (identifier2 == PGM_TEXT_CODE) {
final int maxgray = Integer.parseInt(wsr.readtoWhiteSpace());
return new PgmFileInfo(width, height, false, maxgray);
} else if (identifier2 == PGM_RAW_CODE) {
final int maxgray = Integer.parseInt(wsr.readtoWhiteSpace());
return new PgmFileInfo(width, height, true, maxgray);
} else if (identifier2 == PPM_TEXT_CODE) {
final int max = Integer.parseInt(wsr.readtoWhiteSpace());
return new PpmFileInfo(width, height, false, max);
} else if (identifier2 == PPM_RAW_CODE) {
final int max = Integer.parseInt(wsr.readtoWhiteSpace());
return new PpmFileInfo(width, height, true, max);
} else {
throw new ImageReadException("PNM file has invalid header.");
}
} else if (identifier2 == PAM_RAW_CODE) {
int width = -1;
boolean seenWidth = false;
int height = -1;
boolean seenHeight = false;
int depth = -1;
boolean seenDepth = false;
int maxVal = -1;
boolean seenMaxVal = false;
String tupleType = "";
boolean seenTupleType = false;
// Advance to next line
wsr.readLine();
String line;
while ((line = wsr.readLine()) != null) {
line = line.trim();
if (line.startsWith("#")) {
continue;
}
final StringTokenizer tokenizer = new StringTokenizer(line, " ", false);
final String type = tokenizer.nextToken();
if (type.equals("WIDTH")) {
seenWidth = true;
width = Integer.parseInt(tokenizer.nextToken());
} else if (type.equals("HEIGHT")) {
seenHeight = true;
height = Integer.parseInt(tokenizer.nextToken());
} else if (type.equals("DEPTH")) {
seenDepth = true;
depth = Integer.parseInt(tokenizer.nextToken());
} else if (type.equals("MAXVAL")) {
seenMaxVal = true;
maxVal = Integer.parseInt(tokenizer.nextToken());
} else if (type.equals("TUPLTYPE")) {
seenTupleType = true;
tupleType += tokenizer.nextToken();
} else if (type.equals("ENDHDR")) {
break;
} else {
throw new ImageReadException("Invalid PAM file header type " + type);
}
}
if (!seenWidth) {
throw new ImageReadException("PAM header has no WIDTH");
} else if (!seenHeight) {
throw new ImageReadException("PAM header has no HEIGHT");
} else if (!seenDepth) {
throw new ImageReadException("PAM header has no DEPTH");
} else if (!seenMaxVal) {
throw new ImageReadException("PAM header has no MAXVAL");
} else if (!seenTupleType) {
throw new ImageReadException("PAM header has no TUPLTYPE");
}
return new PamFileInfo(width, height, depth, maxVal, tupleType);
} else {
throw new ImageReadException("PNM file has invalid prefix byte 2");
}
}
private FileInfo readHeader(final ByteSource byteSource)
throws ImageReadException, IOException {
InputStream is = null;
boolean canThrow = false;
try {
is = byteSource.getInputStream();
final FileInfo ret = readHeader(is);
canThrow = true;
return ret;
} finally {
IoUtils.closeQuietly(canThrow, is);
}
}
@Override
public byte[] getICCProfileBytes(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
return null;
}
@Override
public Dimension getImageSize(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
final FileInfo info = readHeader(byteSource);
if (info == null) {
throw new ImageReadException("PNM: Couldn't read Header");
}
return new Dimension(info.width, info.height);
}
public byte[] embedICCProfile(final byte image[], final byte profile[]) {
return null;
}
@Override
public boolean embedICCProfile(final File src, final File dst, final byte profile[]) {
return false;
}
@Override
public IImageMetadata getMetadata(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
return null;
}
@Override
public ImageInfo getImageInfo(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
final FileInfo info = readHeader(byteSource);
if (info == null) {
throw new ImageReadException("PNM: Couldn't read Header");
}
final List<String> Comments = new ArrayList<String>();
final int BitsPerPixel = info.getBitDepth() * info.getNumComponents();
final ImageFormat Format = info.getImageType();
final String FormatName = info.getImageTypeDescription();
final String MimeType = info.getMIMEType();
final int NumberOfImages = 1;
final boolean isProgressive = false;
// boolean isProgressive = (fPNGChunkIHDR.InterlaceMethod != 0);
//
final int PhysicalWidthDpi = 72;
final float PhysicalWidthInch = (float) ((double) info.width / (double) PhysicalWidthDpi);
final int PhysicalHeightDpi = 72;
final float PhysicalHeightInch = (float) ((double) info.height / (double) PhysicalHeightDpi);
final String FormatDetails = info.getImageTypeDescription();
final boolean isTransparent = info.hasAlpha();
final boolean usesPalette = false;
final int ColorType = info.getColorType();
final String compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_NONE;
final ImageInfo result = new ImageInfo(FormatDetails, BitsPerPixel, Comments,
Format, FormatName, info.height, MimeType, NumberOfImages,
PhysicalHeightDpi, PhysicalHeightInch, PhysicalWidthDpi,
PhysicalWidthInch, info.width, isProgressive, isTransparent,
usesPalette, ColorType, compressionAlgorithm);
return result;
}
@Override
public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
throws ImageReadException, IOException {
pw.println("pnm.dumpImageFile");
final ImageInfo imageData = getImageInfo(byteSource);
if (imageData == null) {
return false;
}
imageData.toString(pw, "");
pw.println("");
return true;
}
@Override
public BufferedImage getBufferedImage(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
InputStream is = null;
boolean canThrow = false;
try {
is = byteSource.getInputStream();
final FileInfo info = readHeader(is);
final int width = info.width;
final int height = info.height;
final boolean hasAlpha = info.hasAlpha();
final ImageBuilder imageBuilder = new ImageBuilder(width, height,
hasAlpha);
info.readImage(imageBuilder, is);
final BufferedImage ret = imageBuilder.getBufferedImage();
canThrow = true;
return ret;
} finally {
IoUtils.closeQuietly(canThrow, is);
}
}
public static final String PARAM_KEY_PNM_RAWBITS = "PNM_RAWBITS";
public static final String PARAM_VALUE_PNM_RAWBITS_YES = "YES";
public static final String PARAM_VALUE_PNM_RAWBITS_NO = "NO";
@Override
public void writeImage(final BufferedImage src, final OutputStream os, Map<String,Object> params)
throws ImageWriteException, IOException {
PnmWriter writer = null;
boolean useRawbits = true;
final boolean hasAlpha = new PaletteFactory().hasTransparency(src);
if (params != null) {
final Object useRawbitsParam = params.get(PARAM_KEY_PNM_RAWBITS);
if (useRawbitsParam != null) {
if (useRawbitsParam.equals(PARAM_VALUE_PNM_RAWBITS_NO)) {
useRawbits = false;
}
}
final Object subtype = params.get(PARAM_KEY_FORMAT);
if (subtype != null) {
if (subtype.equals(ImageFormats.PBM)) {
writer = new PbmWriter(useRawbits);
} else if (subtype.equals(ImageFormats.PGM)) {
writer = new PgmWriter(useRawbits);
} else if (subtype.equals(ImageFormats.PPM)) {
writer = new PpmWriter(useRawbits);
} else if (subtype.equals(ImageFormats.PAM)) {
writer = new PamWriter();
}
}
}
if (writer == null) {
if (hasAlpha) {
writer = new PamWriter();
} else {
writer = new PpmWriter(useRawbits);
}
}
// make copy of params; we'll clear keys as we consume them.
if (params != null) {
params = new HashMap<String,Object>(params);
} else {
params = new HashMap<String,Object>();
}
// clear format key.
if (params.containsKey(PARAM_KEY_FORMAT)) {
params.remove(PARAM_KEY_FORMAT);
}
if (params.size() > 0) {
final Object firstKey = params.keySet().iterator().next();
throw new ImageWriteException("Unknown parameter: " + firstKey);
}
writer.writeImage(src, os, params);
}
/**
* Extracts embedded XML metadata as XML string.
* <p>
*
* @param byteSource
* File containing image data.
* @param params
* Map of optional parameters, defined in ImagingConstants.
* @return Xmp Xml as String, if present. Otherwise, returns null.
*/
@Override
public String getXmpXml(final ByteSource byteSource, final Map<String,Object> params)
throws ImageReadException, IOException {
return null;
}
}