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