org.robolectric.shadows.ShadowLegacyBitmap Maven / Gradle / Ivy
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.S;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.Integer.max;
import static java.lang.Integer.min;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.os.Parcel;
import android.util.DisplayMetrics;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
import java.io.FileDescriptor;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
@SuppressWarnings({"UnusedDeclaration"})
@Implements(value = Bitmap.class, isInAndroidSdk = false)
public class ShadowLegacyBitmap extends ShadowBitmap {
/** Number of bytes used internally to represent each pixel */
private static final int INTERNAL_BYTES_PER_PIXEL = 4;
int createdFromResId = -1;
String createdFromPath;
InputStream createdFromStream;
FileDescriptor createdFromFileDescriptor;
byte[] createdFromBytes;
@RealObject private Bitmap realBitmap;
private Bitmap createdFromBitmap;
private Bitmap scaledFromBitmap;
private int createdFromX = -1;
private int createdFromY = -1;
private int createdFromWidth = -1;
private int createdFromHeight = -1;
private int[] createdFromColors;
private Matrix createdFromMatrix;
private boolean createdFromFilter;
private int width;
private int height;
private BufferedImage bufferedImage;
private Bitmap.Config config;
private boolean mutable = true;
private String description = "";
private boolean recycled = false;
private boolean hasMipMap;
private boolean requestPremultiplied = true;
private boolean hasAlpha;
private ColorSpace colorSpace;
@Implementation
protected static Bitmap createBitmap(int width, int height, Bitmap.Config config) {
return createBitmap((DisplayMetrics) null, width, height, config);
}
@Implementation(minSdk = JELLY_BEAN_MR1)
protected static Bitmap createBitmap(
DisplayMetrics displayMetrics, int width, int height, Bitmap.Config config) {
return createBitmap(displayMetrics, width, height, config, true);
}
@Implementation(minSdk = JELLY_BEAN_MR1)
protected static Bitmap createBitmap(
DisplayMetrics displayMetrics,
int width,
int height,
Bitmap.Config config,
boolean hasAlpha) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
checkNotNull(config);
Bitmap scaledBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
ShadowLegacyBitmap shadowBitmap = Shadow.extract(scaledBitmap);
shadowBitmap.setDescription("Bitmap (" + width + " x " + height + ")");
shadowBitmap.width = width;
shadowBitmap.height = height;
shadowBitmap.config = config;
shadowBitmap.hasAlpha = hasAlpha;
shadowBitmap.setMutable(true);
if (displayMetrics != null) {
scaledBitmap.setDensity(displayMetrics.densityDpi);
}
shadowBitmap.bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
if (RuntimeEnvironment.getApiLevel() >= O) {
shadowBitmap.colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
}
return scaledBitmap;
}
@Implementation(minSdk = O)
protected static Bitmap createBitmap(
int width, int height, Bitmap.Config config, boolean hasAlpha, ColorSpace colorSpace) {
checkArgument(colorSpace != null || config == Bitmap.Config.ALPHA_8);
Bitmap bitmap = createBitmap(null, width, height, config, hasAlpha);
ShadowLegacyBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.colorSpace = colorSpace;
return bitmap;
}
@Implementation
protected static Bitmap createBitmap(
Bitmap src, int x, int y, int width, int height, Matrix matrix, boolean filter) {
if (x == 0
&& y == 0
&& width == src.getWidth()
&& height == src.getHeight()
&& (matrix == null || matrix.isIdentity())) {
return src; // Return the original.
}
if (x + width > src.getWidth()) {
throw new IllegalArgumentException("x + width must be <= bitmap.width()");
}
if (y + height > src.getHeight()) {
throw new IllegalArgumentException("y + height must be <= bitmap.height()");
}
Bitmap newBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
ShadowLegacyBitmap shadowNewBitmap = Shadow.extract(newBitmap);
ShadowLegacyBitmap shadowSrcBitmap = Shadow.extract(src);
shadowNewBitmap.appendDescription(shadowSrcBitmap.getDescription());
shadowNewBitmap.appendDescription(" at (" + x + "," + y + ")");
shadowNewBitmap.appendDescription(" with width " + width + " and height " + height);
shadowNewBitmap.createdFromBitmap = src;
shadowNewBitmap.createdFromX = x;
shadowNewBitmap.createdFromY = y;
shadowNewBitmap.createdFromWidth = width;
shadowNewBitmap.createdFromHeight = height;
shadowNewBitmap.createdFromMatrix = matrix;
shadowNewBitmap.createdFromFilter = filter;
shadowNewBitmap.config = src.getConfig();
if (matrix != null) {
ShadowMatrix shadowMatrix = Shadow.extract(matrix);
shadowNewBitmap.appendDescription(" using matrix " + shadowMatrix.getDescription());
// Adjust width and height by using the matrix.
RectF mappedRect = new RectF();
matrix.mapRect(mappedRect, new RectF(0, 0, width, height));
width = Math.round(mappedRect.width());
height = Math.round(mappedRect.height());
}
if (filter) {
shadowNewBitmap.appendDescription(" with filter");
}
// updated if matrix is non-null
shadowNewBitmap.width = width;
shadowNewBitmap.height = height;
shadowNewBitmap.setMutable(true);
newBitmap.setDensity(src.getDensity());
if ((matrix == null || matrix.isIdentity()) && shadowSrcBitmap.bufferedImage != null) {
// Only simple cases are supported for setting image data to the new Bitmap.
shadowNewBitmap.bufferedImage =
shadowSrcBitmap.bufferedImage.getSubimage(x, y, width, height);
}
if (RuntimeEnvironment.getApiLevel() >= O) {
shadowNewBitmap.colorSpace = shadowSrcBitmap.colorSpace;
}
return newBitmap;
}
@Implementation
protected static Bitmap createBitmap(
int[] colors, int offset, int stride, int width, int height, Bitmap.Config config) {
return createBitmap(null, colors, offset, stride, width, height, config);
}
@Implementation(minSdk = JELLY_BEAN_MR1)
protected static Bitmap createBitmap(
DisplayMetrics displayMetrics,
int[] colors,
int offset,
int stride,
int width,
int height,
Bitmap.Config config) {
if (width <= 0) {
throw new IllegalArgumentException("width must be > 0");
}
if (height <= 0) {
throw new IllegalArgumentException("height must be > 0");
}
if (Math.abs(stride) < width) {
throw new IllegalArgumentException("abs(stride) must be >= width");
}
checkNotNull(config);
int lastScanline = offset + (height - 1) * stride;
int length = colors.length;
if (offset < 0
|| (offset + width > length)
|| lastScanline < 0
|| (lastScanline + width > length)) {
throw new ArrayIndexOutOfBoundsException();
}
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
bufferedImage.setRGB(0, 0, width, height, colors, offset, stride);
Bitmap bitmap = createBitmap(bufferedImage, width, height, config);
ShadowLegacyBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.setMutable(false);
shadowBitmap.createdFromColors = colors;
if (displayMetrics != null) {
bitmap.setDensity(displayMetrics.densityDpi);
}
if (RuntimeEnvironment.getApiLevel() >= O) {
shadowBitmap.colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
}
return bitmap;
}
private static Bitmap createBitmap(
BufferedImage bufferedImage, int width, int height, Bitmap.Config config) {
Bitmap newBitmap = Bitmap.createBitmap(width, height, config);
ShadowLegacyBitmap shadowBitmap = Shadow.extract(newBitmap);
shadowBitmap.bufferedImage = bufferedImage;
return newBitmap;
}
@Implementation
protected static Bitmap createScaledBitmap(
Bitmap src, int dstWidth, int dstHeight, boolean filter) {
if (dstWidth == src.getWidth() && dstHeight == src.getHeight() && !filter) {
return src; // Return the original.
}
if (dstWidth <= 0 || dstHeight <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
Bitmap scaledBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
ShadowLegacyBitmap shadowBitmap = Shadow.extract(scaledBitmap);
ShadowLegacyBitmap shadowSrcBitmap = Shadow.extract(src);
shadowBitmap.appendDescription(shadowSrcBitmap.getDescription());
shadowBitmap.appendDescription(" scaled to " + dstWidth + " x " + dstHeight);
if (filter) {
shadowBitmap.appendDescription(" with filter " + filter);
}
shadowBitmap.createdFromBitmap = src;
shadowBitmap.scaledFromBitmap = src;
shadowBitmap.createdFromFilter = filter;
shadowBitmap.width = dstWidth;
shadowBitmap.height = dstHeight;
shadowBitmap.config = src.getConfig();
shadowBitmap.mutable = true;
if (!ImageUtil.scaledBitmap(src, scaledBitmap, filter)) {
shadowBitmap.bufferedImage =
new BufferedImage(dstWidth, dstHeight, BufferedImage.TYPE_INT_ARGB);
shadowBitmap.setPixelsInternal(
new int[shadowBitmap.getHeight() * shadowBitmap.getWidth()],
0,
0,
0,
0,
shadowBitmap.getWidth(),
shadowBitmap.getHeight());
}
if (RuntimeEnvironment.getApiLevel() >= O) {
shadowBitmap.colorSpace = shadowSrcBitmap.colorSpace;
}
return scaledBitmap;
}
@Implementation
protected static Bitmap nativeCreateFromParcel(Parcel p) {
int parceledWidth = p.readInt();
int parceledHeight = p.readInt();
Bitmap.Config parceledConfig = (Bitmap.Config) p.readSerializable();
int[] parceledColors = new int[parceledHeight * parceledWidth];
p.readIntArray(parceledColors);
return createBitmap(
parceledColors, 0, parceledWidth, parceledWidth, parceledHeight, parceledConfig);
}
static int getBytesPerPixel(Bitmap.Config config) {
if (config == null) {
throw new NullPointerException("Bitmap config was null.");
}
switch (config) {
case RGBA_F16:
return 8;
case ARGB_8888:
case HARDWARE:
return 4;
case RGB_565:
case ARGB_4444:
return 2;
case ALPHA_8:
return 1;
default:
throw new IllegalArgumentException("Unknown bitmap config: " + config);
}
}
/**
* Reference to original Bitmap from which this Bitmap was created. {@code null} if this Bitmap
* was not copied from another instance.
*
* @return Original Bitmap from which this Bitmap was created.
*/
@Override
public Bitmap getCreatedFromBitmap() {
return createdFromBitmap;
}
/**
* Resource ID from which this Bitmap was created. {@code 0} if this Bitmap was not created from a
* resource.
*
* @return Resource ID from which this Bitmap was created.
*/
@Override
public int getCreatedFromResId() {
return createdFromResId;
}
/**
* Path from which this Bitmap was created. {@code null} if this Bitmap was not create from a
* path.
*
* @return Path from which this Bitmap was created.
*/
@Override
public String getCreatedFromPath() {
return createdFromPath;
}
/**
* {@link InputStream} from which this Bitmap was created. {@code null} if this Bitmap was not
* created from a stream.
*
* @return InputStream from which this Bitmap was created.
*/
@Override
public InputStream getCreatedFromStream() {
return createdFromStream;
}
/**
* Bytes from which this Bitmap was created. {@code null} if this Bitmap was not created from
* bytes.
*
* @return Bytes from which this Bitmap was created.
*/
@Override
public byte[] getCreatedFromBytes() {
return createdFromBytes;
}
/**
* Horizontal offset within {@link #getCreatedFromBitmap()} of this Bitmap's content, or -1.
*
* @return Horizontal offset within {@link #getCreatedFromBitmap()}.
*/
@Override
public int getCreatedFromX() {
return createdFromX;
}
/**
* Vertical offset within {@link #getCreatedFromBitmap()} of this Bitmap's content, or -1.
*
* @return Vertical offset within {@link #getCreatedFromBitmap()} of this Bitmap's content, or -1.
*/
@Override
public int getCreatedFromY() {
return createdFromY;
}
/**
* Width from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this Bitmap's
* content, or -1.
*
* @return Width from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this
* Bitmap's content, or -1.
*/
@Override
public int getCreatedFromWidth() {
return createdFromWidth;
}
/**
* Height from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this Bitmap's
* content, or -1.
*
* @return Height from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this
* Bitmap's content, or -1.
*/
@Override
public int getCreatedFromHeight() {
return createdFromHeight;
}
/**
* Color array from which this Bitmap was created. {@code null} if this Bitmap was not created
* from a color array.
*
* @return Color array from which this Bitmap was created.
*/
@Override
public int[] getCreatedFromColors() {
return createdFromColors;
}
/**
* Matrix from which this Bitmap's content was transformed, or {@code null}.
*
* @return Matrix from which this Bitmap's content was transformed, or {@code null}.
*/
@Override
public Matrix getCreatedFromMatrix() {
return createdFromMatrix;
}
/**
* {@code true} if this Bitmap was created with filtering.
*
* @return {@code true} if this Bitmap was created with filtering.
*/
@Override
public boolean getCreatedFromFilter() {
return createdFromFilter;
}
@Implementation(minSdk = S)
protected Bitmap asShared() {
setMutable(false);
return realBitmap;
}
@Implementation
protected boolean compress(Bitmap.CompressFormat format, int quality, OutputStream stream) {
appendDescription(" compressed as " + format + " with quality " + quality);
return ImageUtil.writeToStream(realBitmap, format, quality, stream);
}
@Implementation
protected void setPixels(
int[] pixels, int offset, int stride, int x, int y, int width, int height) {
checkBitmapMutable();
setPixelsInternal(pixels, offset, stride, x, y, width, height);
}
void setPixelsInternal(
int[] pixels, int offset, int stride, int x, int y, int width, int height) {
if (bufferedImage == null) {
bufferedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
}
bufferedImage.setRGB(x, y, width, height, pixels, offset, stride);
}
@Implementation
protected int getPixel(int x, int y) {
internalCheckPixelAccess(x, y);
if (bufferedImage != null) {
// Note that getPixel() returns a non-premultiplied ARGB value; if
// config is RGB_565, our return value will likely be more precise than
// on a physical device, since it needs to map each color component from
// 5 or 6 bits to 8 bits.
return bufferedImage.getRGB(x, y);
} else {
return 0;
}
}
@Implementation
protected void setPixel(int x, int y, int color) {
checkBitmapMutable();
internalCheckPixelAccess(x, y);
if (bufferedImage == null) {
bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
bufferedImage.setRGB(x, y, color);
}
/**
* Note that this method will return a RuntimeException unless: - {@code pixels} has the same
* length as the number of pixels of the bitmap. - {@code x = 0} - {@code y = 0} - {@code width}
* and {@code height} height match the current bitmap's dimensions.
*/
@Implementation
protected void getPixels(
int[] pixels, int offset, int stride, int x, int y, int width, int height) {
bufferedImage.getRGB(x, y, width, height, pixels, offset, stride);
}
@Implementation
protected int getRowBytes() {
return getBytesPerPixel(config) * getWidth();
}
@Implementation
protected int getByteCount() {
return getRowBytes() * getHeight();
}
@Implementation
protected void recycle() {
recycled = true;
}
@Implementation
protected final boolean isRecycled() {
return recycled;
}
@Implementation
protected Bitmap copy(Bitmap.Config config, boolean isMutable) {
Bitmap newBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
ShadowLegacyBitmap shadowBitmap = Shadow.extract(newBitmap);
shadowBitmap.createdFromBitmap = realBitmap;
shadowBitmap.config = config;
shadowBitmap.mutable = isMutable;
shadowBitmap.height = getHeight();
shadowBitmap.width = getWidth();
if (bufferedImage != null) {
ColorModel cm = bufferedImage.getColorModel();
WritableRaster raster =
bufferedImage.copyData(bufferedImage.getRaster().createCompatibleWritableRaster());
shadowBitmap.bufferedImage = new BufferedImage(cm, raster, false, null);
}
return newBitmap;
}
@Implementation(minSdk = KITKAT)
protected final int getAllocationByteCount() {
return getRowBytes() * getHeight();
}
@Implementation
protected final Bitmap.Config getConfig() {
return config;
}
@Implementation(minSdk = KITKAT)
protected void setConfig(Bitmap.Config config) {
this.config = config;
}
@Implementation
protected final boolean isMutable() {
return mutable;
}
@Override
public void setMutable(boolean mutable) {
this.mutable = mutable;
}
@Override
public void appendDescription(String s) {
description += s;
}
@Override
public String getDescription() {
return description;
}
@Override
public void setDescription(String s) {
description = s;
}
@Implementation
protected final boolean hasAlpha() {
return hasAlpha && config != Bitmap.Config.RGB_565;
}
@Implementation
protected void setHasAlpha(boolean hasAlpha) {
this.hasAlpha = hasAlpha;
}
@Implementation
protected Bitmap extractAlpha() {
WritableRaster raster = bufferedImage.getAlphaRaster();
BufferedImage alphaImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
alphaImage.getAlphaRaster().setRect(raster);
return createBitmap(alphaImage, getWidth(), getHeight(), Bitmap.Config.ALPHA_8);
}
/**
* This shadow implementation ignores the given paint and offsetXY and simply calls {@link
* #extractAlpha()}.
*/
@Implementation
protected Bitmap extractAlpha(Paint paint, int[] offsetXY) {
return extractAlpha();
}
@Implementation(minSdk = JELLY_BEAN_MR1)
protected final boolean hasMipMap() {
return hasMipMap;
}
@Implementation(minSdk = JELLY_BEAN_MR1)
protected final void setHasMipMap(boolean hasMipMap) {
this.hasMipMap = hasMipMap;
}
@Implementation
protected int getWidth() {
return width;
}
@Implementation(minSdk = KITKAT)
protected void setWidth(int width) {
this.width = width;
}
@Implementation
protected int getHeight() {
return height;
}
@Implementation(minSdk = KITKAT)
protected void setHeight(int height) {
this.height = height;
}
@Implementation
protected int getGenerationId() {
return 0;
}
@Implementation(minSdk = M)
protected Bitmap createAshmemBitmap() {
return realBitmap;
}
@Implementation
protected void eraseColor(int color) {
if (bufferedImage != null) {
int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
Arrays.fill(pixels, color);
}
setDescription(String.format("Bitmap (%d, %d)", width, height));
if (color != 0) {
appendDescription(String.format(" erased with 0x%08x", color));
}
}
@Implementation
protected void writeToParcel(Parcel p, int flags) {
p.writeInt(width);
p.writeInt(height);
p.writeSerializable(config);
int[] pixels = new int[width * height];
getPixels(pixels, 0, width, 0, 0, width, height);
p.writeIntArray(pixels);
}
@Implementation
protected void copyPixelsFromBuffer(Buffer dst) {
if (isRecycled()) {
throw new IllegalStateException("Can't call copyPixelsFromBuffer() on a recycled bitmap");
}
// See the related comment in #copyPixelsToBuffer(Buffer).
if (getBytesPerPixel(config) != INTERNAL_BYTES_PER_PIXEL) {
throw new RuntimeException(
"Not implemented: only Bitmaps with "
+ INTERNAL_BYTES_PER_PIXEL
+ " bytes per pixel are supported");
}
if (!(dst instanceof ByteBuffer) && !(dst instanceof IntBuffer)) {
throw new RuntimeException("Not implemented: unsupported Buffer subclass");
}
ByteBuffer byteBuffer = null;
IntBuffer intBuffer;
if (dst instanceof IntBuffer) {
intBuffer = (IntBuffer) dst;
} else {
byteBuffer = (ByteBuffer) dst;
intBuffer = byteBuffer.asIntBuffer();
}
if (intBuffer.remaining() < (width * height)) {
throw new RuntimeException("Buffer not large enough for pixels");
}
int[] colors = new int[width * height];
intBuffer.get(colors);
if (byteBuffer != null) {
byteBuffer.position(byteBuffer.position() + intBuffer.position() * INTERNAL_BYTES_PER_PIXEL);
}
int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
System.arraycopy(colors, 0, pixels, 0, pixels.length);
}
@Implementation
protected void copyPixelsToBuffer(Buffer dst) {
// Ensure that the Bitmap uses 4 bytes per pixel, since we always use 4 bytes per pixels
// internally. Clients of this API probably expect that the buffer size must be >=
// getByteCount(), but if we don't enforce this restriction then for RGB_4444 and other
// configs that value would be smaller then the buffer size we actually need.
if (getBytesPerPixel(config) != INTERNAL_BYTES_PER_PIXEL) {
throw new RuntimeException(
"Not implemented: only Bitmaps with "
+ INTERNAL_BYTES_PER_PIXEL
+ " bytes per pixel are supported");
}
if (!(dst instanceof ByteBuffer) && !(dst instanceof IntBuffer)) {
throw new RuntimeException("Not implemented: unsupported Buffer subclass");
}
int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
if (dst instanceof ByteBuffer) {
IntBuffer intBuffer = ((ByteBuffer) dst).asIntBuffer();
intBuffer.put(pixels);
dst.position(intBuffer.position() * 4);
} else if (dst instanceof IntBuffer) {
((IntBuffer) dst).put(pixels);
}
}
@Implementation(minSdk = KITKAT)
protected void reconfigure(int width, int height, Bitmap.Config config) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this.config == Bitmap.Config.HARDWARE) {
throw new IllegalStateException("native-backed bitmaps may not be reconfigured");
}
// This should throw if the resulting allocation size is greater than the initial allocation
// size of our Bitmap, but we don't keep track of that information reliably, so we're forced to
// assume that our original dimensions and config are large enough to fit the new dimensions and
// config
this.width = width;
this.height = height;
this.config = config;
bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
@Implementation(minSdk = KITKAT)
protected boolean isPremultiplied() {
return requestPremultiplied && hasAlpha();
}
@Implementation(minSdk = KITKAT)
protected void setPremultiplied(boolean isPremultiplied) {
this.requestPremultiplied = isPremultiplied;
}
@Implementation(minSdk = O)
protected ColorSpace getColorSpace() {
return colorSpace;
}
@Implementation(minSdk = Q)
protected void setColorSpace(ColorSpace colorSpace) {
this.colorSpace = checkNotNull(colorSpace);
}
@Implementation
protected boolean sameAs(Bitmap other) {
if (other == null) {
return false;
}
ShadowLegacyBitmap shadowOtherBitmap = Shadow.extract(other);
if (this.width != shadowOtherBitmap.width || this.height != shadowOtherBitmap.height) {
return false;
}
if (this.config != shadowOtherBitmap.config) {
return false;
}
if (bufferedImage == null && shadowOtherBitmap.bufferedImage != null) {
return false;
} else if (bufferedImage != null && shadowOtherBitmap.bufferedImage == null) {
return false;
} else if (bufferedImage != null && shadowOtherBitmap.bufferedImage != null) {
int[] pixels = ((DataBufferInt) bufferedImage.getData().getDataBuffer()).getData();
int[] otherPixels =
((DataBufferInt) shadowOtherBitmap.bufferedImage.getData().getDataBuffer()).getData();
if (!Arrays.equals(pixels, otherPixels)) {
return false;
}
}
// When Bitmap.createScaledBitmap is called, the colors array is cleared, so we need a basic
// way to detect if two scaled bitmaps are the same.
if (scaledFromBitmap != null && shadowOtherBitmap.scaledFromBitmap != null) {
return scaledFromBitmap.sameAs(shadowOtherBitmap.scaledFromBitmap);
}
return true;
}
void setCreatedFromResId(int resId, String description) {
this.createdFromResId = resId;
appendDescription(" for resource:" + description);
}
private void checkBitmapMutable() {
if (isRecycled()) {
throw new IllegalStateException("Can't call setPixel() on a recycled bitmap");
} else if (!isMutable()) {
throw new IllegalStateException("Bitmap is immutable");
}
}
private void internalCheckPixelAccess(int x, int y) {
if (x < 0) {
throw new IllegalArgumentException("x must be >= 0");
}
if (y < 0) {
throw new IllegalArgumentException("y must be >= 0");
}
if (x >= getWidth()) {
throw new IllegalArgumentException("x must be < bitmap.width()");
}
if (y >= getHeight()) {
throw new IllegalArgumentException("y must be < bitmap.height()");
}
}
void drawRect(Rect r, Paint paint) {
if (bufferedImage == null) {
return;
}
int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
Rect toDraw =
new Rect(
max(0, r.left), max(0, r.top), min(getWidth(), r.right), min(getHeight(), r.bottom));
if (toDraw.left == 0 && toDraw.top == 0 && toDraw.right == getWidth()) {
Arrays.fill(pixels, 0, getWidth() * toDraw.bottom, paint.getColor());
return;
}
for (int y = toDraw.top; y < toDraw.bottom; y++) {
Arrays.fill(
pixels, y * getWidth() + toDraw.left, y * getWidth() + toDraw.right, paint.getColor());
}
}
void drawRect(RectF r, Paint paint) {
if (bufferedImage == null) {
return;
}
Graphics2D graphics2D = bufferedImage.createGraphics();
Rectangle2D r2d = new Rectangle2D.Float(r.left, r.top, r.right - r.left, r.bottom - r.top);
graphics2D.setColor(new Color(paint.getColor()));
graphics2D.draw(r2d);
graphics2D.dispose();
}
void drawBitmap(Bitmap source, int left, int top) {
ShadowLegacyBitmap shadowSource = Shadow.extract(source);
if (bufferedImage == null || shadowSource.bufferedImage == null) {
// pixel data not available, so there's nothing we can do
return;
}
int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
int[] sourcePixels =
((DataBufferInt) shadowSource.bufferedImage.getRaster().getDataBuffer()).getData();
// fast path
if (left == 0 && top == 0 && getWidth() == source.getWidth()) {
int size = min(getWidth() * getHeight(), source.getWidth() * source.getHeight());
System.arraycopy(sourcePixels, 0, pixels, 0, size);
return;
}
// slower (row-by-row) path
int startSourceY = max(0, -top);
int startSourceX = max(0, -left);
int startY = max(0, top);
int startX = max(0, left);
int endY = min(getHeight(), top + source.getHeight());
int endX = min(getWidth(), left + source.getWidth());
int lenY = endY - startY;
int lenX = endX - startX;
for (int y = 0; y < lenY; y++) {
System.arraycopy(
sourcePixels,
(startSourceY + y) * source.getWidth() + startSourceX,
pixels,
(startY + y) * getWidth() + startX,
lenX);
}
}
BufferedImage getBufferedImage() {
return bufferedImage;
}
void setBufferedImage(BufferedImage bufferedImage) {
this.bufferedImage = bufferedImage;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy