summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--graphics/java/android/graphics/Bitmap.java51
-rw-r--r--graphics/java/android/graphics/Gainmap.java11
-rw-r--r--libs/hwui/jni/Gainmap.cpp11
-rw-r--r--tests/SilkFX/res/layout/gainmap_transform_test.xml122
-rw-r--r--tests/SilkFX/src/com/android/test/silkfx/Main.kt4
-rw-r--r--tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt28
-rw-r--r--tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt116
7 files changed, 340 insertions, 3 deletions
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 2307d6080f9f..b9d3756ac6d2 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -997,12 +997,63 @@ public final class Bitmap implements Parcelable {
canvas.concat(m);
canvas.drawBitmap(source, srcR, dstR, paint);
canvas.setBitmap(null);
+
+ // If the source has a gainmap, apply the same set of transformations to the gainmap
+ // and set it on the output
+ if (source.hasGainmap()) {
+ Bitmap newMapContents = transformGainmap(source, m, neww, newh, paint, srcR, dstR,
+ deviceR);
+ if (newMapContents != null) {
+ bitmap.setGainmap(new Gainmap(source.getGainmap(), newMapContents));
+ }
+ }
+
if (isHardware) {
return bitmap.copy(Config.HARDWARE, false);
}
return bitmap;
}
+ private static Bitmap transformGainmap(Bitmap source, Matrix m, int neww, int newh, Paint paint,
+ Rect srcR, RectF dstR, RectF deviceR) {
+ Canvas canvas;
+ Bitmap sourceGainmap = source.getGainmap().getGainmapContents();
+ // Gainmaps can be scaled relative to the base image (eg, 1/4th res)
+ // Preserve that relative scaling between the base & gainmap in the output
+ float scaleX = (sourceGainmap.getWidth() / (float) source.getWidth());
+ float scaleY = (sourceGainmap.getHeight() / (float) source.getHeight());
+ int mapw = Math.round(neww * scaleX);
+ int maph = Math.round(newh * scaleY);
+
+ if (mapw == 0 || maph == 0) {
+ // The gainmap has been scaled away entirely, drop it
+ return null;
+ }
+
+ // Scale the computed `srcR` used for rendering the source bitmap to the destination
+ // to be in gainmap dimensions
+ Rect gSrcR = new Rect((int) (srcR.left * scaleX),
+ (int) (srcR.top * scaleY), (int) (srcR.right * scaleX),
+ (int) (srcR.bottom * scaleY));
+
+ // Note: createBitmap isn't used as that requires a non-null colorspace, however
+ // gainmaps don't have a colorspace. So use `nativeCreate` directly to bypass
+ // that colorspace enforcement requirement (#getColorSpace() allows a null return)
+ Bitmap newMapContents = nativeCreate(null, 0, mapw, mapw, maph,
+ sourceGainmap.getConfig().nativeInt, true, 0);
+ newMapContents.eraseColor(0);
+ canvas = new Canvas(newMapContents);
+ // Scale the translate & matrix to be in gainmap-relative dimensions
+ canvas.scale(scaleX, scaleY);
+ canvas.translate(-deviceR.left, -deviceR.top);
+ canvas.concat(m);
+ canvas.drawBitmap(sourceGainmap, gSrcR, dstR, paint);
+ canvas.setBitmap(null);
+ // Create a new gainmap using a copy of the metadata information from the source but
+ // with the transformed bitmap created above
+ return newMapContents;
+ }
+
/**
* Returns a mutable bitmap with the specified width and height. Its
* initial density is as per {@link #getDensity}. The newly created
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index 9ac84a6159da..f639521ff250 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -122,6 +122,16 @@ public final class Gainmap implements Parcelable {
}
/**
+ * Creates a new gainmap using the provided gainmap as the metadata source and the provided
+ * bitmap as the replacement for the gainmapContents
+ * TODO: Make public, it's useful
+ * @hide
+ */
+ public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) {
+ this(gainmapContents, nCreateCopy(gainmap.mNativePtr));
+ }
+
+ /**
* @return Returns the image data of the gainmap represented as a Bitmap. This is represented
* as a Bitmap for broad API compatibility, however certain aspects of the Bitmap are ignored
* such as {@link Bitmap#getColorSpace()} or {@link Bitmap#getGainmap()} as they are not
@@ -325,6 +335,7 @@ public final class Gainmap implements Parcelable {
private static native long nGetFinalizer();
private static native long nCreateEmpty();
+ private static native long nCreateCopy(long source);
private static native void nSetBitmap(long ptr, Bitmap bitmap);
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index 0f8a85dd9e62..cec0ee7ee247 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -86,6 +86,16 @@ jlong Gainmap_createEmpty(JNIEnv*, jobject) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap));
}
+jlong Gainmap_createCopy(JNIEnv*, jobject, jlong sourcePtr) {
+ Gainmap* gainmap = new Gainmap();
+ gainmap->incStrong(0);
+ if (sourcePtr) {
+ Gainmap* src = fromJava(sourcePtr);
+ gainmap->info = src->info;
+ }
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap));
+}
+
static void Gainmap_setBitmap(JNIEnv* env, jobject, jlong gainmapPtr, jobject jBitmap) {
android::Bitmap* bitmap = GraphicsJNI::getNativeBitmap(env, jBitmap);
fromJava(gainmapPtr)->bitmap = sk_ref_sp(bitmap);
@@ -237,6 +247,7 @@ static void Gainmap_readFromParcel(JNIEnv* env, jobject, jlong nativeObject, job
static const JNINativeMethod gGainmapMethods[] = {
{"nGetFinalizer", "()J", (void*)Gainmap_getNativeFinalizer},
{"nCreateEmpty", "()J", (void*)Gainmap_createEmpty},
+ {"nCreateCopy", "(J)J", (void*)Gainmap_createCopy},
{"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*)Gainmap_setBitmap},
{"nSetRatioMin", "(JFFF)V", (void*)Gainmap_setRatioMin},
{"nGetRatioMin", "(J[F)V", (void*)Gainmap_getRatioMin},
diff --git a/tests/SilkFX/res/layout/gainmap_transform_test.xml b/tests/SilkFX/res/layout/gainmap_transform_test.xml
new file mode 100644
index 000000000000..5aeb53661cbc
--- /dev/null
+++ b/tests/SilkFX/res/layout/gainmap_transform_test.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.test.silkfx.hdr.GainmapTransformsTest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/original"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Original" />
+
+ <Button
+ android:id="@+id/scaled"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Scaled (1/3)" />
+
+ <Button
+ android:id="@+id/rotate_90"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Rotate 90" />
+
+ <Button
+ android:id="@+id/rotate_90_scaled"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Rot90+Scale" />
+
+ <Button
+ android:id="@+id/crop"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Crop" />
+
+ <Button
+ android:id="@+id/crop_200"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Crop 200" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/source_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/sdr_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageView
+ android:id="@+id/sdr_source"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="8dp"
+ android:scaleType="fitStart" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/gainmap_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageView
+ android:id="@+id/gainmap"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="8dp"
+ android:scaleType="fitStart" />
+ </LinearLayout>
+
+ </LinearLayout>
+
+</com.android.test.silkfx.hdr.GainmapTransformsTest> \ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/SilkFX/src/com/android/test/silkfx/Main.kt
index a6cdbb9865bc..59a6078376cf 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/Main.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/Main.kt
@@ -55,7 +55,9 @@ private val AllDemos = listOf(
Demo("Color Grid", R.layout.color_grid),
Demo("Gradient Sweep", R.layout.gradient_sweep),
Demo("Gainmap Image", R.layout.gainmap_image),
- Demo("Gainmap Decode Test", R.layout.gainmap_decode_test, commonControls = false)
+ Demo("Gainmap Decode Test", R.layout.gainmap_decode_test, commonControls = false),
+ Demo("Gainmap Transform Test", R.layout.gainmap_transform_test,
+ commonControls = false)
)),
DemoGroup("Materials", listOf(
Demo("Glass", GlassActivity::class),
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt
index a004fb5a4305..585320aee615 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt
@@ -17,7 +17,12 @@
package com.android.test.silkfx.hdr
import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.ColorMatrixColorFilter
+import android.graphics.Gainmap
import android.graphics.ImageDecoder
+import android.graphics.Paint
import android.graphics.Rect
import android.util.AttributeSet
import android.widget.Button
@@ -34,6 +39,25 @@ enum class DecodeMode {
CropedSquaredScaled33
}
+fun gainmapVisualizer(gainmap: Gainmap): Bitmap {
+ val map = gainmap.gainmapContents
+ val gainmapVisualizer = Bitmap.createBitmap(map.width, map.height,
+ Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(gainmapVisualizer!!)
+ val paint = Paint()
+ paint.colorFilter = ColorMatrixColorFilter(
+ floatArrayOf(
+ 0f, 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 0f, 255f
+ )
+ )
+ canvas.drawBitmap(map, 0f, 0f, paint)
+ canvas.setBitmap(null)
+ return gainmapVisualizer
+}
+
class GainmapDecodeTest(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
private fun decode(mode: DecodeMode) {
@@ -71,7 +95,7 @@ class GainmapDecodeTest(context: Context, attrs: AttributeSet?) : LinearLayout(c
}
}
- val gainmapContents = gainmapImage.gainmap!!.gainmapContents!!
+ val gainmapContents = gainmapImage.gainmap?.let { gainmapVisualizer(it) }
val sdrBitmap = gainmapImage.also { it.gainmap = null }
findViewById<ImageView>(R.id.sdr_source)!!.setImageBitmap(sdrBitmap)
@@ -80,7 +104,7 @@ class GainmapDecodeTest(context: Context, attrs: AttributeSet?) : LinearLayout(c
findViewById<ImageView>(R.id.gainmap)!!.setImageBitmap(gainmapContents)
findViewById<TextView>(R.id.gainmap_label)!!.text =
- "Gainmap Size: ${gainmapContents.width}x${gainmapContents.height}"
+ "Gainmap Size: ${gainmapContents?.width ?: 0}x${gainmapContents?.height ?: 0}"
}
override fun onFinishInflate() {
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt
new file mode 100644
index 000000000000..20984fae2133
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.silkfx.hdr
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.Matrix
+import android.util.AttributeSet
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.android.test.silkfx.R
+
+class GainmapTransformsTest(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
+
+ private val sourceImage = loadSample()
+
+ private fun loadSample(): Bitmap {
+ val source = ImageDecoder.createSource(resources.assets,
+ "gainmaps/${context.assets.list("gainmaps")!![0]}")
+
+ return ImageDecoder.decodeBitmap(source) { decoder, info, source ->
+ decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ }
+ }
+
+ private fun process(transform: (Bitmap) -> Bitmap) {
+ val result = transform(sourceImage)
+
+ val gainmapContents = result.gainmap?.let { gainmapVisualizer(it) }
+ val sdrBitmap = result.also { it.gainmap = null }
+
+ findViewById<ImageView>(R.id.sdr_source)!!.setImageBitmap(sdrBitmap)
+ findViewById<TextView>(R.id.sdr_label)!!.text =
+ "SDR Size: ${sdrBitmap.width}x${sdrBitmap.height}"
+
+ findViewById<ImageView>(R.id.gainmap)!!.setImageBitmap(gainmapContents)
+ findViewById<TextView>(R.id.gainmap_label)!!.text =
+ "Gainmap Size: ${gainmapContents?.width ?: 0}x${gainmapContents?.height ?: 0}"
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ val sourceInfo = findViewById<TextView>(R.id.source_info)!!
+ sourceInfo.text = "Original size ${sourceImage.width}x${sourceImage.height}"
+ process { it.copy(Bitmap.Config.ARGB_8888, false) }
+
+ findViewById<Button>(R.id.original)!!.setOnClickListener {
+ process { it.copy(Bitmap.Config.ARGB_8888, false) }
+ }
+
+ findViewById<Button>(R.id.scaled)!!.setOnClickListener {
+ process { Bitmap.createScaledBitmap(it, it.width / 3, it.height / 3, true) }
+ }
+
+ findViewById<Button>(R.id.rotate_90)!!.setOnClickListener {
+ process {
+ val width: Int = it.width
+ val height: Int = it.height
+
+ val m = Matrix()
+ m.setRotate(90.0f, (width / 2).toFloat(), (height / 2).toFloat())
+ Bitmap.createBitmap(it, 0, 0, width, height, m, false)
+ }
+ }
+
+ findViewById<Button>(R.id.rotate_90_scaled)!!.setOnClickListener {
+ process {
+ val width: Int = it.width
+ val height: Int = it.height
+
+ val m = Matrix()
+ m.setRotate(90.0f, (width / 2).toFloat(), (height / 2).toFloat())
+ m.preScale(.3f, .3f)
+ Bitmap.createBitmap(it, 0, 0, width, height, m, false)
+ }
+ }
+
+ findViewById<Button>(R.id.crop)!!.setOnClickListener {
+ process {
+ val width: Int = it.width
+ val height: Int = it.height
+ Bitmap.createBitmap(it, width / 2, height / 2,
+ width / 4, height / 4, null, false)
+ }
+ }
+
+ findViewById<Button>(R.id.crop_200)!!.setOnClickListener {
+ process {
+ val width: Int = it.width
+ val height: Int = it.height
+
+ val m = Matrix()
+ m.setRotate(200.0f, (width / 2).toFloat(), (height / 2).toFloat())
+ Bitmap.createBitmap(it, width / 2, height / 2,
+ width / 4, height / 4, m, false)
+ }
+ }
+ }
+} \ No newline at end of file