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