/* * 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.imagepipeline.bitmaps; import javax.annotation.Nullable; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; import android.util.DisplayMetrics; import com.facebook.common.references.CloseableReference; import com.facebook.common.internal.Preconditions; /** * Bitmap factory optimized for the platform. */ public abstract class PlatformBitmapFactory { /** * Creates a bitmap of the specified width and height. * * @param width the width of the bitmap * @param height the height of the bitmap * @param bitmapConfig the Bitmap.Config used to create the Bitmap * @return a reference to the bitmap * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( int width, int height, Bitmap.Config bitmapConfig) { return createBitmap(width, height, bitmapConfig, null); } /** * Creates a bitmap of the specified width and height. * The bitmap will be created with the default ARGB_8888 configuration * * @param width the width of the bitmap * @param height the height of the bitmap * @return a reference to the bitmap * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap(int width, int height) { return createBitmap(width, height, Bitmap.Config.ARGB_8888); } /** * Creates a bitmap of the specified width and height. * * @param width the width of the bitmap * @param height the height of the bitmap * @param bitmapConfig the Bitmap.Config used to create the Bitmap * @param callerContext the Tag to track who create the Bitmap * @return a reference to the bitmap * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( int width, int height, Bitmap.Config bitmapConfig, @Nullable Object callerContext) { CloseableReference<Bitmap> reference = createBitmapInternal(width, height, bitmapConfig); addBitmapReference(reference.get(), callerContext); return reference; } /** * Creates a bitmap of the specified width and height. * The bitmap will be created with the default ARGB_8888 configuration * * @param width the width of the bitmap * @param height the height of the bitmap * @param callerContext the Tag to track who create the Bitmap * @return a reference to the bitmap * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( int width, int height, @Nullable Object callerContext) { return createBitmap(width, height, Bitmap.Config.ARGB_8888, callerContext); } /** * Creates a bitmap from the specified source bitmap. * It is initialized with the same density as the original bitmap. * * @param source The bitmap we are copying * @return a reference to the bitmap * @throws IllegalArgumentException if the x, y, width, height values are * outside of the dimensions of the source bitmap, or width is <= 0, * or height is <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap(Bitmap source) { return createBitmap(source, null); } /** * Creates a bitmap from the specified source bitmap. * It is initialized with the same density as the original bitmap. * * @param source The bitmap we are copying * @param callerContext the Tag to track who create the Bitmap * @return a reference to the bitmap * @throws IllegalArgumentException if the x, y, width, height values are * outside of the dimensions of the source bitmap, or width is <= 0, * or height is <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap(Bitmap source, @Nullable Object callerContext) { return createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), callerContext); } /** * Creates a bitmap from the specified subset of the source * bitmap. It is initialized with the same density as the original bitmap. * * @param source The bitmap we are subsetting * @param x The x coordinate of the first pixel in source * @param y The y coordinate of the first pixel in source * @param width The number of pixels in each row * @param height The number of rows * @return a reference to the bitmap * @throws IllegalArgumentException if the x, y, width, height values are * outside of the dimensions of the source bitmap, or width is <= 0, * or height is <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( Bitmap source, int x, int y, int width, int height) { return createBitmap(source, x, y, width, height, null); } /** * Creates a bitmap from the specified subset of the source * bitmap. It is initialized with the same density as the original bitmap. * * @param source The bitmap we are subsetting * @param x The x coordinate of the first pixel in source * @param y The y coordinate of the first pixel in source * @param width The number of pixels in each row * @param height The number of rows * @param callerContext the Tag to track who create the Bitmap * @return a reference to the bitmap * @throws IllegalArgumentException if the x, y, width, height values are * outside of the dimensions of the source bitmap, or width is <= 0, * or height is <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( Bitmap source, int x, int y, int width, int height, @Nullable Object callerContext) { return createBitmap(source, x, y, width, height, null, false, callerContext); } /** * Creates a bitmap from subset of the source bitmap, * transformed by the optional matrix. It is initialized with the same * density as the original bitmap. * * @param source The bitmap we are subsetting * @param x The x coordinate of the first pixel in source * @param y The y coordinate of the first pixel in source * @param width The number of pixels in each row * @param height The number of rows * @param matrix Optional matrix to be applied to the pixels * @param filter true if the source should be filtered. * Only applies if the matrix contains more than just * translation. * @return a reference to the bitmap * @throws IllegalArgumentException if the x, y, width, height values are * outside of the dimensions of the source bitmap, or width is <= 0, * or height is <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( Bitmap source, int x, int y, int width, int height, @Nullable Matrix matrix, boolean filter) { return createBitmap( source, x, y, width, height, matrix, filter, null); } /** * Creates a bitmap from the specified source scaled to have the height and width * as specified. It is initialized with the same density as the original bitmap. * * @param source The bitmap we are subsetting * @param destinationWidth The number of pixels in each row of the final bitmap * @param destinationHeight The number of rows in the final bitmap * @return a reference to the bitmap * @throws IllegalArgumentException if the destinationWidth is <= 0, * or destinationHeight is <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createScaledBitmap( Bitmap source, int destinationWidth, int destinationHeight, boolean filter) { return createScaledBitmap(source, destinationWidth, destinationHeight, filter, null); } /** * Creates a bitmap from the specified source scaled to have the height and width * as specified. It is initialized with the same density as the original bitmap. * * @param source The bitmap we are subsetting * @param destinationWidth The number of pixels in each row of the final bitmap * @param destinationHeight The number of rows in the final bitmap * @param callerContext the Tag to track who create the Bitmap * @return a reference to the bitmap * @throws IllegalArgumentException if the destinationWidth is <= 0, * or destinationHeight is <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createScaledBitmap( Bitmap source, int destinationWidth, int destinationHeight, boolean filter, @Nullable Object callerContext) { checkWidthHeight(destinationWidth, destinationHeight); Matrix matrix = new Matrix(); final int width = source.getWidth(); final int height = source.getHeight(); final float sx = destinationWidth / (float) width; final float sy = destinationHeight / (float) height; matrix.setScale(sx, sy); return createBitmap(source, 0, 0, width, height, matrix, filter, callerContext); } /** * Creates a bitmap from subset of the source bitmap, * transformed by the optional matrix. It is initialized with the same * density as the original bitmap. * * @param source The bitmap we are subsetting * @param x The x coordinate of the first pixel in source * @param y The y coordinate of the first pixel in source * @param width The number of pixels in each row * @param height The number of rows * @param matrix Optional matrix to be applied to the pixels * @param filter true if the source should be filtered. * Only applies if the matrix contains more than just * translation. * @param callerContext the Tag to track who create the Bitmap * @return a reference to the bitmap * @throws IllegalArgumentException if the x, y, width, height values are * outside of the dimensions of the source bitmap, or width is <= 0, * or height is <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( Bitmap source, int x, int y, int width, int height, @Nullable Matrix matrix, boolean filter, @Nullable Object callerContext) { Preconditions.checkNotNull(source, "Source bitmap cannot be null"); checkXYSign(x, y); checkWidthHeight(width, height); checkFinalImageBounds(source, x, y, width, height); // assigned because matrix can modify the final width, height int newWidth = width; int newHeight = height; Canvas canvas = new Canvas(); CloseableReference<Bitmap> bitmapRef; Paint paint; Rect srcRectangle = new Rect(x, y, x + width, y + height); RectF dstRectangle = new RectF(0, 0, width, height); Bitmap.Config newConfig = getSuitableBitmapConfig(source); if (matrix == null || matrix.isIdentity()) { bitmapRef = createBitmap(newWidth, newHeight, newConfig, source.hasAlpha(), callerContext); paint = null; // not needed } else { boolean transformed = !matrix.rectStaysRect(); RectF deviceRectangle = new RectF(); matrix.mapRect(deviceRectangle, dstRectangle); newWidth = Math.round(deviceRectangle.width()); newHeight = Math.round(deviceRectangle.height()); bitmapRef = createBitmap( newWidth, newHeight, transformed ? Bitmap.Config.ARGB_8888 : newConfig, transformed || source.hasAlpha(), callerContext); canvas.translate(-deviceRectangle.left, -deviceRectangle.top); canvas.concat(matrix); paint = new Paint(); paint.setFilterBitmap(filter); if (transformed) { paint.setAntiAlias(true); } } // The new bitmap was created from a known bitmap source so assume that // they use the same density Bitmap bitmap = bitmapRef.get(); bitmap.setDensity(source.getDensity()); if (Build.VERSION.SDK_INT >= 12) { bitmap.setHasAlpha(source.hasAlpha()); } if (Build.VERSION.SDK_INT >= 19) { bitmap.setPremultiplied(source.isPremultiplied()); } canvas.setBitmap(bitmap); canvas.drawBitmap(source, srcRectangle, dstRectangle, paint); canvas.setBitmap(null); return bitmapRef; } /** * Creates a bitmap with the specified width and height. Its * initial density is determined from the given DisplayMetrics. * * @param display Display metrics for the display this bitmap will be * drawn on. * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. * @return a reference to the bitmap * @throws IllegalArgumentException if the width or height are <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( DisplayMetrics display, int width, int height, Bitmap.Config config) { return createBitmap(display, width, height, config, null); } /** * Creates a bitmap with the specified width and height. Its * initial density is determined from the given DisplayMetrics. * * @param display Display metrics for the display this bitmap will be * drawn on. * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. * @param callerContext the Tag to track who create the Bitmap * @return a reference to the bitmap * @throws IllegalArgumentException if the width or height are <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( DisplayMetrics display, int width, int height, Bitmap.Config config, @Nullable Object callerContext) { return createBitmap(display, width, height, config, true, callerContext); } /** * Creates a bitmap with the specified width and height. * * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the * bitmap as opaque. Doing so will clear the bitmap in black * instead of transparent. * @return a reference to the bitmap * @throws IllegalArgumentException if the width or height are <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ private CloseableReference<Bitmap> createBitmap( int width, int height, Bitmap.Config config, boolean hasAlpha) { return createBitmap(width, height, config, hasAlpha, null); } /** * Creates a bitmap with the specified width and height. * * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the * bitmap as opaque. Doing so will clear the bitmap in black * instead of transparent. * @param callerContext the Tag to track who create the Bitmap * @return a reference to the bitmap * @throws IllegalArgumentException if the width or height are <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ private CloseableReference<Bitmap> createBitmap( int width, int height, Bitmap.Config config, boolean hasAlpha, @Nullable Object callerContext) { return createBitmap(null, width, height, config, hasAlpha, callerContext); } /** * Creates a bitmap with the specified width and height. Its initial density is * determined from the given DisplayMetrics. * * @param display Display metrics for the display this bitmap will be drawn on * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the bitmap as opaque * Doing so will clear the bitmap in black instead of transparent * @return a reference to the bitmap * @throws IllegalArgumentException if the width or height are <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ private CloseableReference<Bitmap> createBitmap( DisplayMetrics display, int width, int height, Bitmap.Config config, boolean hasAlpha) { return createBitmap(display, width, height, config, hasAlpha, null); } /** * Creates a bitmap with the specified width and height. Its initial density is * determined from the given DisplayMetrics. * * @param display Display metrics for the display this bitmap will be drawn on * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the bitmap as opaque * Doing so will clear the bitmap in black instead of transparent * @param callerContext the Tag to track who create the Bitmap * @return a reference to the bitmap * @throws IllegalArgumentException if the width or height are <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ private CloseableReference<Bitmap> createBitmap( DisplayMetrics display, int width, int height, Bitmap.Config config, boolean hasAlpha, @Nullable Object callerContext) { checkWidthHeight(width, height); CloseableReference<Bitmap> bitmapRef = createBitmapInternal(width, height, config); Bitmap bitmap = bitmapRef.get(); if (display != null) { bitmap.setDensity(display.densityDpi); } if (Build.VERSION.SDK_INT >= 12) { bitmap.setHasAlpha(hasAlpha); } if (config == Bitmap.Config.ARGB_8888 && !hasAlpha) { bitmap.eraseColor(0xff000000); } addBitmapReference(bitmapRef.get(), callerContext); return bitmapRef; } /** * Creates a bitmap with the specified width and height. Its initial density is * determined from the given DisplayMetrics. * * @param colors The colors to write to the bitmap * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create * @return a reference to the bitmap * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( int[] colors, int width, int height, Bitmap.Config config) { return createBitmap(colors, width, height, config, null); } /** * Creates a bitmap with the specified width and height. Its initial density is * determined from the given DisplayMetrics. * * @param colors The colors to write to the bitmap * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create * @param callerContext the Tag to track who create the Bitmap * @return a reference to the bitmap * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( int[] colors, int width, int height, Bitmap.Config config, @Nullable Object callerContext) { CloseableReference<Bitmap> bitmapRef = createBitmapInternal(width, height, config); Bitmap bitmap = bitmapRef.get(); bitmap.setPixels(colors, 0, width, 0, 0, width, height); addBitmapReference(bitmapRef.get(), callerContext); return bitmapRef; } /** * Creates a bitmap with the specified width and height. Its initial density is * determined from the given DisplayMetrics. * * @param display Display metrics for the display this bitmap will be drawn on * @param colors The colors to write to the bitmap * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create * @return a reference to the bitmap * @throws IllegalArgumentException if the width or height are <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( DisplayMetrics display, int[] colors, int width, int height, Bitmap.Config config) { return createBitmap(display, colors, width, height, config, null); } /** * Creates a bitmap with the specified width and height. Its initial density is * determined from the given DisplayMetrics. * * @param display Display metrics for the display this bitmap will be drawn on * @param colors The colors to write to the bitmap * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create * @param callerContext the Tag to track who create the Bitmap * @return a reference to the bitmap * @throws IllegalArgumentException if the width or height are <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( DisplayMetrics display, int[] colors, int width, int height, Bitmap.Config config, @Nullable Object callerContext) { // Set the stride as the width of the image return createBitmap(display, colors, 0, width, width, height, config, callerContext); } /** * Creates a bitmap with the specified width and height. Its initial density is * determined from the given DisplayMetrics. * * @param display Display metrics for the display this bitmap will be drawn on * @param colors The colors to write to the bitmap * @param offset The index of the first color to read from colors[] * @param stride The number of colors in pixels[] to skip between rows. * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create * @return a reference to the bitmap * @throws IllegalArgumentException if the width or height are <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( DisplayMetrics display, int[] colors, int offset, int stride, int width, int height, Bitmap.Config config) { return createBitmap(display, colors, offset, stride, width, height, config, null); } /** * Creates a bitmap with the specified width and height. Its initial density is * determined from the given DisplayMetrics. * * @param display Display metrics for the display this bitmap will be drawn on * @param colors The colors to write to the bitmap * @param offset The index of the first color to read from colors[] * @param stride The number of colors in pixels[] to skip between rows. * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create * @param callerContext the Tag to track who create the Bitmap * @return a reference to the bitmap * @throws IllegalArgumentException if the width or height are <= 0 * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public CloseableReference<Bitmap> createBitmap( DisplayMetrics display, int[] colors, int offset, int stride, int width, int height, Bitmap.Config config, @Nullable Object callerContext) { CloseableReference<Bitmap> bitmapRef = createBitmap( display, width, height, config, callerContext); Bitmap bitmap = bitmapRef.get(); bitmap.setPixels(colors, offset, stride, 0, 0, width, height); return bitmapRef; } /** * Returns suitable Bitmap Config for the new Bitmap based on the source Bitmap configurations. * * @param source the source Bitmap * @return the Bitmap Config for the new Bitmap */ private static Bitmap.Config getSuitableBitmapConfig(Bitmap source) { Bitmap.Config finalConfig = Bitmap.Config.ARGB_8888; final Bitmap.Config sourceConfig = source.getConfig(); // GIF files generate null configs, assume ARGB_8888 if (sourceConfig != null) { switch (sourceConfig) { case RGB_565: finalConfig = Bitmap.Config.RGB_565; break; case ALPHA_8: finalConfig = Bitmap.Config.ALPHA_8; break; case ARGB_4444: case ARGB_8888: default: finalConfig = Bitmap.Config.ARGB_8888; break; } } return finalConfig; } /** * Common code for checking that width and height are > 0 * * @param width width to ensure is > 0 * @param height height to ensure is > 0 */ private static void checkWidthHeight(int width, int height) { Preconditions.checkArgument(width > 0, "width must be > 0"); Preconditions.checkArgument(height > 0, "height must be > 0"); } /** * Common code for checking that x and y are >= 0 * * @param x x coordinate to ensure is >= 0 * @param y y coordinate to ensure is >= 0 */ private static void checkXYSign(int x, int y) { Preconditions.checkArgument(x >= 0, "x must be >= 0"); Preconditions.checkArgument(y >= 0, "y must be >= 0"); } /** * Common code for checking that x + width and y + height are within image bounds * * @param source the source Bitmap * @param x starting x coordinate of source image subset * @param y starting y coordinate of source image subset * @param width width of the source image subset * @param height height of the source image subset */ private static void checkFinalImageBounds(Bitmap source, int x, int y, int width, int height) { Preconditions.checkArgument( x + width <= source.getWidth(), "x + width must be <= bitmap.width()"); Preconditions.checkArgument( y + height <= source.getHeight(), "y + height must be <= bitmap.height()"); } /** * Creates a bitmap of the specified width and height. This is intended for ImagePipeline's * internal use only. * * @param width the width of the bitmap * @param height the height of the bitmap * @param bitmapConfig the Bitmap.Config used to create the Bitmap * @return a reference to the bitmap * @throws TooManyBitmapsException if the pool is full * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ public abstract CloseableReference<Bitmap> createBitmapInternal( int width, int height, Bitmap.Config bitmapConfig); private static BitmapCreationObserver sBitmapCreationObserver; public void setCreationListener(final BitmapCreationObserver bitmapCreationObserver) { if (sBitmapCreationObserver == null) { sBitmapCreationObserver = bitmapCreationObserver; } } public void addBitmapReference( Bitmap bitmap, @Nullable Object callerContext) { if (sBitmapCreationObserver != null) { sBitmapCreationObserver.onBitmapCreated(bitmap, callerContext); } } /** * Observer that notifies external creation of bitmap using * {@link PlatformBitmapFactory#createBitmap(int, int)} or * {@link PlatformBitmapFactory#createBitmap(int, int, Bitmap.Config)}. */ public interface BitmapCreationObserver { void onBitmapCreated(Bitmap bitmap, @Nullable Object callerContext); } }