001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.imaging.formats.pnm; 018 019import static org.apache.commons.imaging.common.BinaryFunctions.readByte; 020 021import java.awt.Dimension; 022import java.awt.image.BufferedImage; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.io.PrintWriter; 027import java.nio.ByteOrder; 028import java.util.ArrayList; 029import java.util.List; 030import java.util.StringTokenizer; 031 032import org.apache.commons.imaging.ImageFormat; 033import org.apache.commons.imaging.ImageFormats; 034import org.apache.commons.imaging.ImageInfo; 035import org.apache.commons.imaging.ImageParser; 036import org.apache.commons.imaging.ImageReadException; 037import org.apache.commons.imaging.ImageWriteException; 038import org.apache.commons.imaging.common.ImageBuilder; 039import org.apache.commons.imaging.common.ImageMetadata; 040import org.apache.commons.imaging.common.bytesource.ByteSource; 041import org.apache.commons.imaging.palette.PaletteFactory; 042 043public class PnmImageParser extends ImageParser<PnmImagingParameters> { 044 private static final String DEFAULT_EXTENSION = ImageFormats.PNM.getDefaultExtension(); 045 private static final String[] ACCEPTED_EXTENSIONS = { 046 ImageFormats.PAM.getDefaultExtension(), 047 ImageFormats.PBM.getDefaultExtension(), 048 ImageFormats.PGM.getDefaultExtension(), 049 ImageFormats.PNM.getDefaultExtension(), 050 ImageFormats.PPM.getDefaultExtension() 051 }; 052 053 public PnmImageParser() { 054 super.setByteOrder(ByteOrder.LITTLE_ENDIAN); 055 } 056 057 @Override 058 public PnmImagingParameters getDefaultParameters() { 059 return new PnmImagingParameters(); 060 } 061 062 @Override 063 public String getName() { 064 return "Pbm-Custom"; 065 } 066 067 @Override 068 public String getDefaultExtension() { 069 return DEFAULT_EXTENSION; 070 } 071 072 @Override 073 protected String[] getAcceptedExtensions() { 074 return ACCEPTED_EXTENSIONS; 075 } 076 077 @Override 078 protected ImageFormat[] getAcceptedTypes() { 079 return new ImageFormat[] { 080 ImageFormats.PBM, 081 ImageFormats.PGM, 082 ImageFormats.PPM, 083 ImageFormats.PNM, 084 ImageFormats.PAM 085 }; 086 } 087 088 private FileInfo readHeader(final InputStream is) throws ImageReadException, 089 IOException { 090 final byte identifier1 = readByte("Identifier1", is, "Not a Valid PNM File"); 091 final byte identifier2 = readByte("Identifier2", is, "Not a Valid PNM File"); 092 093 if (identifier1 != PnmConstants.PNM_PREFIX_BYTE) { 094 throw new ImageReadException("PNM file has invalid prefix byte 1"); 095 } 096 097 final WhiteSpaceReader wsr = new WhiteSpaceReader(is); 098 099 if (identifier2 == PnmConstants.PBM_TEXT_CODE 100 || identifier2 == PnmConstants.PBM_RAW_CODE 101 || identifier2 == PnmConstants.PGM_TEXT_CODE 102 || identifier2 == PnmConstants.PGM_RAW_CODE 103 || identifier2 == PnmConstants.PPM_TEXT_CODE 104 || identifier2 == PnmConstants.PPM_RAW_CODE) { 105 106 final int width; 107 try { 108 width = Integer.parseInt(wsr.readtoWhiteSpace()); 109 } catch (final NumberFormatException e) { 110 throw new ImageReadException("Invalid width specified." , e); 111 } 112 final int height; 113 try { 114 height = Integer.parseInt(wsr.readtoWhiteSpace()); 115 } catch (final NumberFormatException e) { 116 throw new ImageReadException("Invalid height specified." , e); 117 } 118 119 if (identifier2 == PnmConstants.PBM_TEXT_CODE) { 120 return new PbmFileInfo(width, height, false); 121 } 122 if (identifier2 == PnmConstants.PBM_RAW_CODE) { 123 return new PbmFileInfo(width, height, true); 124 } 125 if (identifier2 == PnmConstants.PGM_TEXT_CODE) { 126 final int maxgray = Integer.parseInt(wsr.readtoWhiteSpace()); 127 return new PgmFileInfo(width, height, false, maxgray); 128 } 129 if (identifier2 == PnmConstants.PGM_RAW_CODE) { 130 final int maxgray = Integer.parseInt(wsr.readtoWhiteSpace()); 131 return new PgmFileInfo(width, height, true, maxgray); 132 } 133 if (identifier2 == PnmConstants.PPM_TEXT_CODE) { 134 final int max = Integer.parseInt(wsr.readtoWhiteSpace()); 135 return new PpmFileInfo(width, height, false, max); 136 } 137 if (identifier2 == PnmConstants.PPM_RAW_CODE) { 138 final int max = Integer.parseInt(wsr.readtoWhiteSpace()); 139 return new PpmFileInfo(width, height, true, max); 140 } 141 } else if (identifier2 == PnmConstants.PAM_RAW_CODE) { 142 int width = -1; 143 boolean seenWidth = false; 144 int height = -1; 145 boolean seenHeight = false; 146 int depth = -1; 147 boolean seenDepth = false; 148 int maxVal = -1; 149 boolean seenMaxVal = false; 150 final StringBuilder tupleType = new StringBuilder(); 151 boolean seenTupleType = false; 152 153 // Advance to next line 154 wsr.readLine(); 155 String line; 156 while ((line = wsr.readLine()) != null) { 157 line = line.trim(); 158 if (line.charAt(0) == '#') { 159 continue; 160 } 161 final StringTokenizer tokenizer = new StringTokenizer(line, " ", false); 162 final String type = tokenizer.nextToken(); 163 if ("WIDTH".equals(type)) { 164 seenWidth = true; 165 if(!tokenizer.hasMoreTokens()) { 166 throw new ImageReadException("PAM header has no WIDTH value"); 167 } 168 width = Integer.parseInt(tokenizer.nextToken()); 169 } else if ("HEIGHT".equals(type)) { 170 seenHeight = true; 171 if(!tokenizer.hasMoreTokens()) { 172 throw new ImageReadException("PAM header has no HEIGHT value"); 173 } 174 height = Integer.parseInt(tokenizer.nextToken()); 175 } else if ("DEPTH".equals(type)) { 176 seenDepth = true; 177 if(!tokenizer.hasMoreTokens()) { 178 throw new ImageReadException("PAM header has no DEPTH value"); 179 } 180 depth = Integer.parseInt(tokenizer.nextToken()); 181 } else if ("MAXVAL".equals(type)) { 182 seenMaxVal = true; 183 if(!tokenizer.hasMoreTokens()) { 184 throw new ImageReadException("PAM header has no MAXVAL value"); 185 } 186 maxVal = Integer.parseInt(tokenizer.nextToken()); 187 } else if ("TUPLTYPE".equals(type)) { 188 seenTupleType = true; 189 if(!tokenizer.hasMoreTokens()) { 190 throw new ImageReadException("PAM header has no TUPLTYPE value"); 191 } 192 tupleType.append(tokenizer.nextToken()); 193 } else if ("ENDHDR".equals(type)) { 194 break; 195 } else { 196 throw new ImageReadException("Invalid PAM file header type " + type); 197 } 198 } 199 200 if (!seenWidth) { 201 throw new ImageReadException("PAM header has no WIDTH"); 202 } 203 if (!seenHeight) { 204 throw new ImageReadException("PAM header has no HEIGHT"); 205 } 206 if (!seenDepth) { 207 throw new ImageReadException("PAM header has no DEPTH"); 208 } 209 if (!seenMaxVal) { 210 throw new ImageReadException("PAM header has no MAXVAL"); 211 } 212 if (!seenTupleType) { 213 throw new ImageReadException("PAM header has no TUPLTYPE"); 214 } 215 216 return new PamFileInfo(width, height, depth, maxVal, tupleType.toString()); 217 } 218 throw new ImageReadException("PNM file has invalid prefix byte 2"); 219 } 220 221 private FileInfo readHeader(final ByteSource byteSource) 222 throws ImageReadException, IOException { 223 try (InputStream is = byteSource.getInputStream()) { 224 return readHeader(is); 225 } 226 } 227 228 @Override 229 public byte[] getICCProfileBytes(final ByteSource byteSource, final PnmImagingParameters params) 230 throws ImageReadException, IOException { 231 return null; 232 } 233 234 @Override 235 public Dimension getImageSize(final ByteSource byteSource, final PnmImagingParameters params) 236 throws ImageReadException, IOException { 237 final FileInfo info = readHeader(byteSource); 238 239 return new Dimension(info.width, info.height); 240 } 241 242 @Override 243 public ImageMetadata getMetadata(final ByteSource byteSource, final PnmImagingParameters params) 244 throws ImageReadException, IOException { 245 return null; 246 } 247 248 @Override 249 public ImageInfo getImageInfo(final ByteSource byteSource, final PnmImagingParameters params) 250 throws ImageReadException, IOException { 251 final FileInfo info = readHeader(byteSource); 252 253 final List<String> comments = new ArrayList<>(); 254 255 final int bitsPerPixel = info.getBitDepth() * info.getNumComponents(); 256 final ImageFormat format = info.getImageType(); 257 final String formatName = info.getImageTypeDescription(); 258 final String mimeType = info.getMIMEType(); 259 final int numberOfImages = 1; 260 final boolean progressive = false; 261 262 // boolean progressive = (fPNGChunkIHDR.InterlaceMethod != 0); 263 // 264 final int physicalWidthDpi = 72; 265 final float physicalWidthInch = (float) ((double) info.width / (double) physicalWidthDpi); 266 final int physicalHeightDpi = 72; 267 final float physicalHeightInch = (float) ((double) info.height / (double) physicalHeightDpi); 268 269 final String formatDetails = info.getImageTypeDescription(); 270 271 final boolean transparent = info.hasAlpha(); 272 final boolean usesPalette = false; 273 274 final ImageInfo.ColorType colorType = info.getColorType(); 275 final ImageInfo.CompressionAlgorithm compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE; 276 277 return new ImageInfo(formatDetails, bitsPerPixel, comments, 278 format, formatName, info.height, mimeType, numberOfImages, 279 physicalHeightDpi, physicalHeightInch, physicalWidthDpi, 280 physicalWidthInch, info.width, progressive, transparent, 281 usesPalette, colorType, compressionAlgorithm); 282 } 283 284 @Override 285 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) 286 throws ImageReadException, IOException { 287 pw.println("pnm.dumpImageFile"); 288 289 final ImageInfo imageData = getImageInfo(byteSource); 290 if (imageData == null) { 291 return false; 292 } 293 294 imageData.toString(pw, ""); 295 296 pw.println(""); 297 298 return true; 299 } 300 301 @Override 302 public BufferedImage getBufferedImage(final ByteSource byteSource, final PnmImagingParameters params) 303 throws ImageReadException, IOException { 304 try (InputStream is = byteSource.getInputStream()) { 305 final FileInfo info = readHeader(is); 306 307 final int width = info.width; 308 final int height = info.height; 309 310 final boolean hasAlpha = info.hasAlpha(); 311 final ImageBuilder imageBuilder = new ImageBuilder(width, height, 312 hasAlpha); 313 info.readImage(imageBuilder, is); 314 315 return imageBuilder.getBufferedImage(); 316 } 317 } 318 319 @Override 320 public void writeImage(final BufferedImage src, final OutputStream os, PnmImagingParameters params) 321 throws ImageWriteException, IOException { 322 PnmWriter writer = null; 323 boolean useRawbits = true; 324 325 if (params != null) { 326 useRawbits = params.isRawBits(); 327 328 final ImageFormats subtype = params.getSubtype(); 329 if (subtype != null) { 330 if (subtype.equals(ImageFormats.PBM)) { 331 writer = new PbmWriter(useRawbits); 332 } else if (subtype.equals(ImageFormats.PGM)) { 333 writer = new PgmWriter(useRawbits); 334 } else if (subtype.equals(ImageFormats.PPM)) { 335 writer = new PpmWriter(useRawbits); 336 } else if (subtype.equals(ImageFormats.PAM)) { 337 writer = new PamWriter(); 338 } 339 } 340 } 341 342 if (writer == null) { 343 final boolean hasAlpha = new PaletteFactory().hasTransparency(src); 344 if (hasAlpha) { 345 writer = new PamWriter(); 346 } else { 347 writer = new PpmWriter(useRawbits); 348 } 349 } 350 351 writer.writeImage(src, os, params); 352 } 353}