PnmImageParser.java
/*
* 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 static org.apache.commons.imaging.common.BinaryFunctions.readByte;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.stream.Stream;
import org.apache.commons.imaging.AbstractImageParser;
import org.apache.commons.imaging.ImageFormat;
import org.apache.commons.imaging.ImageFormats;
import org.apache.commons.imaging.ImageInfo;
import org.apache.commons.imaging.ImagingException;
import org.apache.commons.imaging.bytesource.ByteSource;
import org.apache.commons.imaging.common.ImageBuilder;
import org.apache.commons.imaging.common.ImageMetadata;
import org.apache.commons.imaging.palette.PaletteFactory;
public class PnmImageParser extends AbstractImageParser<PnmImagingParameters> {
private static final String TOKEN_ENDHDR = "ENDHDR";
private static final String TOKEN_TUPLTYPE = "TUPLTYPE";
private static final String TOKEN_MAXVAL = "MAXVAL";
private static final String TOKEN_DEPTH = "DEPTH";
private static final String TOKEN_HEIGHT = "HEIGHT";
private static final String TOKEN_WIDTH = "WIDTH";
private static final int DPI = 72;
private static final ImageFormat[] IMAGE_FORMATS;
private static final String DEFAULT_EXTENSION = ImageFormats.PNM.getDefaultExtension();
private static final String[] ACCEPTED_EXTENSIONS;
static {
IMAGE_FORMATS = new ImageFormat[] {
// @formatter:off
ImageFormats.PAM,
ImageFormats.PBM,
ImageFormats.PGM,
ImageFormats.PNM,
ImageFormats.PPM
// @formatter:on
};
ACCEPTED_EXTENSIONS = Stream.of(IMAGE_FORMATS).map(ImageFormat::getDefaultExtension).toArray(String[]::new);
}
public PnmImageParser() {
super(ByteOrder.LITTLE_ENDIAN);
}
private void check(final boolean value, final String type) throws ImagingException {
if (!value) {
throw new ImagingException("PAM header has no " + type + " value");
}
}
private void checkFound(final int value, final String type) throws ImagingException {
check(value != -1, type);
}
private String checkNextTokens(final StringTokenizer tokenizer, final String type) throws ImagingException {
check(tokenizer.hasMoreTokens(), type);
return tokenizer.nextToken();
}
private int checkNextTokensAsInt(final StringTokenizer tokenizer, final String type) throws ImagingException {
return Integer.parseInt(checkNextTokens(tokenizer, type));
}
@Override
public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
pw.println("pnm.dumpImageFile");
final ImageInfo imageData = getImageInfo(byteSource);
if (imageData == null) {
return false;
}
imageData.toString(pw, "");
pw.println("");
return true;
}
@Override
protected String[] getAcceptedExtensions() {
return ACCEPTED_EXTENSIONS.clone();
}
@Override
protected ImageFormat[] getAcceptedTypes() {
return IMAGE_FORMATS.clone();
}
@Override
public BufferedImage getBufferedImage(final ByteSource byteSource, final PnmImagingParameters params) throws ImagingException, IOException {
try (InputStream is = byteSource.getInputStream()) {
final AbstractFileInfo 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);
return imageBuilder.getBufferedImage();
}
}
@Override
public String getDefaultExtension() {
return DEFAULT_EXTENSION;
}
@Override
public PnmImagingParameters getDefaultParameters() {
return new PnmImagingParameters();
}
@Override
public byte[] getIccProfileBytes(final ByteSource byteSource, final PnmImagingParameters params) throws ImagingException, IOException {
return null;
}
@Override
public ImageInfo getImageInfo(final ByteSource byteSource, final PnmImagingParameters params) throws ImagingException, IOException {
final AbstractFileInfo info = readHeader(byteSource);
final List<String> comments = new ArrayList<>();
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 progressive = false;
// boolean progressive = (fPNGChunkIHDR.InterlaceMethod != 0);
//
final int physicalWidthDpi = DPI;
final float physicalWidthInch = (float) ((double) info.width / (double) physicalWidthDpi);
final int physicalHeightDpi = DPI;
final float physicalHeightInch = (float) ((double) info.height / (double) physicalHeightDpi);
final String formatDetails = info.getImageTypeDescription();
final boolean transparent = info.hasAlpha();
final boolean usesPalette = false;
final ImageInfo.ColorType colorType = info.getColorType();
final ImageInfo.CompressionAlgorithm compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
return new ImageInfo(formatDetails, bitsPerPixel, comments, format, formatName, info.height, mimeType, numberOfImages, physicalHeightDpi,
physicalHeightInch, physicalWidthDpi, physicalWidthInch, info.width, progressive, transparent, usesPalette, colorType, compressionAlgorithm);
}
@Override
public Dimension getImageSize(final ByteSource byteSource, final PnmImagingParameters params) throws ImagingException, IOException {
final AbstractFileInfo info = readHeader(byteSource);
return new Dimension(info.width, info.height);
}
@Override
public ImageMetadata getMetadata(final ByteSource byteSource, final PnmImagingParameters params) throws ImagingException, IOException {
return null;
}
@Override
public String getName() {
return "Pbm-Custom";
}
private AbstractFileInfo readHeader(final ByteSource byteSource) throws ImagingException, IOException {
try (InputStream is = byteSource.getInputStream()) {
return readHeader(is);
}
}
private AbstractFileInfo readHeader(final InputStream inputStream) throws ImagingException, IOException {
final byte identifier1 = readByte("Identifier1", inputStream, "Not a Valid PNM File");
final byte identifier2 = readByte("Identifier2", inputStream, "Not a Valid PNM File");
if (identifier1 != PnmConstants.PNM_PREFIX_BYTE) {
throw new ImagingException("PNM file has invalid prefix byte 1");
}
final WhiteSpaceReader wsReader = new WhiteSpaceReader(inputStream);
if (identifier2 == PnmConstants.PBM_TEXT_CODE || identifier2 == PnmConstants.PBM_RAW_CODE || identifier2 == PnmConstants.PGM_TEXT_CODE
|| identifier2 == PnmConstants.PGM_RAW_CODE || identifier2 == PnmConstants.PPM_TEXT_CODE || identifier2 == PnmConstants.PPM_RAW_CODE) {
final int width;
try {
width = Integer.parseInt(wsReader.readtoWhiteSpace());
} catch (final NumberFormatException e) {
throw new ImagingException("Invalid width specified.", e);
}
final int height;
try {
height = Integer.parseInt(wsReader.readtoWhiteSpace());
} catch (final NumberFormatException e) {
throw new ImagingException("Invalid height specified.", e);
}
switch (identifier2) {
case PnmConstants.PBM_TEXT_CODE:
return new PbmFileInfo(width, height, false);
case PnmConstants.PBM_RAW_CODE:
return new PbmFileInfo(width, height, true);
case PnmConstants.PGM_TEXT_CODE: {
final int maxgray = Integer.parseInt(wsReader.readtoWhiteSpace());
return new PgmFileInfo(width, height, false, maxgray);
}
case PnmConstants.PGM_RAW_CODE: {
final int maxgray = Integer.parseInt(wsReader.readtoWhiteSpace());
return new PgmFileInfo(width, height, true, maxgray);
}
case PnmConstants.PPM_TEXT_CODE: {
final int max = Integer.parseInt(wsReader.readtoWhiteSpace());
return new PpmFileInfo(width, height, false, max);
}
case PnmConstants.PPM_RAW_CODE: {
final int max = Integer.parseInt(wsReader.readtoWhiteSpace());
return new PpmFileInfo(width, height, true, max);
}
default:
break;
}
} else if (identifier2 == PnmConstants.PAM_RAW_CODE) {
int width = -1;
int height = -1;
int depth = -1;
int maxVal = -1;
final StringBuilder tupleType = new StringBuilder();
// Advance to next line
wsReader.readLine();
String line;
while ((line = wsReader.readLine()) != null) {
line = line.trim();
if (line.charAt(0) == '#') {
continue;
}
final StringTokenizer tokenizer = new StringTokenizer(line, " ", false);
final String type = tokenizer.nextToken();
switch (type) {
case TOKEN_WIDTH:
width = checkNextTokensAsInt(tokenizer, type);
break;
case TOKEN_HEIGHT:
height = checkNextTokensAsInt(tokenizer, type);
break;
case TOKEN_DEPTH:
depth = checkNextTokensAsInt(tokenizer, type);
break;
case TOKEN_MAXVAL:
maxVal = checkNextTokensAsInt(tokenizer, type);
break;
case TOKEN_TUPLTYPE:
tupleType.append(checkNextTokens(tokenizer, type));
break;
case TOKEN_ENDHDR:
// consumed & noop
break;
default:
throw new ImagingException("Invalid PAM file header type " + type);
}
if (TOKEN_ENDHDR.equals(type)) {
break;
}
}
checkFound(width, TOKEN_WIDTH);
checkFound(height, TOKEN_HEIGHT);
checkFound(depth, TOKEN_DEPTH);
checkFound(maxVal, TOKEN_MAXVAL);
check(tupleType.length() > 0, TOKEN_TUPLTYPE);
return new PamFileInfo(width, height, depth, maxVal, tupleType.toString());
}
throw new ImagingException("PNM file has invalid prefix byte 2");
}
@Override
public void writeImage(final BufferedImage src, final OutputStream os, final PnmImagingParameters params) throws ImagingException, IOException {
PnmWriter writer = null;
boolean useRawbits = true;
if (params != null) {
useRawbits = params.isRawBits();
final ImageFormats subtype = params.getSubtype();
if (subtype != null) {
switch (subtype) {
case PBM:
writer = new PbmWriter(useRawbits);
break;
case PGM:
writer = new PgmWriter(useRawbits);
break;
case PPM:
writer = new PpmWriter(useRawbits);
break;
case PAM:
writer = new PamWriter();
break;
default:
// see null-check below
break;
}
}
}
if (writer == null) {
writer = new PaletteFactory().hasTransparency(src) ? new PamWriter() : new PpmWriter(useRawbits);
}
writer.writeImage(src, os, params);
}
}