/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.imageformat; import javax.annotation.Nullable; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import com.facebook.common.internal.ByteStreams; import com.facebook.common.internal.Closeables; import com.facebook.common.internal.Preconditions; import com.facebook.common.internal.Throwables; /** * Detects the format of an encoded image. */ public class ImageFormatChecker { private static ImageFormatChecker sInstance; private int mMaxHeaderLength; @Nullable private List<ImageFormat.FormatChecker> mCustomImageFormatCheckers; private final ImageFormat.FormatChecker mDefaultFormatChecker = new DefaultImageFormatChecker(); private ImageFormatChecker() { updateMaxHeaderLength(); } public void setCustomImageFormatCheckers( @Nullable List<ImageFormat.FormatChecker> customImageFormatCheckers) { mCustomImageFormatCheckers = customImageFormatCheckers; updateMaxHeaderLength(); } public ImageFormat determineImageFormat(final InputStream is) throws IOException { Preconditions.checkNotNull(is); final byte[] imageHeaderBytes = new byte[mMaxHeaderLength]; final int headerSize = readHeaderFromStream(mMaxHeaderLength, is, imageHeaderBytes); if (mCustomImageFormatCheckers != null) { for (ImageFormat.FormatChecker formatChecker : mCustomImageFormatCheckers) { ImageFormat format = formatChecker.determineFormat(imageHeaderBytes, headerSize); if (format != null && format != ImageFormat.UNKNOWN) { return format; } } } ImageFormat format = mDefaultFormatChecker.determineFormat(imageHeaderBytes, headerSize); if (format == null) { format = ImageFormat.UNKNOWN; } return format; } private void updateMaxHeaderLength() { mMaxHeaderLength = mDefaultFormatChecker.getHeaderSize(); if (mCustomImageFormatCheckers != null) { for (ImageFormat.FormatChecker checker : mCustomImageFormatCheckers) { mMaxHeaderLength = Math.max(mMaxHeaderLength, checker.getHeaderSize()); } } } /** * Reads up to maxHeaderLength bytes from is InputStream. If mark is supported by is, it is * used to restore content of the stream after appropriate amount of data is read. * Read bytes are stored in imageHeaderBytes, which should be capable of storing * maxHeaderLength bytes. * @param maxHeaderLength the maximum header length * @param is * @param imageHeaderBytes * @return number of bytes read from is * @throws IOException */ private static int readHeaderFromStream( int maxHeaderLength, final InputStream is, final byte[] imageHeaderBytes) throws IOException { Preconditions.checkNotNull(is); Preconditions.checkNotNull(imageHeaderBytes); Preconditions.checkArgument(imageHeaderBytes.length >= maxHeaderLength); // If mark is supported by the stream, use it to let the owner of the stream re-read the same // data. Otherwise, just consume some data. if (is.markSupported()) { try { is.mark(maxHeaderLength); return ByteStreams.read(is, imageHeaderBytes, 0, maxHeaderLength); } finally { is.reset(); } } else { return ByteStreams.read(is, imageHeaderBytes, 0, maxHeaderLength); } } /** * Get the currently used instance of the image format checker * @return the image format checker to use */ public static synchronized ImageFormatChecker getInstance() { if (sInstance == null) { sInstance = new ImageFormatChecker(); } return sInstance; } /** * Tries to read up to MAX_HEADER_LENGTH bytes from InputStream is and use read bytes to * determine type of the image contained in is. If provided input stream does not support mark, * then this method consumes data from is and it is not safe to read further bytes from is after * this method returns. Otherwise, if mark is supported, it will be used to preserve original * content of is. * @param is * @return ImageFormat matching content of is InputStream or UNKNOWN if no type is suitable * @throws IOException if exception happens during read */ public static ImageFormat getImageFormat(final InputStream is) throws IOException { return getInstance().determineImageFormat(is); } /* * A variant of getImageFormat that wraps IOException with RuntimeException. * This relieves clients of implementing dummy rethrow try-catch block. */ public static ImageFormat getImageFormat_WrapIOException(final InputStream is) { try { return getImageFormat(is); } catch (IOException ioe) { throw Throwables.propagate(ioe); } } /** * Reads image header from a file indicated by provided filename and determines * its format. This method does not throw IOException if one occurs. In this case, * {@link ImageFormat#UNKNOWN} will be returned. * @param filename * @return ImageFormat for image stored in filename */ public static ImageFormat getImageFormat(String filename) { FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(filename); return getImageFormat(fileInputStream); } catch (IOException ioe) { return ImageFormat.UNKNOWN; } finally { Closeables.closeQuietly(fileInputStream); } } }