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;
018
019import java.awt.Dimension;
020import java.awt.color.ICC_Profile;
021import java.awt.image.BufferedImage;
022import java.io.BufferedOutputStream;
023import java.io.ByteArrayOutputStream;
024import java.io.File;
025import java.io.FileOutputStream;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.OutputStream;
029import java.util.Arrays;
030import java.util.List;
031import java.util.Locale;
032import java.util.Objects;
033import java.util.stream.Stream;
034
035import org.apache.commons.imaging.bytesource.ByteSource;
036import org.apache.commons.imaging.common.ImageMetadata;
037import org.apache.commons.imaging.common.XmpEmbeddable;
038import org.apache.commons.imaging.icc.IccProfileInfo;
039import org.apache.commons.imaging.icc.IccProfileParser;
040import org.apache.commons.imaging.internal.ImageParserFactory;
041
042/**
043 * The primary application programming interface (API) to the Imaging library.
044 *
045 * <h2>Application Notes</h2>
046 *
047 * <h3>Using this class</h3>
048 *
049 * <p>
050 * 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
051 * class is similar to the Java API's ImageIO class, though Imaging supports formats not included in the standard Java API.
052 * </p>
053 *
054 * <p>
055 * All of methods provided by the Imaging class are declared static.
056 * </p>
057 *
058 * <p>
059 * The Apache Commons Imaging package is a pure Java implementation.
060 * </p>
061 *
062 * <h3>Format support</h3>
063 *
064 * <p>
065 * 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
066 * information on support for specific formats, refer to <a href="https://commons.apache.org/imaging/formatsupport.html">Format Support</a> at the main project
067 * development web site.
068 * </p>
069 *
070 * <h3>Optional parameters for image reading and writing</h3>
071 *
072 * <p>
073 * Many of the operations provided in this class as static calls can be accessed directly using format-specific {@link AbstractImageParser} instances. These
074 * static methods are provided for convenience in simple use cases.
075 * </p>
076 *
077 * <h3>Example code</h3>
078 *
079 * <p>
080 * See the source of the SampleUsage class and other classes in the org.apache.commons.imaging.examples package for examples.
081 * </p>
082 *
083 * @see <a href="https://svn.apache.org/repos/asf/commons/proper/imaging/trunk/src/test/java/org/apache/commons/imaging/examples/SampleUsage.java">
084 *      org.apache.commons.imaging.examples.SampleUsage</a>
085 * @see <a href="https://commons.apache.org/imaging/formatsupport.html">Format Support</a>
086 */
087public final class Imaging {
088
089    private static final int[] MAGIC_NUMBERS_GIF = { 0x47, 0x49, };
090    private static final int[] MAGIC_NUMBERS_PNG = { 0x89, 0x50, };
091    private static final int[] MAGIC_NUMBERS_JPEG = { 0xff, 0xd8, };
092    private static final int[] MAGIC_NUMBERS_BMP = { 0x42, 0x4d, };
093    private static final int[] MAGIC_NUMBERS_TIFF_MOTOROLA = { 0x4D, 0x4D, };
094    private static final int[] MAGIC_NUMBERS_TIFF_INTEL = { 0x49, 0x49, };
095    private static final int[] MAGIC_NUMBERS_PAM = { 0x50, 0x37, };
096    private static final int[] MAGIC_NUMBERS_PSD = { 0x38, 0x42, };
097    private static final int[] MAGIC_NUMBERS_PBM_A = { 0x50, 0x31, };
098    private static final int[] MAGIC_NUMBERS_PBM_B = { 0x50, 0x34, };
099    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.ENGLISH);
859
860        for (final AbstractImageParser<?> imageParser : AbstractImageParser.getAllImageParsers()) {
861            for (final String extension : imageParser.getAcceptedExtensions()) {
862                if (normalizedFileName.endsWith(extension.toLowerCase(Locale.ENGLISH))) {
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}