diff options
| -rw-r--r-- | graphics/java/android/graphics/Bitmap.java | 51 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Gainmap.java | 11 | ||||
| -rw-r--r-- | libs/hwui/jni/Gainmap.cpp | 11 | ||||
| -rw-r--r-- | tests/SilkFX/res/layout/gainmap_transform_test.xml | 122 | ||||
| -rw-r--r-- | tests/SilkFX/src/com/android/test/silkfx/Main.kt | 4 | ||||
| -rw-r--r-- | tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt | 28 | ||||
| -rw-r--r-- | tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt | 116 |
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 |