View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.formats.pnm;
18  
19  import static org.apache.commons.imaging.common.BinaryFunctions.readByte;
20  
21  import java.awt.Dimension;
22  import java.awt.image.BufferedImage;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.io.PrintWriter;
27  import java.nio.ByteOrder;
28  import java.util.ArrayList;
29  import java.util.List;
30  import java.util.StringTokenizer;
31  
32  import org.apache.commons.imaging.ImageFormat;
33  import org.apache.commons.imaging.ImageFormats;
34  import org.apache.commons.imaging.ImageInfo;
35  import org.apache.commons.imaging.ImageParser;
36  import org.apache.commons.imaging.ImageReadException;
37  import org.apache.commons.imaging.ImageWriteException;
38  import org.apache.commons.imaging.common.ImageBuilder;
39  import org.apache.commons.imaging.common.ImageMetadata;
40  import org.apache.commons.imaging.common.bytesource.ByteSource;
41  import org.apache.commons.imaging.palette.PaletteFactory;
42  
43  public class PnmImageParser extends ImageParser<PnmImagingParameters> {
44      private static final String DEFAULT_EXTENSION = ImageFormats.PNM.getDefaultExtension();
45      private static final String[] ACCEPTED_EXTENSIONS = {
46              ImageFormats.PAM.getDefaultExtension(),
47              ImageFormats.PBM.getDefaultExtension(),
48              ImageFormats.PGM.getDefaultExtension(),
49              ImageFormats.PNM.getDefaultExtension(),
50              ImageFormats.PPM.getDefaultExtension()
51      };
52  
53      public PnmImageParser() {
54          super.setByteOrder(ByteOrder.LITTLE_ENDIAN);
55      }
56  
57      @Override
58      public PnmImagingParameters getDefaultParameters() {
59          return new PnmImagingParameters();
60      }
61  
62      @Override
63      public String getName() {
64          return "Pbm-Custom";
65      }
66  
67      @Override
68      public String getDefaultExtension() {
69          return DEFAULT_EXTENSION;
70      }
71  
72      @Override
73      protected String[] getAcceptedExtensions() {
74          return ACCEPTED_EXTENSIONS;
75      }
76  
77      @Override
78      protected ImageFormat[] getAcceptedTypes() {
79          return new ImageFormat[] {
80                  ImageFormats.PBM,
81                  ImageFormats.PGM,
82                  ImageFormats.PPM,
83                  ImageFormats.PNM,
84                  ImageFormats.PAM
85          };
86      }
87  
88      private FileInfo readHeader(final InputStream is) throws ImageReadException,
89              IOException {
90          final byte identifier1 = readByte("Identifier1", is, "Not a Valid PNM File");
91          final byte identifier2 = readByte("Identifier2", is, "Not a Valid PNM File");
92  
93          if (identifier1 != PnmConstants.PNM_PREFIX_BYTE) {
94              throw new ImageReadException("PNM file has invalid prefix byte 1");
95          }
96  
97          final WhiteSpaceReaderrmats/pnm/WhiteSpaceReader.html#WhiteSpaceReader">WhiteSpaceReader wsr = new WhiteSpaceReader(is);
98  
99          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 ImageBuilderImageBuilder.html#ImageBuilder">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 }