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;
18  
19  import java.awt.Dimension;
20  import java.awt.color.ICC_Profile;
21  import java.awt.image.BufferedImage;
22  import java.io.BufferedOutputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.OutputStream;
29  import java.util.Arrays;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Objects;
33  import java.util.stream.Stream;
34  
35  import org.apache.commons.imaging.bytesource.ByteSource;
36  import org.apache.commons.imaging.common.ImageMetadata;
37  import org.apache.commons.imaging.common.XmpEmbeddable;
38  import org.apache.commons.imaging.icc.IccProfileInfo;
39  import org.apache.commons.imaging.icc.IccProfileParser;
40  import org.apache.commons.imaging.internal.ImageParserFactory;
41  
42  /**
43   * The primary application programming interface (API) to the Imaging library.
44   *
45   * <h2>Application Notes</h2>
46   *
47   * <h3>Using this class</h3>
48   *
49   * <p>
50   * Almost all of the Apache Commons Imaging library's core functionality can be accessed through the methods provided by this class. The use of the Imaging
51   * class is similar to the Java API's ImageIO class, though Imaging supports formats not included in the standard Java API.
52   * </p>
53   *
54   * <p>
55   * All of methods provided by the Imaging class are declared static.
56   * </p>
57   *
58   * <p>
59   * The Apache Commons Imaging package is a pure Java implementation.
60   * </p>
61   *
62   * <h3>Format support</h3>
63   *
64   * <p>
65   * While the Apache Commons Imaging package handles a number of different graphics formats, support for some formats is not yet complete. For the most recent
66   * information on support for specific formats, refer to <a href="https://commons.apache.org/imaging/formatsupport.html">Format Support</a> at the main project
67   * development web site.
68   * </p>
69   *
70   * <h3>Optional parameters for image reading and writing</h3>
71   *
72   * <p>
73   * Many of the operations provided in this class as static calls can be accessed directly using format-specific {@link AbstractImageParser} instances. These
74   * static methods are provided for convenience in simple use cases.
75   * </p>
76   *
77   * <h3>Example code</h3>
78   *
79   * <p>
80   * See the source of the SampleUsage class and other classes in the org.apache.commons.imaging.examples package for examples.
81   * </p>
82   *
83   * @see <a href="https://svn.apache.org/repos/asf/commons/proper/imaging/trunk/src/test/java/org/apache/commons/imaging/examples/SampleUsage.java">
84   *      org.apache.commons.imaging.examples.SampleUsage</a>
85   * @see <a href="https://commons.apache.org/imaging/formatsupport.html">Format Support</a>
86   */
87  public final class Imaging {
88  
89      private static final int[] MAGIC_NUMBERS_GIF = { 0x47, 0x49, };
90      private static final int[] MAGIC_NUMBERS_PNG = { 0x89, 0x50, };
91      private static final int[] MAGIC_NUMBERS_JPEG = { 0xff, 0xd8, };
92      private static final int[] MAGIC_NUMBERS_BMP = { 0x42, 0x4d, };
93      private static final int[] MAGIC_NUMBERS_TIFF_MOTOROLA = { 0x4D, 0x4D, };
94      private static final int[] MAGIC_NUMBERS_TIFF_INTEL = { 0x49, 0x49, };
95      private static final int[] MAGIC_NUMBERS_PAM = { 0x50, 0x37, };
96      private static final int[] MAGIC_NUMBERS_PSD = { 0x38, 0x42, };
97      private static final int[] MAGIC_NUMBERS_PBM_A = { 0x50, 0x31, };
98      private static final int[] MAGIC_NUMBERS_PBM_B = { 0x50, 0x34, };
99      private static final int[] MAGIC_NUMBERS_PGM_A = { 0x50, 0x32, };
100     private static final int[] MAGIC_NUMBERS_PGM_B = { 0x50, 0x35, };
101     private static final int[] MAGIC_NUMBERS_PPM_A = { 0x50, 0x33, };
102     private static final int[] MAGIC_NUMBERS_PPM_B = { 0x50, 0x36, };
103     private static final int[] MAGIC_NUMBERS_JBIG2_1 = { 0x97, 0x4A, };
104     private static final int[] MAGIC_NUMBERS_JBIG2_2 = { 0x42, 0x32, };
105     private static final int[] MAGIC_NUMBERS_ICNS = { 0x69, 0x63, };
106     private static final int[] MAGIC_NUMBERS_DCX = { 0xB1, 0x68, };
107     private static final int[] MAGIC_NUMBERS_RGBE = { 0x23, 0x3F, };
108     private static final int[] MAGIC_NUMBERS_RIFF_1 = { 0x52, 0x49, };
109     private static final int[] MAGIC_NUMBERS_RIFF_2 = { 0x46, 0x46, };
110     private static final byte[] MAGIC_NUMBERS_WEBP = { 0x57, 0x45, 0x42, 0x50, };
111 
112     private static boolean compareBytePair(final int[] a, final int[] b) {
113         if (a.length != 2 && b.length != 2) {
114             throw new IllegalArgumentException("Invalid Byte Pair.");
115         }
116         return a[0] == b[0] && a[1] == b[1];
117     }
118 
119     /**
120      * Write the ImageInfo and format-specific information for the image content of the specified byte array to a string.
121      *
122      * @param bytes A valid array of bytes.
123      * @return A valid string.
124      * @throws ImagingException In the event that the specified content does not conform to the format of the specific parser implementation.
125      * @throws IOException      In the event of unsuccessful read or access operation.
126      */
127     public static String dumpImageFile(final byte[] bytes) throws ImagingException, IOException {
128         return dumpImageFile(ByteSource.array(bytes));
129     }
130 
131     private static String dumpImageFile(final ByteSource byteSource) throws ImagingException, IOException {
132         final AbstractImageParser<?> imageParser = ImageParserFactory.getImageParser(byteSource);
133         return imageParser.dumpImageFile(byteSource);
134     }
135 
136     /**
137      * Write the ImageInfo and format-specific information for the image content of the specified file to a string.
138      *
139      * @param file A valid file reference.
140      * @return A valid string.
141      * @throws ImagingException In the event that the specified content does not conform to the format of the specific parser implementation.
142      * @throws IOException      In the event of unsuccessful read or access operation.
143      */
144     public static String dumpImageFile(final File file) throws ImagingException, IOException {
145         return dumpImageFile(ByteSource.file(file));
146     }
147 
148     /**
149      * Gets all images specified by the byte array (some formats may include multiple images within a single data source).
150      *
151      * @param bytes a valid array of bytes
152      * @return A valid (potentially empty) list of BufferedImage objects.
153      * @throws ImagingException In the event that the specified content does not conform to the format of the specific parser implementation.
154      * @throws IOException      In the event of unsuccessful read or access operation.
155      */
156     public static List<BufferedImage> getAllBufferedImages(final byte[] bytes) throws ImagingException, IOException {
157         return getAllBufferedImages(ByteSource.array(bytes));
158     }
159 
160     private static List<BufferedImage> getAllBufferedImages(final ByteSource byteSource) throws ImagingException, IOException {
161         final AbstractImageParser<?> imageParser = ImageParserFactory.getImageParser(byteSource);
162         return imageParser.getAllBufferedImages(byteSource);
163     }
164 
165     /**
166      * Gets all images specified by the file (some formats may include multiple images within a single data source).
167      *
168      * @param file A reference to a valid data file.
169      * @return A valid (potentially empty) list of BufferedImage objects.
170      * @throws ImagingException In the event that the specified content does not conform to the format of the specific parser implementation.
171      * @throws IOException      In the event of unsuccessful read or access operation.
172      */
173     public static List<BufferedImage> getAllBufferedImages(final File file) throws ImagingException, IOException {
174         return getAllBufferedImages(ByteSource.file(file));
175     }
176 
177     /**
178      * Gets all images specified by the InputStream (some formats may include multiple images within a single data source).
179      *
180      * @param is       A valid InputStream
181      * @param fileName File name associated with image data (optional).
182      * @return A valid (potentially empty) list of BufferedImage objects.
183      * @throws ImagingException In the event that the specified content does not conform to the format of the specific parser implementation.
184      * @throws IOException      In the event of unsuccessful read or access operation.
185      */
186     public static List<BufferedImage> getAllBufferedImages(final InputStream is, final String fileName) throws ImagingException, IOException {
187         return getAllBufferedImages(ByteSource.inputStream(is, fileName));
188     }
189 
190     /**
191      * Reads the first image from a byte array.
192      *
193      * <p>
194      * For the most recent information on support for specific formats, refer to <a href="https://commons.apache.org/imaging/formatsupport.html">Format
195      * Support</a> at the main project development web site. While the Apache Commons Imaging package does not fully support all formats, it can read image
196      * info, metadata and ICC profiles from all image formats that provide this data.
197      * </p>
198      *
199      * @param bytes a valid array of bytes from which to read data.
200      * @return if successful, a valid buffered image
201      * @throws ImagingException in the event of a processing error while reading an image (i.e. a format violation, etc.).
202      * @throws IOException      in the event of an unrecoverable I/O exception.
203      */
204     public static BufferedImage getBufferedImage(final byte[] bytes) throws ImagingException, IOException {
205         return getBufferedImage(ByteSource.array(bytes));
206     }
207 
208     private static BufferedImage getBufferedImage(final ByteSource byteSource) throws ImagingException, IOException {
209         final AbstractImageParser<?> imageParser = ImageParserFactory.getImageParser(byteSource);
210         return imageParser.getBufferedImage(byteSource, null);
211     }
212 
213     /**
214      * Reads the first image from a file.
215      *
216      * <p>
217      * For the most recent information on support for specific formats, refer to <a href="https://commons.apache.org/imaging/formatsupport.html">Format
218      * Support</a> at the main project development web site. While the Apache Commons Imaging package does not fully support all formats, it can read image
219      * info, metadata and ICC profiles from all image formats that provide this data.
220      * </p>
221      *
222      * @param file a valid reference to a file containing image data.
223      * @return if successful, a valid buffered image
224      * @throws ImagingException in the event of a processing error while reading an image (i.e. a format violation, etc.).
225      * @throws IOException      in the event of an unrecoverable I/O exception.
226      */
227     public static BufferedImage getBufferedImage(final File file) throws ImagingException, IOException {
228         return getBufferedImage(ByteSource.file(file));
229     }
230 
231     /**
232      * Reads the first image from an InputStream.
233      *
234      * <p>
235      * For the most recent information on support for specific formats, refer to <a href="https://commons.apache.org/imaging/formatsupport.html">Format
236      * Support</a> at the main project development web site. While the Apache Commons Imaging package does not fully support all formats, it can read image
237      * info, metadata and ICC profiles from all image formats that provide this data.
238      * </p>
239      *
240      * @param is a valid ImageStream from which to read data.
241      * @return if successful, a valid buffered image
242      * @throws ImagingException in the event of a processing errorfileName while reading an image (i.e. a format violation, etc.).
243      * @throws IOException      in the event of an unrecoverable I/O exception.
244      */
245     public static BufferedImage getBufferedImage(final InputStream is) throws ImagingException, IOException {
246         return getBufferedImage(is, null);
247     }
248 
249     /**
250      * Reads the first image from an InputStream.
251      *
252      * <p>
253      * For the most recent information on support for specific formats, refer to <a href="https://commons.apache.org/imaging/formatsupport.html">Format
254      * Support</a> at the main project development web site. While the Apache Commons Imaging package does not fully support all formats, it can read image
255      * info, metadata and ICC profiles from all image formats that provide this data.
256      * </p>
257      *
258      * @param is       a valid ImageStream from which to read data.
259      * @param fileName the image file name.
260      * @return if successful, a valid buffered image
261      * @throws ImagingException in the event of a processing error while reading an image (i.e. a format violation, etc.).
262      * @throws IOException      in the event of an unrecoverable I/O exception.
263      */
264     public static BufferedImage getBufferedImage(final InputStream is, final String fileName) throws ImagingException, IOException {
265         return getBufferedImage(ByteSource.inputStream(is, fileName));
266     }
267 
268     /**
269      * Attempts to determine the image format of the specified data and evaluates its format compliance.
270      *
271      * <p>
272      * This method returns a FormatCompliance object which includes information about the data's compliance to a specific format.
273      * </p>
274      *
275      * @param bytes a valid array of bytes containing image data.
276      * @return if successful, a valid FormatCompliance object.
277      * @throws ImagingException in the event of unreadable data.
278      * @throws IOException      in the event of an unrecoverable I/O condition.
279      */
280     public static FormatCompliance getFormatCompliance(final byte[] bytes) throws ImagingException, IOException {
281         return getFormatCompliance(ByteSource.array(bytes));
282     }
283 
284     private static FormatCompliance getFormatCompliance(final ByteSource byteSource) throws ImagingException, IOException {
285         final AbstractImageParser<?> imageParser = ImageParserFactory.getImageParser(byteSource);
286         return imageParser.getFormatCompliance(byteSource);
287     }
288 
289     /**
290      * Attempts to determine the image format of the specified data and evaluates its format compliance. This method returns a FormatCompliance object which
291      * includes information about the data's compliance to a specific format.
292      *
293      * @param file valid file containing image data
294      * @return if successful, a valid FormatCompliance object.
295      * @throws ImagingException in the event of unreadable data.
296      * @throws IOException      in the event of an unrecoverable I/O condition.
297      */
298     public static FormatCompliance getFormatCompliance(final File file) throws ImagingException, IOException {
299         return getFormatCompliance(ByteSource.file(file));
300     }
301 
302     /**
303      * Extracts an ICC Profile (if present) from JPEG, PNG, PSD (Photoshop) and TIFF images.
304      *
305      * @param bytes Byte array containing an image file.
306      * @return An instance of ICC_Profile or null if the image contains no ICC profile.
307      * @throws ImagingException if it fails to parse the image
308      * @throws IOException      if it fails to read the image data
309      */
310     public static ICC_Profile getIccProfile(final byte[] bytes) throws ImagingException, IOException {
311         return getIccProfile(ByteSource.array(bytes));
312     }
313 
314     protected static ICC_Profile getIccProfile(final ByteSource byteSource) throws ImagingException, IOException {
315         final byte[] bytes = getIccProfileBytes(byteSource);
316         if (bytes == null) {
317             return null;
318         }
319 
320         final IccProfileParser parser = new IccProfileParser();
321         final IccProfileInfo info = parser.getIccProfileInfo(bytes);
322         if (info == null) {
323             return null;
324         }
325         if (info.isSrgb()) {
326             return null;
327         }
328 
329         return ICC_Profile.getInstance(bytes);
330     }
331 
332     /**
333      * Extracts an ICC Profile (if present) from JPEG, PNG, PSD (Photoshop) and TIFF images.
334      *
335      * @param file File containing image data.
336      * @return An instance of ICC_Profile or null if the image contains no ICC profile.
337      * @throws ImagingException if it fails to parse the image
338      * @throws IOException      if it fails to read the image data
339      */
340     public static ICC_Profile getIccProfile(final File file) throws ImagingException, IOException {
341         return getIccProfile(ByteSource.file(file));
342     }
343 
344     /**
345      * Extracts an ICC Profile (if present) from JPEG, PNG, PSD (Photoshop) and TIFF images.
346      *
347      * @param is       InputStream from which to read image data.
348      * @param fileName File name associated with image data (optional).
349      * @return An instance of ICC_Profile or null if the image contains no ICC profile.
350      * @throws ImagingException if it fails to parse the image
351      * @throws IOException      if it fails to read the image data
352      */
353     public static ICC_Profile getIccProfile(final InputStream is, final String fileName) throws ImagingException, IOException {
354         return getIccProfile(ByteSource.inputStream(is, fileName));
355     }
356 
357     /**
358      * Extracts the raw bytes of an ICC Profile (if present) from JPEG, PNG, PSD (Photoshop) and TIFF images.
359      *
360      * <p>
361      * To parse the result use IccProfileParser or ICC_Profile.getInstance(bytes).
362      * </p>
363      *
364      * @param bytes Byte array containing an image file.
365      * @return A byte array.
366      * @see IccProfileParser
367      * @see ICC_Profile
368      * @throws ImagingException if it fails to parse the image
369      * @throws IOException      if it fails to read the image data
370      */
371     public static byte[] getIccProfileBytes(final byte[] bytes) throws ImagingException, IOException {
372         return getIccProfileBytes(ByteSource.array(bytes));
373     }
374 
375     private static byte[] getIccProfileBytes(final ByteSource byteSource) throws ImagingException, IOException {
376         final AbstractImageParser<?> imageParser = ImageParserFactory.getImageParser(byteSource);
377         return imageParser.getIccProfileBytes(byteSource, null);
378     }
379 
380     /**
381      * Extracts the raw bytes of an ICC Profile (if present) from JPEG, PNG, PSD (Photoshop) and TIFF images.
382      *
383      * <p>
384      * To parse the result use IccProfileParser or ICC_Profile.getInstance(bytes).
385      * </p>
386      *
387      * @param file File containing image data.
388      * @return A byte array.
389      * @see IccProfileParser
390      * @see ICC_Profile
391      * @throws ImagingException if it fails to parse the image
392      * @throws IOException      if it fails to read the image data
393      */
394     public static byte[] getIccProfileBytes(final File file) throws ImagingException, IOException {
395         return getIccProfileBytes(ByteSource.file(file));
396     }
397 
398     /**
399      * Parses the "image info" of an image.
400      *
401      * <p>
402      * "Image info" is a summary of basic information about the image such as: width, height, file format, bit depth, color type, etc.
403      * </p>
404      *
405      * <p>
406      * Not to be confused with "image metadata."
407      * </p>
408      *
409      * @param bytes Byte array containing an image file.
410      * @return An instance of ImageInfo.
411      * @see ImageInfo
412      * @throws ImagingException if it fails to parse the image
413      * @throws IOException      if it fails to read the image data
414      */
415     public static ImageInfo getImageInfo(final byte[] bytes) throws ImagingException, IOException {
416         return getImageInfo(ByteSource.array(bytes));
417     }
418 
419     private static ImageInfo getImageInfo(final ByteSource byteSource) throws ImagingException, IOException {
420         return ImageParserFactory.getImageParser(byteSource).getImageInfo(byteSource, null);
421     }
422 
423     /**
424      * Parses the "image info" of an image file.
425      *
426      * <p>
427      * "Image info" is a summary of basic information about the image such as: width, height, file format, bit depth, color type, etc.
428      * </p>
429      *
430      * <p>
431      * Not to be confused with "image metadata."
432      * </p>
433      *
434      * @param file File containing image data.
435      * @return An instance of ImageInfo.
436      * @see ImageInfo
437      * @throws ImagingException if it fails to parse the image
438      * @throws IOException      if it fails to read the image data
439      */
440     public static ImageInfo getImageInfo(final File file) throws ImagingException, IOException {
441         return getImageInfo(ByteSource.file(file));
442     }
443 
444     /**
445      * Parses the "image info" of an image.
446      *
447      * <p>
448      * "Image info" is a summary of basic information about the image such as: width, height, file format, bit depth, color type, etc.
449      * </p>
450      *
451      * <p>
452      * Not to be confused with "image metadata."
453      * </p>
454      *
455      * @param is       InputStream from which to read image data.
456      * @param fileName File name associated with image data (optional).
457      * @return An instance of ImageInfo.
458      * @see ImageInfo
459      * @throws ImagingException if it fails to parse the image
460      * @throws IOException      if it fails to read the image data
461      */
462     public static ImageInfo getImageInfo(final InputStream is, final String fileName) throws ImagingException, IOException {
463         return getImageInfo(ByteSource.inputStream(is, fileName));
464     }
465 
466     /**
467      * Parses the "image info" of an image.
468      *
469      * <p>
470      * "Image info" is a summary of basic information about the image such as: width, height, file format, bit depth, color type, etc.
471      * </p>
472      *
473      * <p>
474      * Not to be confused with "image metadata."
475      * </p>
476      *
477      * @param fileName String.
478      * @param bytes    Byte array containing an image file.
479      * @return An instance of ImageInfo.
480      * @see ImageInfo
481      * @throws ImagingException if it fails to parse the image
482      * @throws IOException      if it fails to read the image data
483      */
484     public static ImageInfo getImageInfo(final String fileName, final byte[] bytes) throws ImagingException, IOException {
485         return getImageInfo(ByteSource.array(bytes, fileName));
486     }
487 
488     /**
489      * Determines the width and height of an image.
490      *
491      * @param bytes Byte array containing an image file.
492      * @return The width and height of the image.
493      * @throws ImagingException if it fails to parse the image
494      * @throws IOException      if it fails to read the image data
495      */
496     public static Dimension getImageSize(final byte[] bytes) throws ImagingException, IOException {
497         return getImageSize(ByteSource.array(bytes));
498     }
499 
500     /**
501      * Determines the width and height of an image byte source.
502      *
503      * @param byteSource Byte source data.
504      * @return The width and height of the image.
505      * @throws ImagingException if it fails to parse the image
506      * @throws IOException      if it fails to read the image data
507      */
508     public static Dimension getImageSize(final ByteSource byteSource) throws ImagingException, IOException {
509         final AbstractImageParser<?> imageParser = ImageParserFactory.getImageParser(byteSource);
510         return imageParser.getImageSize(byteSource, null);
511     }
512 
513     /**
514      * Determines the width and height of an image file.
515      *
516      * @param file File containing image data.
517      * @return The width and height of the image.
518      * @throws ImagingException if it fails to parse the image
519      * @throws IOException      if it fails to read the image data
520      */
521     public static Dimension getImageSize(final File file) throws ImagingException, IOException {
522         return getImageSize(ByteSource.file(file));
523     }
524 
525     /**
526      * Determines the width and height of an image.
527      *
528      * @param is       InputStream from which to read image data.
529      * @param fileName File name associated with image data (optional).
530      * @return The width and height of the image.
531      * @throws ImagingException if it fails to parse the image
532      * @throws IOException      if it fails to read the image data
533      */
534     public static Dimension getImageSize(final InputStream is, final String fileName) throws ImagingException, IOException {
535         return getImageSize(ByteSource.inputStream(is, fileName));
536     }
537 
538     /**
539      * Parses the metadata of an image. This metadata depends on the format of the image.
540      *
541      * <p>
542      * JPEG/JFIF files may contain EXIF and/or IPTC metadata. PNG files may contain comments. TIFF files may contain metadata.
543      * </p>
544      *
545      * <p>
546      * The instance of IImageMetadata returned by getMetadata() should be upcast (depending on image format).
547      * </p>
548      *
549      * <p>
550      * Not to be confused with "image info."
551      * </p>
552      *
553      * @param bytes Byte array containing an image file.
554      * @return An instance of ImageMetadata.
555      * @see org.apache.commons.imaging.common.ImageMetadata
556      * @throws ImagingException if it fails to read the image metadata
557      * @throws IOException      if it fails to read the image data
558      */
559     public static ImageMetadata getMetadata(final byte[] bytes) throws ImagingException, IOException {
560         return getMetadata(ByteSource.array(bytes));
561     }
562 
563     private static ImageMetadata getMetadata(final ByteSource byteSource) throws ImagingException, IOException {
564         final AbstractImageParser<?> imageParser = ImageParserFactory.getImageParser(byteSource);
565         return imageParser.getMetadata(byteSource, null);
566     }
567 
568     /**
569      * Parses the metadata of an image file. This metadata depends on the format of the image.
570      *
571      * <p>
572      * JPEG/JFIF files may contain EXIF and/or IPTC metadata. PNG files may contain comments. TIFF files may contain metadata.
573      * </p>
574      *
575      * <p>
576      * The instance of IImageMetadata returned by getMetadata() should be upcast (depending on image format).
577      * </p>
578      *
579      * <p>
580      * Not to be confused with "image info."
581      * </p>
582      *
583      * @param file File containing image data.
584      * @return An instance of IImageMetadata.
585      * @see org.apache.commons.imaging.common.ImageMetadata
586      * @throws ImagingException if it fails to read the image metadata
587      * @throws IOException      if it fails to read the image data
588      */
589     public static ImageMetadata getMetadata(final File file) throws ImagingException, IOException {
590         return getMetadata(ByteSource.file(file));
591     }
592 
593     /**
594      * Parses the metadata of an image file. This metadata depends on the format of the image.
595      *
596      * <p>
597      * JPEG/JFIF files may contain EXIF and/or IPTC metadata. PNG files may contain comments. TIFF files may contain metadata.
598      * </p>
599      *
600      * <p>
601      * The instance of IImageMetadata returned by getMetadata() should be upcast (depending on image format).
602      * </p>
603      *
604      * <p>
605      * Not to be confused with "image info."
606      * </p>
607      *
608      * @param is       InputStream from which to read image data.
609      * @param fileName File name associated with image data (optional).
610      * @return An instance of IImageMetadata.
611      * @see org.apache.commons.imaging.common.ImageMetadata
612      * @throws ImagingException if it fails to read the image metadata
613      * @throws IOException      if it fails to read the image data
614      */
615     public static ImageMetadata getMetadata(final InputStream is, final String fileName) throws ImagingException, IOException {
616         return getMetadata(ByteSource.inputStream(is, fileName));
617     }
618 
619     /**
620      * Extracts the embedded XML metadata as an XML string.
621      *
622      * @param bytes Byte array containing an image file.
623      * @return Xmp Xml as String, if present. Otherwise, returns null.
624      * @throws ImagingException if it fails to parse the image
625      * @throws IOException      if it fails to read the image data
626      */
627     public static String getXmpXml(final byte[] bytes) throws ImagingException, IOException {
628         return getXmpXml(ByteSource.array(bytes));
629     }
630 
631     /**
632      * Extracts the embedded XML metadata as an XML string.
633      *
634      * @param byteSource File containing image data.
635      * @return Xmp Xml as String, if present. Otherwise, returns null.
636      * @throws ImagingException if it fails to parse the image
637      * @throws IOException      if it fails to read the image data
638      */
639     public static String getXmpXml(final ByteSource byteSource) throws ImagingException, IOException {
640         final AbstractImageParser<?> imageParser = ImageParserFactory.getImageParser(byteSource);
641         if (imageParser instanceof XmpEmbeddable) {
642             return ((XmpEmbeddable<?>) imageParser).getXmpXml(byteSource, null);
643         }
644         return null;
645     }
646 
647     /**
648      * Extracts the embedded XML metadata as an XML string.
649      *
650      * @param file File containing image data.
651      * @return Xmp Xml as String, if present. Otherwise, returns null.
652      * @throws ImagingException if it fails to parse the image
653      * @throws IOException      if it fails to read the image data
654      */
655     public static String getXmpXml(final File file) throws ImagingException, IOException {
656         return getXmpXml(ByteSource.file(file));
657     }
658 
659     /**
660      * Extracts the embedded XML metadata as an XML string.
661      *
662      * @param is       InputStream from which to read image data.
663      * @param fileName File name associated with image data (optional).
664      * @return Xmp Xml as String, if present. Otherwise, returns null.
665      * @throws ImagingException if it fails to parse the image
666      * @throws IOException      if it fails to read the image data
667      */
668     public static String getXmpXml(final InputStream is, final String fileName) throws ImagingException, IOException {
669         return getXmpXml(ByteSource.inputStream(is, fileName));
670     }
671 
672     /**
673      * Attempts to determine the image format of a file based on its "magic numbers," the first bytes of the data.
674      *
675      * <p>
676      * Many graphics format specify identifying byte values that appear at the beginning of the data file. This method checks for such identifying elements and
677      * returns a ImageFormat enumeration indicating what it detects. Note that this method can return "false positives" in cases where non-image files begin
678      * with the specified byte values.
679      * </p>
680      *
681      * @param bytes Byte array containing an image file.
682      * @return An ImageFormat, such as ImageFormat.IMAGE_FORMAT_JPEG. Returns ImageFormat.IMAGE_FORMAT_UNKNOWN if the image type cannot be determined.
683      * @throws IOException in the event of an unrecoverable I/O condition.
684      */
685     public static ImageFormat guessFormat(final byte[] bytes) throws IOException {
686         return guessFormat(ByteSource.array(bytes));
687     }
688 
689     /**
690      * Attempts to determine the image format of a file based on its "magic numbers," the first bytes of the data.
691      *
692      * <p>
693      * Many graphics formats specify identifying byte values that appear at the beginning of the data file. This method checks for such identifying elements and
694      * returns a ImageFormat enumeration indicating what it detects. Note that this method can return "false positives" in cases where non-image files begin
695      * with the specified byte values.
696      * </p>
697      *
698      * @param byteSource a valid ByteSource object potentially supplying data for an image.
699      * @return An ImageFormat, such as ImageFormat.IMAGE_FORMAT_JPEG. Returns ImageFormat.IMAGE_FORMAT_UNKNOWN if the image type cannot be determined.
700      * @throws IllegalArgumentException in the event of an unsuccessful attempt to read the image data
701      * @throws IOException              in the event of an unrecoverable I/O condition.
702      */
703     public static ImageFormat guessFormat(final ByteSource byteSource) throws IOException {
704         if (byteSource == null) {
705             return ImageFormats.UNKNOWN;
706         }
707 
708         try (InputStream is = byteSource.getInputStream()) {
709             final int i1 = is.read();
710             final int i2 = is.read();
711             if (i1 < 0 || i2 < 0) {
712                 throw new IllegalArgumentException("Couldn't read magic numbers to guess format.");
713             }
714 
715             final int b1 = i1 & 0xff;
716             final int b2 = i2 & 0xff;
717             final int[] bytePair = { b1, b2, };
718 
719             if (compareBytePair(MAGIC_NUMBERS_GIF, bytePair)) {
720                 return ImageFormats.GIF;
721                 // } else if (b1 == 0x00 && b2 == 0x00) // too similar to TGA
722                 // {
723                 // return ImageFormat.IMAGE_FORMAT_ICO;
724             }
725             if (compareBytePair(MAGIC_NUMBERS_PNG, bytePair)) {
726                 return ImageFormats.PNG;
727             }
728             if (compareBytePair(MAGIC_NUMBERS_JPEG, bytePair)) {
729                 return ImageFormats.JPEG;
730             }
731             if (compareBytePair(MAGIC_NUMBERS_BMP, bytePair)) {
732                 return ImageFormats.BMP;
733             }
734             if (compareBytePair(MAGIC_NUMBERS_TIFF_MOTOROLA, bytePair)) {
735                 return ImageFormats.TIFF;
736             }
737             if (compareBytePair(MAGIC_NUMBERS_TIFF_INTEL, bytePair)) {
738                 return ImageFormats.TIFF;
739             }
740             if (compareBytePair(MAGIC_NUMBERS_PSD, bytePair)) {
741                 return ImageFormats.PSD;
742             }
743             if (compareBytePair(MAGIC_NUMBERS_PAM, bytePair)) {
744                 return ImageFormats.PAM;
745             }
746             if (compareBytePair(MAGIC_NUMBERS_PBM_A, bytePair)) {
747                 return ImageFormats.PBM;
748             }
749             if (compareBytePair(MAGIC_NUMBERS_PBM_B, bytePair)) {
750                 return ImageFormats.PBM;
751             }
752             if (compareBytePair(MAGIC_NUMBERS_PGM_A, bytePair)) {
753                 return ImageFormats.PGM;
754             }
755             if (compareBytePair(MAGIC_NUMBERS_PGM_B, bytePair)) {
756                 return ImageFormats.PGM;
757             }
758             if (compareBytePair(MAGIC_NUMBERS_PPM_A, bytePair)) {
759                 return ImageFormats.PPM;
760             }
761             if (compareBytePair(MAGIC_NUMBERS_PPM_B, bytePair)) {
762                 return ImageFormats.PPM;
763             }
764             if (compareBytePair(MAGIC_NUMBERS_JBIG2_1, bytePair)) {
765                 final int i3 = is.read();
766                 final int i4 = is.read();
767                 if (i3 < 0 || i4 < 0) {
768                     throw new IllegalArgumentException("Couldn't read magic numbers to guess format.");
769                 }
770 
771                 final int b3 = i3 & 0xff;
772                 final int b4 = i4 & 0xff;
773                 final int[] bytePair2 = { b3, b4, };
774                 if (compareBytePair(MAGIC_NUMBERS_JBIG2_2, bytePair2)) {
775                     return ImageFormats.JBIG2;
776                 }
777             } else if (compareBytePair(MAGIC_NUMBERS_ICNS, bytePair)) {
778                 return ImageFormats.ICNS;
779             } else if (compareBytePair(MAGIC_NUMBERS_DCX, bytePair)) {
780                 return ImageFormats.DCX;
781             } else if (compareBytePair(MAGIC_NUMBERS_RGBE, bytePair)) {
782                 return ImageFormats.RGBE;
783             } else if (compareBytePair(MAGIC_NUMBERS_RIFF_1, bytePair)) {
784                 final int i3 = is.read();
785                 final int i4 = is.read();
786                 if (i3 < 0 || i4 < 0) {
787                     throw new IllegalArgumentException("Couldn't read magic numbers to guess format.");
788                 }
789 
790                 final int b3 = i3 & 0xff;
791                 final int b4 = i4 & 0xff;
792                 final int[] bytePair2 = { b3, b4, };
793                 if (compareBytePair(MAGIC_NUMBERS_RIFF_2, bytePair2)) {
794                     final byte[] bytes = new byte[4];
795                     if (is.read(bytes) < 4) { // Skip file size
796                         throw new IllegalArgumentException("Couldn't read magic numbers to guess format.");
797                     }
798 
799                     if (is.read(bytes) == 4 && Arrays.equals(MAGIC_NUMBERS_WEBP, bytes)) {
800                         return ImageFormats.WEBP;
801                     }
802                 }
803             }
804             return Stream.of(ImageFormats.values()).filter(imageFormat -> Stream.of(imageFormat.getExtensions()).anyMatch(extension -> {
805                 final String fileName = byteSource.getFileName();
806                 if (fileName == null || fileName.trim().isEmpty()) {
807                     return false;
808                 }
809                 final String fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1);
810                 return extension != null && !extension.trim().isEmpty() && fileExtension.equalsIgnoreCase(extension);
811             })).findFirst().orElse(ImageFormats.UNKNOWN);
812         }
813     }
814 
815     /**
816      * Attempts to determine the image format of a file based on its "magic numbers," the first bytes of the data.
817      *
818      * <p>
819      * Many graphics formats specify identifying byte values that appear at the beginning of the data file. This method checks for such identifying elements and
820      * returns a ImageFormat enumeration indicating what it detects. Note that this method can return "false positives" in cases where non-image files begin
821      * with the specified byte values.
822      * </p>
823      *
824      * @param file File containing image data.
825      * @return An ImageFormat, such as ImageFormat.IMAGE_FORMAT_JPEG. Returns ImageFormat.IMAGE_FORMAT_UNKNOWN if the image type cannot be determined.
826      * @throws IOException in the event of an unrecoverable I/O condition.
827      */
828     public static ImageFormat guessFormat(final File file) throws IOException {
829         return guessFormat(ByteSource.file(file));
830     }
831 
832     /**
833      * Attempts to determine if a file contains an image recorded in a supported graphics format based on its file-name extension (for example "&#46;jpg",
834      * "&#46;gif", "&#46;png", etc&#46;).
835      *
836      * @param file A valid File object providing a reference to a file that may contain an image.
837      * @return true if the file-name includes a supported image format file extension; otherwise, false.
838      */
839     public static boolean hasImageFileExtension(final File file) {
840         if (file == null || !file.isFile()) {
841             return false;
842         }
843         return hasImageFileExtension(file.getName());
844     }
845 
846     /**
847      * Attempts to determine if a file contains an image recorded in a supported graphics format based on its file-name extension (for example "&#46;jpg",
848      * "&#46;gif", "&#46;png", etc&#46;).
849      *
850      * @param fileName A valid string representing name of file which may contain an image.
851      * @return true if the file name has an image format file extension.
852      */
853     public static boolean hasImageFileExtension(final String fileName) {
854         if (fileName == null) {
855             return false;
856         }
857 
858         final String normalizedFileName = fileName.toLowerCase(Locale.ROOT);
859 
860         for (final AbstractImageParser<?> imageParser : AbstractImageParser.getAllImageParsers()) {
861             for (final String extension : imageParser.getAcceptedExtensions()) {
862                 if (normalizedFileName.endsWith(extension.toLowerCase(Locale.ROOT))) {
863                     return true;
864                 }
865             }
866         }
867 
868         return false;
869     }
870 
871     /**
872      * Writes the content of a BufferedImage to a file using the specified image format.
873      *
874      * <p>
875      * Image writing is not supported for all graphics formats. For the most recent information on support for specific formats, refer to
876      * <a href="https://commons.apache.org/imaging/formatsupport.html">Format Support</a> at the main project development web site. While the Apache Commons
877      * Imaging package does not fully support all formats, it can read image info, metadata and ICC profiles from all image formats that provide this data.
878      * </p>
879      *
880      * @param src    a valid BufferedImage object
881      * @param file   the file to which the output image is to be written
882      * @param format the format in which the output image is to be written
883      * @throws ImagingException in the event of a format violation, unsupported image format, etc.
884      * @throws IOException      in the event of an unrecoverable I/O exception.
885      * @see ImagingConstants
886      */
887     public static void writeImage(final BufferedImage src, final File file, final ImageFormat format) throws ImagingException, IOException {
888         try (FileOutputStream fos = new FileOutputStream(file);
889                 BufferedOutputStream os = new BufferedOutputStream(fos)) {
890             writeImage(src, os, format);
891         }
892     }
893 
894     /**
895      * Writes the content of a BufferedImage to an OutputStream using the specified image format.
896      *
897      * <p>
898      * Image writing is not supported for all graphics formats. For the most recent information on support for specific formats, refer to
899      * <a href="https://commons.apache.org/imaging/formatsupport.html">Format Support</a> at the main project development web site. While the Apache Commons
900      * Imaging package does not fully support all formats, it can read image info, metadata and ICC profiles from all image formats that provide this data.
901      * </p>
902      *
903      * @param src          a valid BufferedImage object
904      * @param outputStream the OutputStream to which the output image is to be written
905      * @param format       the format in which the output image is to be written
906      * @throws ImagingException in the event of a format violation, unsupported image format, etc.
907      * @throws IOException      in the event of an unrecoverable I/O exception.
908      * @see ImagingConstants
909      */
910     public static void writeImage(final BufferedImage src, final OutputStream outputStream, final ImageFormat format) throws ImagingException, IOException {
911         Objects.requireNonNull(src, "src");
912         Objects.requireNonNull(outputStream, "outputStream");
913         Objects.requireNonNull(format, "format");
914 
915         final AbstractImageParser<?> imageParser = ImageParserFactory.getImageParser(format);
916         imageParser.writeImage(src, outputStream, null);
917     }
918 
919     /**
920      * Writes the content of a BufferedImage to a byte array using the specified image format.
921      *
922      * <p>
923      * Image writing is not supported for all graphics formats. For the most recent information on support for specific formats, refer to
924      * <a href="https://commons.apache.org/imaging/formatsupport.html">Format Support</a> at the main project development web site. While the Apache Commons
925      * Imaging package does not fully support all formats, it can read image info, metadata and ICC profiles from all image formats that provide this data.
926      * </p>
927      *
928      * @param src    a valid BufferedImage object
929      * @param format the format in which the output image is to be written
930      * @return if successful, a valid array of bytes.
931      * @throws ImagingException in the event of a format violation, unsupported image format, etc.
932      * @throws IOException      in the event of an unrecoverable I/O exception.
933      * @see ImagingConstants
934      */
935     public static byte[] writeImageToBytes(final BufferedImage src, final ImageFormat format) throws ImagingException, IOException {
936         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
937             writeImage(src, os, format);
938             return os.toByteArray();
939         }
940     }
941 
942     private Imaging() {
943         // Instances can not be created
944     }
945 }