1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 import java.util.stream.Stream;
32
33 import org.apache.commons.imaging.AbstractImageParser;
34 import org.apache.commons.imaging.ImageFormat;
35 import org.apache.commons.imaging.ImageFormats;
36 import org.apache.commons.imaging.ImageInfo;
37 import org.apache.commons.imaging.ImagingException;
38 import org.apache.commons.imaging.bytesource.ByteSource;
39 import org.apache.commons.imaging.common.ImageBuilder;
40 import org.apache.commons.imaging.common.ImageMetadata;
41 import org.apache.commons.imaging.palette.PaletteFactory;
42
43 public class PnmImageParser extends AbstractImageParser<PnmImagingParameters> {
44
45 private static final String TOKEN_ENDHDR = "ENDHDR";
46 private static final String TOKEN_TUPLTYPE = "TUPLTYPE";
47 private static final String TOKEN_MAXVAL = "MAXVAL";
48 private static final String TOKEN_DEPTH = "DEPTH";
49 private static final String TOKEN_HEIGHT = "HEIGHT";
50 private static final String TOKEN_WIDTH = "WIDTH";
51
52 private static final int DPI = 72;
53 private static final ImageFormat[] IMAGE_FORMATS;
54 private static final String DEFAULT_EXTENSION = ImageFormats.PNM.getDefaultExtension();
55 private static final String[] ACCEPTED_EXTENSIONS;
56
57 static {
58 IMAGE_FORMATS = new ImageFormat[] {
59
60 ImageFormats.PAM,
61 ImageFormats.PBM,
62 ImageFormats.PGM,
63 ImageFormats.PNM,
64 ImageFormats.PPM
65
66 };
67 ACCEPTED_EXTENSIONS = Stream.of(IMAGE_FORMATS).map(ImageFormat::getDefaultExtension).toArray(String[]::new);
68 }
69
70 public PnmImageParser() {
71 super(ByteOrder.LITTLE_ENDIAN);
72 }
73
74 private void check(final boolean value, final String type) throws ImagingException {
75 if (!value) {
76 throw new ImagingException("PAM header has no " + type + " value");
77 }
78 }
79
80 private void checkFound(final int value, final String type) throws ImagingException {
81 check(value != -1, type);
82 }
83
84 private String checkNextTokens(final StringTokenizer tokenizer, final String type) throws ImagingException {
85 check(tokenizer.hasMoreTokens(), type);
86 return tokenizer.nextToken();
87 }
88
89 private int checkNextTokensAsInt(final StringTokenizer tokenizer, final String type) throws ImagingException {
90 return Integer.parseInt(checkNextTokens(tokenizer, type));
91 }
92
93 @Override
94 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
95 pw.println("pnm.dumpImageFile");
96
97 final ImageInfo imageData = getImageInfo(byteSource);
98 if (imageData == null) {
99 return false;
100 }
101
102 imageData.toString(pw, "");
103
104 pw.println("");
105
106 return true;
107 }
108
109 @Override
110 protected String[] getAcceptedExtensions() {
111 return ACCEPTED_EXTENSIONS.clone();
112 }
113
114 @Override
115 protected ImageFormat[] getAcceptedTypes() {
116 return IMAGE_FORMATS.clone();
117 }
118
119 @Override
120 public BufferedImage getBufferedImage(final ByteSource byteSource, final PnmImagingParameters params) throws ImagingException, IOException {
121 try (InputStream is = byteSource.getInputStream()) {
122 final AbstractFileInfo info = readHeader(is);
123
124 final int width = info.width;
125 final int height = info.height;
126
127 final boolean hasAlpha = info.hasAlpha();
128 final ImageBuilder imageBuilder = new ImageBuilder(width, height, hasAlpha);
129 info.readImage(imageBuilder, is);
130
131 return imageBuilder.getBufferedImage();
132 }
133 }
134
135 @Override
136 public String getDefaultExtension() {
137 return DEFAULT_EXTENSION;
138 }
139
140 @Override
141 public PnmImagingParameters getDefaultParameters() {
142 return new PnmImagingParameters();
143 }
144
145 @Override
146 public byte[] getIccProfileBytes(final ByteSource byteSource, final PnmImagingParameters params) throws ImagingException, IOException {
147 return null;
148 }
149
150 @Override
151 public ImageInfo getImageInfo(final ByteSource byteSource, final PnmImagingParameters params) throws ImagingException, IOException {
152 final AbstractFileInfo info = readHeader(byteSource);
153
154 final List<String> comments = new ArrayList<>();
155
156 final int bitsPerPixel = info.getBitDepth() * info.getNumComponents();
157 final ImageFormat format = info.getImageType();
158 final String formatName = info.getImageTypeDescription();
159 final String mimeType = info.getMimeType();
160 final int numberOfImages = 1;
161 final boolean progressive = false;
162
163
164
165 final int physicalWidthDpi = DPI;
166 final float physicalWidthInch = (float) ((double) info.width / (double) physicalWidthDpi);
167 final int physicalHeightDpi = DPI;
168 final float physicalHeightInch = (float) ((double) info.height / (double) physicalHeightDpi);
169
170 final String formatDetails = info.getImageTypeDescription();
171
172 final boolean transparent = info.hasAlpha();
173 final boolean usesPalette = false;
174
175 final ImageInfo.ColorType colorType = info.getColorType();
176 final ImageInfo.CompressionAlgorithm compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
177
178 return new ImageInfo(formatDetails, bitsPerPixel, comments, format, formatName, info.height, mimeType, numberOfImages, physicalHeightDpi,
179 physicalHeightInch, physicalWidthDpi, physicalWidthInch, info.width, progressive, transparent, usesPalette, colorType, compressionAlgorithm);
180 }
181
182 @Override
183 public Dimension getImageSize(final ByteSource byteSource, final PnmImagingParameters params) throws ImagingException, IOException {
184 final AbstractFileInfo info = readHeader(byteSource);
185 return new Dimension(info.width, info.height);
186 }
187
188 @Override
189 public ImageMetadata getMetadata(final ByteSource byteSource, final PnmImagingParameters params) throws ImagingException, IOException {
190 return null;
191 }
192
193 @Override
194 public String getName() {
195 return "Pbm-Custom";
196 }
197
198 private AbstractFileInfo readHeader(final ByteSource byteSource) throws ImagingException, IOException {
199 try (InputStream is = byteSource.getInputStream()) {
200 return readHeader(is);
201 }
202 }
203
204 private AbstractFileInfo readHeader(final InputStream inputStream) throws ImagingException, IOException {
205 final byte identifier1 = readByte("Identifier1", inputStream, "Not a Valid PNM File");
206 final byte identifier2 = readByte("Identifier2", inputStream, "Not a Valid PNM File");
207
208 if (identifier1 != PnmConstants.PNM_PREFIX_BYTE) {
209 throw new ImagingException("PNM file has invalid prefix byte 1");
210 }
211
212 final WhiteSpaceReader wsReader = new WhiteSpaceReader(inputStream);
213
214 if (identifier2 == PnmConstants.PBM_TEXT_CODE || identifier2 == PnmConstants.PBM_RAW_CODE || identifier2 == PnmConstants.PGM_TEXT_CODE
215 || identifier2 == PnmConstants.PGM_RAW_CODE || identifier2 == PnmConstants.PPM_TEXT_CODE || identifier2 == PnmConstants.PPM_RAW_CODE) {
216
217 final int width;
218 try {
219 width = Integer.parseInt(wsReader.readtoWhiteSpace());
220 } catch (final NumberFormatException e) {
221 throw new ImagingException("Invalid width specified.", e);
222 }
223 final int height;
224 try {
225 height = Integer.parseInt(wsReader.readtoWhiteSpace());
226 } catch (final NumberFormatException e) {
227 throw new ImagingException("Invalid height specified.", e);
228 }
229
230 switch (identifier2) {
231 case PnmConstants.PBM_TEXT_CODE:
232 return new PbmFileInfo(width, height, false);
233 case PnmConstants.PBM_RAW_CODE:
234 return new PbmFileInfo(width, height, true);
235 case PnmConstants.PGM_TEXT_CODE: {
236 final int maxgray = Integer.parseInt(wsReader.readtoWhiteSpace());
237 return new PgmFileInfo(width, height, false, maxgray);
238 }
239 case PnmConstants.PGM_RAW_CODE: {
240 final int maxgray = Integer.parseInt(wsReader.readtoWhiteSpace());
241 return new PgmFileInfo(width, height, true, maxgray);
242 }
243 case PnmConstants.PPM_TEXT_CODE: {
244 final int max = Integer.parseInt(wsReader.readtoWhiteSpace());
245 return new PpmFileInfo(width, height, false, max);
246 }
247 case PnmConstants.PPM_RAW_CODE: {
248 final int max = Integer.parseInt(wsReader.readtoWhiteSpace());
249 return new PpmFileInfo(width, height, true, max);
250 }
251 default:
252 break;
253 }
254 } else if (identifier2 == PnmConstants.PAM_RAW_CODE) {
255 int width = -1;
256 int height = -1;
257 int depth = -1;
258 int maxVal = -1;
259 final StringBuilder tupleType = new StringBuilder();
260
261
262 wsReader.readLine();
263 String line;
264 while ((line = wsReader.readLine()) != null) {
265 line = line.trim();
266 if (line.charAt(0) == '#') {
267 continue;
268 }
269 final StringTokenizer tokenizer = new StringTokenizer(line, " ", false);
270 final String type = tokenizer.nextToken();
271 switch (type) {
272 case TOKEN_WIDTH:
273 width = checkNextTokensAsInt(tokenizer, type);
274 break;
275 case TOKEN_HEIGHT:
276 height = checkNextTokensAsInt(tokenizer, type);
277 break;
278 case TOKEN_DEPTH:
279 depth = checkNextTokensAsInt(tokenizer, type);
280 break;
281 case TOKEN_MAXVAL:
282 maxVal = checkNextTokensAsInt(tokenizer, type);
283 break;
284 case TOKEN_TUPLTYPE:
285 tupleType.append(checkNextTokens(tokenizer, type));
286 break;
287 case TOKEN_ENDHDR:
288
289 break;
290 default:
291 throw new ImagingException("Invalid PAM file header type " + type);
292 }
293 if (TOKEN_ENDHDR.equals(type)) {
294 break;
295 }
296 }
297 checkFound(width, TOKEN_WIDTH);
298 checkFound(height, TOKEN_HEIGHT);
299 checkFound(depth, TOKEN_DEPTH);
300 checkFound(maxVal, TOKEN_MAXVAL);
301 check(tupleType.length() > 0, TOKEN_TUPLTYPE);
302 return new PamFileInfo(width, height, depth, maxVal, tupleType.toString());
303 }
304 throw new ImagingException("PNM file has invalid prefix byte 2");
305 }
306
307 @Override
308 public void writeImage(final BufferedImage src, final OutputStream os, final PnmImagingParameters params) throws ImagingException, IOException {
309 PnmWriter writer = null;
310 boolean useRawbits = true;
311
312 if (params != null) {
313 useRawbits = params.isRawBits();
314
315 final ImageFormats subtype = params.getSubtype();
316 if (subtype != null) {
317 switch (subtype) {
318 case PBM:
319 writer = new PbmWriter(useRawbits);
320 break;
321 case PGM:
322 writer = new PgmWriter(useRawbits);
323 break;
324 case PPM:
325 writer = new PpmWriter(useRawbits);
326 break;
327 case PAM:
328 writer = new PamWriter();
329 break;
330 default:
331
332 break;
333 }
334 }
335 }
336
337 if (writer == null) {
338 writer = new PaletteFactory().hasTransparency(src) ? new PamWriter() : new PpmWriter(useRawbits);
339 }
340
341 writer.writeImage(src, os, params);
342 }
343 }