package android.graphics;

import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static androidx.test.InstrumentationRegistry.getTargetContext;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
import static org.junit.Assert.assertThrows;

import android.content.res.Resources;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.DisplayMetrics;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import androidx.test.platform.graphics.HardwareRendererCompat;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.testapp.R;

/** Compatibility test for {@link Bitmap} */
@RunWith(AndroidJUnit4.class)
public class BitmapTest {

  private Resources resources;

  @Before
  public void setUp() {
    resources = getTargetContext().getResources();
  }

  @Config(minSdk = P)
  @SdkSuppress(minSdkVersion = P)
  @Test
  public void createBitmap() {
    assume().that(System.getProperty("robolectric.graphicsMode")).isNotEqualTo("NATIVE");
    // Bitmap.createBitmap(Picture) requires hardware-backed bitmaps
    HardwareRendererCompat.setDrawingEnabled(true);
    Picture picture = new Picture();
    Canvas canvas = picture.beginRecording(200, 100);

    Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);

    p.setColor(0x88FF0000);
    canvas.drawCircle(50, 50, 40, p);

    p.setColor(Color.GREEN);
    p.setTextSize(30);
    canvas.drawText("Pictures", 60, 60, p);
    picture.endRecording();

    Bitmap bitmap = Bitmap.createBitmap(picture);
    assertThat(bitmap).isNotNull();
  }

  @Test
  public void testEraseColor() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap.eraseColor(0xffff0000);
    assertThat(bitmap.getPixel(10, 10)).isEqualTo(0xffff0000);
    assertThat(bitmap.getPixel(50, 50)).isEqualTo(0xffff0000);
  }

  @Test
  @SdkSuppress(minSdkVersion = M) // getAlpha() returns 0 on less than M
  public void testExtractAlpha() {
    // normal case
    Bitmap bitmap =
        BitmapFactory.decodeResource(resources, R.drawable.an_image, new BitmapFactory.Options());
    Bitmap ret = bitmap.extractAlpha();
    int source = bitmap.getPixel(10, 20);
    int result = ret.getPixel(10, 20);
    assertThat(Color.alpha(result)).isEqualTo(Color.alpha(source));
  }

  @Test
  public void testCopy() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false);
    assertThat(copy.getWidth()).isEqualTo(bitmap.getWidth());
    assertThat(copy.getHeight()).isEqualTo(bitmap.getHeight());
    assertThat(copy.getConfig()).isEqualTo(bitmap.getConfig());
  }

  @Test
  public void testCopyAndEraseColor() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap.eraseColor(0xffffff00);
    assertThat(bitmap.getPixel(10, 10)).isEqualTo(0xffffff00);
    assertThat(bitmap.getPixel(50, 50)).isEqualTo(0xffffff00);

    Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, true);
    assertThat(copy.getPixel(10, 10)).isEqualTo(0xffffff00);
    assertThat(copy.getPixel(50, 50)).isEqualTo(0xffffff00);

    copy.eraseColor(0xffff0000);
    assertThat(copy.getPixel(10, 10)).isEqualTo(0xffff0000);
    assertThat(copy.getPixel(50, 50)).isEqualTo(0xffff0000);
  }

  @Test
  public void compress() {
    Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.an_image);

    ByteArrayOutputStream stm = new ByteArrayOutputStream();
    assertThat(bitmap.compress(CompressFormat.JPEG, 0, stm)).isTrue();
    assertThat(stm.toByteArray()).isNotEmpty();
  }

  @Test
  public void getConfigAfterCompress() throws IOException {
    InputStream inputStream = resources.getAssets().open("robolectric.png");
    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
    Matrix matrix = new Matrix();
    matrix.setScale(0.5f, 0.5f);
    Bitmap scaledBitmap =
        Bitmap.createBitmap(
            bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, /* filter */ true);
    assertThat(scaledBitmap.getConfig()).isEqualTo(Bitmap.Config.ARGB_8888);
  }

  @Test
  public void getConfigAfterCreateScaledBitmap() throws IOException {
    InputStream inputStream = resources.getAssets().open("robolectric.png");
    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
    Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, 50, 50, /* filter= */ false);
    assertThat(scaledBitmap.getConfig()).isEqualTo(Bitmap.Config.ARGB_8888);
  }

  @Test
  public void scaledBitmap_sameAs() {
    Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap1.eraseColor(0xffff0000);
    Bitmap bitmap2 = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap2.eraseColor(0xff00ff00);
    assertThat(bitmap1.sameAs(bitmap2)).isFalse();

    Bitmap scaled1 = Bitmap.createScaledBitmap(bitmap1, 200, 200, false);
    Bitmap scaled2 = Bitmap.createScaledBitmap(bitmap2, 200, 200, false);
    assertThat(scaled1.sameAs(scaled2)).isFalse();
  }

  @Test
  public void checkBitmapNotRecycled() throws IOException {
    InputStream inputStream = resources.getAssets().open("robolectric.png");
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inScaled = true;
    options.inDensity = 100;
    options.inTargetDensity = 500;
    Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
    assertThat(bitmap.isRecycled()).isFalse();
  }

  @Test
  public void decodeResource_withMutableOpt_isMutable() {
    BitmapFactory.Options opt = new BitmapFactory.Options();
    opt.inMutable = true;
    Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.an_image, opt);
    assertThat(bitmap.isMutable()).isTrue();
  }

  @Test
  public void scaledBitmap_isMutable() throws IOException {
    InputStream inputStream = resources.getAssets().open("robolectric.png");
    BitmapFactory.Options opt = new BitmapFactory.Options();
    opt.inMutable = true;
    Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, opt);
    Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, 50, 50, false);
    assertThat(scaledBitmap.isMutable()).isTrue();
  }

  @Test
  public void colorDrawable_drawToBitmap() {
    Drawable colorDrawable = new ColorDrawable(Color.RED);
    Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    assertThat(canvas.getWidth()).isEqualTo(1);
    assertThat(canvas.getHeight()).isEqualTo(1);
    colorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
    colorDrawable.draw(canvas);
    assertThat(bitmap.getPixel(0, 0)).isEqualTo(Color.RED);
  }

  @Test
  public void drawCanvas_bitmap_sameSize() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap.eraseColor(0xff00ff00);
    Bitmap output = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(output);
    canvas.drawBitmap(bitmap, 0, 0, null);
    assertThat(bitmap.sameAs(output)).isTrue();
  }

  @Test
  public void drawCanvas_bitmap_centered() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap.eraseColor(0xff00ff00);
    Bitmap output = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(output);
    canvas.drawBitmap(bitmap, 50, 50, null);
    assertThat(output.getPixel(49, 49)).isEqualTo(0);
    assertThat(output.getPixel(50, 50)).isEqualTo(0xff00ff00);
    assertThat(output.getPixel(149, 149)).isEqualTo(0xff00ff00);
    assertThat(output.getPixel(150, 150)).isEqualTo(0);
  }

  @Test
  public void drawCanvas_overflow_topLeft() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap.eraseColor(0xff00ff00);
    Bitmap output = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(output);
    canvas.drawBitmap(bitmap, -50, -50, null);
    assertThat(output.getPixel(0, 0)).isEqualTo(0xff00ff00);
    assertThat(output.getPixel(49, 49)).isEqualTo(0xff00ff00);
    assertThat(output.getPixel(50, 50)).isEqualTo(0);
  }

  @Test
  public void drawCanvas_overflow_topRight() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap.eraseColor(0xff00ff00);
    Bitmap output = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(output);
    canvas.drawBitmap(bitmap, 50, -50, null);
    assertThat(output.getPixel(99, 0)).isEqualTo(0xff00ff00);
    assertThat(output.getPixel(50, 49)).isEqualTo(0xff00ff00);
    assertThat(output.getPixel(49, 50)).isEqualTo(0);
  }

  @Test
  public void drawCanvas_overflow_bottomLeft() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap.eraseColor(0xff00ff00);
    Bitmap output = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(output);
    canvas.drawBitmap(bitmap, -50, 50, null);
    assertThat(output.getPixel(0, 99)).isEqualTo(0xff00ff00);
    assertThat(output.getPixel(49, 50)).isEqualTo(0xff00ff00);
    assertThat(output.getPixel(50, 49)).isEqualTo(0);
  }

  @Test
  public void drawCanvas_overflow_bottomRight() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap.eraseColor(0xff00ff00);
    Bitmap output = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(output);
    canvas.drawBitmap(bitmap, 50, 50, null);
    assertThat(output.getPixel(99, 99)).isEqualTo(0xff00ff00);
    assertThat(output.getPixel(50, 50)).isEqualTo(0xff00ff00);
    assertThat(output.getPixel(49, 49)).isEqualTo(0);
  }

  @Test
  public void createScaledBitmap_zeroWidthAndHeight_error() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    Exception exception =
        assertThrows(
            IllegalArgumentException.class, () -> Bitmap.createScaledBitmap(bitmap, 0, 0, false));

    assertThat(exception).hasMessageThat().contains("width and height must be > 0");
  }

  @Test
  public void getBitmapPixels_strideTooLong() {
    int[] bitmapPixels = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    Bitmap bitmap = Bitmap.createBitmap(bitmapPixels, 3, 3, Bitmap.Config.ARGB_8888);
    int[] pixelsCopy = new int[bitmap.getHeight() * bitmap.getWidth()];
    assertThrows(
        ArrayIndexOutOfBoundsException.class,
        () ->
            bitmap.getPixels(
                pixelsCopy, 0, bitmap.getRowBytes(), 0, 0, bitmap.getWidth(), bitmap.getHeight()));
  }

  @Test
  public void eraseColor_toTransparent() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap.eraseColor(0);
    assertThat(bitmap.getPixel(0, 0)).isEqualTo(0);
  }

  @Test
  public void reconfigure_drawPixel() {
    Bitmap bitmap = Bitmap.createBitmap(100, 50, Bitmap.Config.ARGB_8888);
    bitmap.reconfigure(50, 100, Bitmap.Config.ARGB_8888);
    bitmap.setPixel(0, 99, Color.RED);
    assertThat(bitmap.getPixel(0, 99)).isEqualTo(Color.RED);
  }

  /**
   * Questionable ARGB_8888 pixel values like '10' may be simplified by some graphics engines to
   * '0'. This happens because '10' has alpha transparency '0', so the values for RGB don't matter.
   * This happens when Java's Graphics2d is used for certain.
   */
  @Test
  public void recompress_png100_samePixelss() {
    Bitmap applicationIconBitmap =
        Bitmap.createBitmap(new int[] {10, 11, 12, 13}, 2, 2, Bitmap.Config.ARGB_8888);

    BitmapDrawable applicationIcon = new BitmapDrawable(resources, applicationIconBitmap);

    ByteArrayOutputStream outputStream1 = new ByteArrayOutputStream();
    applicationIconBitmap.compress(CompressFormat.PNG, 100, outputStream1);

    Bitmap bitmap =
        Bitmap.createBitmap(
            applicationIcon.getIntrinsicWidth(),
            applicationIcon.getIntrinsicHeight(),
            Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    applicationIcon.draw(canvas);
    ByteArrayOutputStream outputStream2 = new ByteArrayOutputStream();
    bitmap.compress(CompressFormat.PNG, 100, outputStream2);
    assertThat(outputStream2.toByteArray()).isEqualTo(outputStream1.toByteArray());
  }

  @Test
  public void compress_thenDecodeStream_sameAs() {
    Bitmap bitmap = Bitmap.createBitmap(/* width= */ 10, /* height= */ 10, Bitmap.Config.ARGB_8888);
    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
    bitmap.compress(CompressFormat.PNG, /* quality= */ 100, outStream);
    byte[] outBytes = outStream.toByteArray();
    ByteArrayInputStream inStream = new ByteArrayInputStream(outBytes);
    Bitmap bitmap2 = BitmapFactory.decodeStream(inStream);
    assertThat(bitmap.sameAs(bitmap2)).isTrue();
  }

  @Test
  public void compress_asJpeg_convertsTransparentToBlack() {
    Bitmap bitmap = Bitmap.createBitmap(/* width= */ 10, /* height= */ 10, Bitmap.Config.ARGB_8888);
    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
    bitmap.compress(CompressFormat.JPEG, /* quality= */ 90, outStream);
    byte[] outBytes = outStream.toByteArray();
    assertThat(outBytes).isNotEmpty();
    ByteArrayInputStream inStream = new ByteArrayInputStream(outBytes);
    Bitmap bitmap2 = BitmapFactory.decodeStream(inStream);
    assertThat(bitmap2.getPixel(0, 0)).isEqualTo(Color.BLACK);
  }

  @Test
  public void createBitmapWithOffsetAndStride() {
    int[] pixels = new int[10];
    Bitmap result = Bitmap.createBitmap(pixels, 0, 2, 2, 5, Bitmap.Config.ARGB_8888);
    assertThat(result).isNotNull();
  }

  @Test
  public void createBitmap_mutability() {
    // Mutable constructor variants.
    assertThat(
            Bitmap.createBitmap(/* width= */ 1, /* height= */ 1, Bitmap.Config.ARGB_8888)
                .isMutable())
        .isTrue();
    assertThat(
            Bitmap.createBitmap(
                    (DisplayMetrics) null, /* width= */ 1, /* height= */ 1, Bitmap.Config.ARGB_8888)
                .isMutable())
        .isTrue();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      assertThat(
              Bitmap.createBitmap(
                      /* width= */ 1,
                      /* height= */ 1,
                      Bitmap.Config.ARGB_8888,
                      /* hasAlpha= */ true)
                  .isMutable())
          .isTrue();
      assertThat(
              Bitmap.createBitmap(
                      /* display= */ null,
                      /* width= */ 1,
                      /* height= */ 1,
                      Bitmap.Config.ARGB_8888,
                      /* hasAlpha= */ true)
                  .isMutable())
          .isTrue();
    }

    // Immutable constructor variants.
    assertThat(
            Bitmap.createBitmap(
                    /* colors= */ new int[] {0},
                    /* width= */ 1,
                    /* height= */ 1,
                    Bitmap.Config.ARGB_8888)
                .isMutable())
        .isFalse();
    assertThat(
            Bitmap.createBitmap(
                    /* colors= */ new int[] {0},
                    /* offset= */ 0,
                    /* stride= */ 1,
                    /* width= */ 1,
                    /* height= */ 1,
                    Bitmap.Config.ARGB_8888)
                .isMutable())
        .isFalse();
    assertThat(
            Bitmap.createBitmap(
                    /* display= */ null,
                    /* colors= */ new int[] {0},
                    /* width= */ 1,
                    /* height= */ 1,
                    Bitmap.Config.ARGB_8888)
                .isMutable())
        .isFalse();
    assertThat(
            Bitmap.createBitmap(
                    /* display= */ null,
                    /* colors= */ new int[] {0},
                    /* offset= */ 0,
                    /* stride= */ 1,
                    /* width= */ 1,
                    /* height= */ 1,
                    Bitmap.Config.ARGB_8888)
                .isMutable())
        .isFalse();
  }

  @Test
  public void createBitmap_hasAlpha() {
    // ARGB_8888 has alpha by default.
    assertThat(
            Bitmap.createBitmap(/* width= */ 1, /* height= */ 1, Bitmap.Config.ARGB_8888)
                .hasAlpha())
        .isTrue();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      assertThat(
              Bitmap.createBitmap(
                      /* width= */ 1,
                      /* height= */ 1,
                      Bitmap.Config.ARGB_8888,
                      /* hasAlpha= */ true)
                  .hasAlpha())
          .isTrue();
      assertThat(
              Bitmap.createBitmap(
                      /* display= */ null,
                      /* width= */ 1,
                      /* height= */ 1,
                      Bitmap.Config.ARGB_8888,
                      /* hasAlpha= */ true)
                  .hasAlpha())
          .isTrue();
    }
    assertThat(
            Bitmap.createBitmap(
                    /* colors= */ new int[] {0},
                    /* width= */ 1,
                    /* height= */ 1,
                    Bitmap.Config.ARGB_8888)
                .hasAlpha())
        .isTrue();

    // Doesn't have alpha
    assertThat(
            Bitmap.createBitmap(/* width= */ 1, /* height= */ 1, Bitmap.Config.RGB_565).hasAlpha())
        .isFalse();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      assertThat(
              Bitmap.createBitmap(
                      /* width= */ 1,
                      /* height= */ 1,
                      Bitmap.Config.ARGB_8888,
                      /* hasAlpha= */ false)
                  .hasAlpha())
          .isFalse();
      assertThat(
              Bitmap.createBitmap(
                      /* display= */ null,
                      /* width= */ 1,
                      /* height= */ 1,
                      Bitmap.Config.ARGB_8888,
                      /* hasAlpha= */ false)
                  .hasAlpha())
          .isFalse();
    }
  }

  @Test
  public void createBitmap_premultiplied() {
    // ARGB_8888 has alpha by default, is premultiplied.
    assertThat(
            Bitmap.createBitmap(/* width= */ 1, /* height= */ 1, Bitmap.Config.ARGB_8888)
                .isPremultiplied())
        .isTrue();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      assertThat(
              Bitmap.createBitmap(
                      /* width= */ 1,
                      /* height= */ 1,
                      Bitmap.Config.ARGB_8888,
                      /* hasAlpha= */ true)
                  .isPremultiplied())
          .isTrue();
      assertThat(
              Bitmap.createBitmap(
                      /* display= */ null,
                      /* width= */ 1,
                      /* height= */ 1,
                      Bitmap.Config.ARGB_8888,
                      /* hasAlpha= */ true)
                  .isPremultiplied())
          .isTrue();
    }
    assertThat(
            Bitmap.createBitmap(
                    /* colors= */ new int[] {0},
                    /* width= */ 1,
                    /* height= */ 1,
                    Bitmap.Config.ARGB_8888)
                .isPremultiplied())
        .isTrue();

    // Doesn't have alpha, is not premultiplied
    assertThat(
            Bitmap.createBitmap(/* width= */ 1, /* height= */ 1, Bitmap.Config.RGB_565)
                .isPremultiplied())
        .isFalse();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      assertThat(
              Bitmap.createBitmap(
                      /* width= */ 1,
                      /* height= */ 1,
                      Bitmap.Config.ARGB_8888,
                      /* hasAlpha= */ false)
                  .isPremultiplied())
          .isFalse();
      assertThat(
              Bitmap.createBitmap(
                      /* display= */ null,
                      /* width= */ 1,
                      /* height= */ 1,
                      Bitmap.Config.ARGB_8888,
                      /* hasAlpha= */ false)
                  .isPremultiplied())
          .isFalse();
    }
  }

  @Test
  public void extractAlpha_isMutable() {
    Bitmap result = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    Bitmap alphaBitmap = result.extractAlpha();
    assertThat(alphaBitmap.isMutable()).isTrue();
  }

  @Test
  public void createBitmap_withBitmap_containsImageData() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap.eraseColor(Color.BLUE);
    Bitmap cropped = Bitmap.createBitmap(bitmap, 0, 0, 50, 50);
    assertThat(cropped.isMutable()).isTrue();
    assertThat(cropped.getPixel(0, 0)).isEqualTo(Color.BLUE);
  }

  @Test
  public void createBitmap_withBitmap_thenCopy_isValid() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap.eraseColor(Color.BLUE);
    Bitmap cropped = Bitmap.createBitmap(bitmap, 50, 50, 50, 50);
    Bitmap copy = cropped.copy(Bitmap.Config.ARGB_8888, true);
    assertThat(copy.isMutable()).isTrue();
    assertThat(copy.getPixel(0, 0)).isEqualTo(Color.BLUE);
  }

  @Test
  public void copyPixelsFromBuffer_intBuffer() {
    Bitmap bitmap = Bitmap.createBitmap(4, 4, Bitmap.Config.ARGB_8888);
    IntBuffer input = IntBuffer.allocate(bitmap.getWidth() * bitmap.getHeight());
    for (int i = 0; i < input.capacity(); i++) {
      // IntBuffer is interpreted as ABGR. Use A=255 to avoid premultiplication.
      input.put((0xFF << 24) | (i + 2) << 16 | (i + 1) << 8 | i);
    }
    input.rewind();
    bitmap.copyPixelsFromBuffer(input);

    IntBuffer output = IntBuffer.allocate(input.capacity());
    bitmap.copyPixelsToBuffer(output);

    input.rewind();
    output.rewind();

    assertThat(output).isEqualTo(input);
  }

  @Test
  public void copyPixelsFromBuffer_byteBuffer() {
    Bitmap bitmap = Bitmap.createBitmap(4, 4, Bitmap.Config.ARGB_8888);
    ByteBuffer input = ByteBuffer.allocate(bitmap.getWidth() * bitmap.getHeight() * 4);
    for (int i = 0; i < bitmap.getWidth() * bitmap.getHeight(); i++) {
      // ByteBuffer is interpreted as RGBA. Use A=255 to avoid premultiplication.
      input.put((byte) i);
      input.put((byte) (i + 1));
      input.put((byte) (i + 2));
      input.put((byte) 0xFF);
    }
    input.rewind();
    bitmap.copyPixelsFromBuffer(input);

    ByteBuffer output = ByteBuffer.allocate(input.capacity());
    bitmap.copyPixelsToBuffer(output);

    input.rewind();
    output.rewind();

    assertThat(output).isEqualTo(input);
  }

  @SdkSuppress(minSdkVersion = O)
  @Config(minSdk = O)
  @Test
  public void createBitmap_colorSpace_defaultColorSpace() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);

    assertThat(bitmap.getColorSpace()).isEqualTo(ColorSpace.get(ColorSpace.Named.SRGB));
  }

  @SdkSuppress(minSdkVersion = O)
  @Config(minSdk = O)
  @Test
  public void createBitmap_colorSpace_customColorSpace() {
    Bitmap bitmap =
        Bitmap.createBitmap(
            100, 100, Bitmap.Config.ARGB_8888, true, ColorSpace.get(ColorSpace.Named.ADOBE_RGB));

    assertThat(bitmap.getColorSpace()).isEqualTo(ColorSpace.get(ColorSpace.Named.ADOBE_RGB));
  }

  @SdkSuppress(minSdkVersion = Q)
  @Config(minSdk = Q)
  @Test
  public void setColorSpace() {
    Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
    bitmap.setColorSpace(ColorSpace.get(ColorSpace.Named.ADOBE_RGB));

    assertThat(bitmap.getColorSpace()).isEqualTo(ColorSpace.get(ColorSpace.Named.ADOBE_RGB));
  }

  @Test
  public void bitmapDrawable_mutate() {
    BitmapDrawable drawable1 = (BitmapDrawable) resources.getDrawable(R.drawable.an_image);
    BitmapDrawable drawable2 = (BitmapDrawable) resources.getDrawable(R.drawable.an_image);

    Drawable mutated1 = drawable1.mutate();
    Drawable mutated2 = drawable2.mutate();
    mutated1.setAlpha(100);
    mutated1.setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN);
    mutated2.setAlpha(200);
    mutated2.setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
    assertThat(mutated1.getAlpha()).isEqualTo(100);
    // ColorFilter is part of the Drawable paint, so BLUE is overridden by RED.
    assertThat(mutated1.getColorFilter())
        .isEqualTo(new PorterDuffColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN));
    assertThat(mutated2.getAlpha()).isEqualTo(200);
    assertThat(mutated2.getColorFilter())
        .isEqualTo(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
  }

  @Test
  public void null_bitmapConfig_throwsNPE() {
    assertThrows(NullPointerException.class, () -> Bitmap.createBitmap(100, 100, null));
  }
}
