    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;
    041import org.apache.commons.lang3.StringUtils;
    042
    043/**
    044 * The primary application programming interface (API) to the Imaging library.
    045 *
    046 * <h2>Application Notes</h2>
    047 *
    048 * <h3>Using this class</h3>
    049 *
    050 * <p>
    051 * 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
    052 * class is similar to the Java API's ImageIO class, though Imaging supports formats not included in the standard Java API.
    053 * </p>
    054 *
    055 * <p>
    056 * All of methods provided by the Imaging class are declared static.
    057 * </p>
    058 *
    059 * <p>
    060 * The Apache Commons Imaging package is a pure Java implementation.
    061 * </p>
    062 *
    063 * <h3>Format support</h3>
    064 *
    065 * <p>
    066 * 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
    067 * information on support for specific formats, refer to <a href="https://commons.apache.org/imaging/formatsupport.html">Format Support</a> at the main project
    068 * development web site.
    069 * </p>
    070 *
    071 * <h3>Optional parameters for image reading and writing</h3>
    072 *
    073 * <p>
    074 * Many of the operations provided in this class as static calls can be accessed directly using format-specific {@link AbstractImageParser} instances. These
    075 * static methods are provided for convenience in simple use cases.
    076 * </p>
    077 *
    078 * <h3>Example code</h3>
    079 *
    080 * <p>
    081 * See the source of the SampleUsage class and other classes in the org.apache.commons.imaging.examples package for examples.
    082 * </p>
    083 *
    084 * @see <a href="https://svn.apache.org/repos/asf/commons/proper/imaging/trunk/src/test/java/org/apache/commons/imaging/examples/SampleUsage.java">
    085 *      org.apache.commons.imaging.examples.SampleUsage</a>
    086 * @see <a href="https://commons.apache.org/imaging/formatsupport.html">Format Support</a>
    087 */
    088public final class Imaging {
    089
    090    private static final int[] MAGIC_NUMBERS_GIF = { 0x47, 0x49, };
    091    private static final int[] MAGIC_NUMBERS_PNG = { 0x89, 0x50, };
    092    private static final int[] MAGIC_NUMBERS_JPEG = { 0xff, 0xd8, };
    093    private static final int[] MAGIC_NUMBERS_BMP = { 0x42, 0x4d, };
    094    private static final int[] MAGIC_NUMBERS_TIFF_MOTOROLA = { 0x4D, 0x4D, };
    095    private static final int[] MAGIC_NUMBERS_TIFF_INTEL = { 0x49, 0x49, };
    096    private static final int[] MAGIC_NUMBERS_PAM = { 0x50, 0x37, };
    097    private static final int[] MAGIC_NUMBERS_PSD = { 0x38, 0x42, };
    098    private static final int[] MAGIC_NUMBERS_PBM_A = { 0x50, 0x31, };
    099    private static final int[] MAGIC_NUMBERS_PBM_B = { 0x50, 0x34, };
    100    private static final int[] MAGIC_NUMBERS_PGM_A = { 0x50, 0x32, };
    101    private static final int[] MAGIC_NUMBERS_PGM_B = { 0x50, 0x35, };
    102    private static final int[] MAGIC_NUMBERS_PPM_A = { 0x50, 0x33, };
    103    private static final int[] MAGIC_NUMBERS_PPM_B = { 0x50, 0x36, };
    104    private static final int[] MAGIC_NUMBERS_JBIG2_1 = { 0x97, 0x4A, };
    105    private static final int[] MAGIC_NUMBERS_JBIG2_2 = { 0x42, 0x32, };
    106    private static final int[] MAGIC_NUMBERS_ICNS = { 0x69, 0x63, };
    107    private static final int[] MAGIC_NUMBERS_DCX = { 0xB1, 0x68, };
    108    private static final int[] MAGIC_NUMBERS_RGBE = { 0x23, 0x3F, };
    109    private static final int[] MAGIC_NUMBERS_RIFF_1 = { 0x52, 0x49, };
    110    private static final int[] MAGIC_NUMBERS_RIFF_2 = { 0x46, 0x46, };
    111    private static final byte[] MAGIC_NUMBERS_WEBP = { 0x57, 0x45, 0x42, 0x50, };
    112
    113    private static boolean compareBytePair(final int[] a, final int[] b) {
    114        if (a.length != 2 && b.length != 2) {
    115            throw new IllegalArgumentException("Invalid Byte Pair.");
    116        }
    117        return a[0] == b[0] && a[1] == b[1];
    118    }
    119
    120    /**
    121     * Writes the ImageInfo and format-specific information for the image content of the specified byte array to a string.
    122     *
    123     * @param bytes A valid array of bytes.
    124     * @return A valid string.
    125     * @throws ImagingException In the event that the specified content does not conform to the format of the specific parser implementation.
    126     * @throws IOException      In the event of unsuccessful read or access operation.
    127     */
    128    public static String dumpImageFile(final byte[] bytes) throws ImagingException, IOException {
    129        return dumpImageFile(ByteSource.array(bytes));
    130    }
    131
    132    private static String dumpImageFile(final ByteSource byteSource) throws ImagingException, IOException {
    133        final AbstractImageParser<?> imageParser = ImageParserFactory.getImageParser(byteSource);
    134        return imageParser.dumpImageFile(byteSource);
    135    }
    136
    137    /**
    138     * Writes the ImageInfo and format-specific information for the image content of the specified file to a string.
    139     *
    140     * @param file A valid file reference.
    141     * @return A valid string.
    142     * @throws ImagingException In the event that the specified content does not conform to the format of the specific parser implementation.
    143     * @throws IOException      In the event of unsuccessful read or access operation.
    144     */
    145    public static String dumpImageFile(final File file) throws ImagingException, IOException {
    146        return dumpImageFile(ByteSource.file(file));
    147    }
    148
    149    /**
    150     * Gets all images specified by the byte array (some formats may include multiple images within a single data source).
    151     *
    152     * @param bytes a valid array of bytes
    153     * @return A valid (potentially empty) list of BufferedImage objects.
    154     * @throws ImagingException In the event that the specified content does not conform to the format of the specific parser implementation.
    155     * @throws IOException      In the event of unsuccessful read or access operation.
    156     */
    157    public static List<BufferedImage> getAllBufferedImages(final byte[] bytes) throws ImagingException, IOException {
    158        return getAllBufferedImages(ByteSource.array(bytes));
    159    }
    160
    161    private static List<BufferedImage> getAllBufferedImages(final ByteSource byteSource) throws ImagingException, IOException {
    162        final AbstractImageParser<?> imageParser = ImageParserFactory.getImageParser(byteSource);
    163        return imageParser.getAllBufferedImages(byteSource);
    164    }
    165
    166    /**
    167     * Gets all images specified by the file (some formats may include multiple images within a single data source).
    168     *
    169     * @param file A reference to a valid data file.
    170     * @return A valid (potentially empty) list of BufferedImage objects.
    171     * @throws ImagingException In the event that the specified content does not conform to the format of the specific parser implementation.
    172     * @throws IOException      In the event of unsuccessful read or access operation.
    173     */
    174    public static List<BufferedImage> getAllBufferedImages(final File file) throws ImagingException, IOException {
    175        return getAllBufferedImages(ByteSource.file(file));
    176    }
    177
    178    /**
    179     * Gets all images specified by the InputStream (some formats may include multiple images within a single data source).
    180     *
    181     * @param is       A valid InputStream
    182     * @param fileName File name associated with image data (optional).
    183     * @return A valid (potentially empty) list of BufferedImage objects.
    184     * @throws ImagingException In the event that the specified content does not conform to the format of the specific parser implementation.
    185     * @throws IOException      In the event of unsuccessful read or access operation.
    186     */
    187    public static List<BufferedImage> getAllBufferedImages(final InputStream is, final String fileName) throws ImagingException, IOException {
    188        return getAllBufferedImages(ByteSource.inputStream(is, fileName));
    189    }
    190
    191    /**
    192     * Reads the first image from a byte array.
    193     *
    194     * <p>
    195     * For the most recent information on support for specific formats, refer to <a href="https://commons.apache.org/imaging/formatsupport.html">Format
    196     * 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
    197     * info, metadata and ICC profiles from all image formats that provide this data.
    198     * </p>
    199     *
    200     * @param bytes a valid array of bytes from which to read data.
    201     * @return if successful, a valid buffered image
    202     * @throws ImagingException in the event of a processing error while reading an image (i.e. a format violation, etc.).
    203     * @throws IOException      in the event of an unrecoverable I/O exception.
    204     */
    205    public static BufferedImage getBufferedImage(final byte[] bytes) throws ImagingException, IOException {
    206        return getBufferedImage(ByteSource.array(bytes));
    207    }
    208
    209    private static BufferedImage getBufferedImage(final ByteSource byteSource) throws ImagingException, IOException {
    210        return ImageParserFactory.getImageParser(byteSource).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 (StringUtils.isEmpty(fileName)) {
    807                    return false;
    808                }
    809                final String fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1);
    810                return 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 cannot be created
    944    }
    945}