diff options
author | 2022-11-04 01:32:24 +0000 | |
---|---|---|
committer | 2023-01-31 00:36:19 +0000 | |
commit | 9776d3e35718efd9ea08400fe0dbea61dc6386ce (patch) | |
tree | 19f4028f6ef2d32f9a8a945e0c1f2c8b4f88930e | |
parent | 397065c0f1c40b907c3fab6396d654c4a5b71341 (diff) |
Tonemap in RecordingCanvas
Intecepts bitmap calls to tonemap whenever the source is HDR (PQ/HLG)
and the destination is SDR.
Also, fix the following bugs discovered as part of testing:
1. Don't implicitly cast to booleans when extracting transfer functions
from a dataspace in hwui's tonemapper.
2. Fix some typos in defining the HLG/PQ transfer functions.
Bug: 261088450
Test: New ColorBitmapActivity in HwAccelerationTest
Change-Id: I9d9d68fc4f57b999b3c6d4156bef281b4409f37e
-rw-r--r-- | graphics/java/android/graphics/ColorSpace.java | 4 | ||||
-rw-r--r-- | libs/hwui/Android.bp | 2 | ||||
-rw-r--r-- | libs/hwui/CanvasTransform.h | 2 | ||||
-rw-r--r-- | libs/hwui/RecordingCanvas.cpp | 15 | ||||
-rw-r--r-- | libs/hwui/Tonemapper.cpp | 17 | ||||
-rw-r--r-- | tests/HwAccelerationTest/AndroidManifest.xml | 9 | ||||
-rw-r--r-- | tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java | 196 |
7 files changed, 234 insertions, 11 deletions
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 2427dec169d6..4c669b8c34f5 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -212,9 +212,9 @@ public abstract class ColorSpace { new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4); private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS = new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f, - 0.28466892f, 0.5599107f, 0.0f, -3.0f, true); + 0.28466892f, 0.5599107f, -11 / 12.0f, -3.0f, true); private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS = - new Rgb.TransferParameters(107 / 128.0f, 1.0f, 32 / 2523.0f, + new Rgb.TransferParameters(-107 / 128.0f, 1.0f, 32 / 2523.0f, 2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true); // See static initialization block next to #get(Named) diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 59e4b7acdba7..2245aae29ecc 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -536,6 +536,7 @@ cc_defaults { "RootRenderNode.cpp", "SkiaCanvas.cpp", "SkiaInterpolator.cpp", + "Tonemapper.cpp", "VectorDrawable.cpp", ], @@ -594,7 +595,6 @@ cc_defaults { "ProfileData.cpp", "ProfileDataContainer.cpp", "Readback.cpp", - "Tonemapper.cpp", "TreeInfo.cpp", "WebViewFunctorManager.cpp", "protos/graphicsstats.proto", diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h index c46a2d369974..291f4cf7193b 100644 --- a/libs/hwui/CanvasTransform.h +++ b/libs/hwui/CanvasTransform.h @@ -45,4 +45,4 @@ bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette pale SkColor transformColor(ColorTransform transform, SkColor color); SkColor transformColorInverse(ColorTransform transform, SkColor color); -} // namespace android::uirenderer;
\ No newline at end of file +} // namespace android::uirenderer diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 3f21940d35a7..bbe79d922b3f 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -43,6 +43,7 @@ #include "SkRegion.h" #include "SkTextBlob.h" #include "SkVertices.h" +#include "Tonemapper.h" #include "VectorDrawable.h" #include "pipeline/skia/AnimatedDrawables.h" #include "pipeline/skia/FunctorDrawable.h" @@ -334,7 +335,9 @@ struct DrawImage final : Op { SkPaint paint; BitmapPalette palette; void draw(SkCanvas* c, const SkMatrix&) const { - c->drawImage(image.get(), x, y, sampling, &paint); + SkPaint newPaint = paint; + tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint); + c->drawImage(image.get(), x, y, sampling, &newPaint); } }; struct DrawImageRect final : Op { @@ -356,7 +359,9 @@ struct DrawImageRect final : Op { SkCanvas::SrcRectConstraint constraint; BitmapPalette palette; void draw(SkCanvas* c, const SkMatrix&) const { - c->drawImageRect(image.get(), src, dst, sampling, &paint, constraint); + SkPaint newPaint = paint; + tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint); + c->drawImageRect(image.get(), src, dst, sampling, &newPaint, constraint); } }; struct DrawImageLattice final : Op { @@ -389,8 +394,10 @@ struct DrawImageLattice final : Op { auto flags = (0 == fs) ? nullptr : pod<SkCanvas::Lattice::RectType>( this, (xs + ys) * sizeof(int) + fs * sizeof(SkColor)); - c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, - filter, &paint); + SkPaint newPaint = paint; + tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint); + c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, filter, + &newPaint); } }; diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp index a7e76b631140..0d39f0e33298 100644 --- a/libs/hwui/Tonemapper.cpp +++ b/libs/hwui/Tonemapper.cpp @@ -18,7 +18,10 @@ #include <SkRuntimeEffect.h> #include <log/log.h> +// libshaders only exists on Android devices +#ifdef __ANDROID__ #include <shaders/shaders.h> +#endif #include "utils/Color.h" @@ -26,6 +29,8 @@ namespace android::uirenderer { namespace { +// custom tonemapping only exists on Android devices +#ifdef __ANDROID__ class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder { public: explicit ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect) @@ -59,20 +64,21 @@ static sk_sp<SkColorFilter> createLinearEffectColorFilter(const shaders::LinearE return effectBuilder.makeColorFilter(); } -static bool extractTransfer(ui::Dataspace dataspace) { - return dataspace & HAL_DATASPACE_TRANSFER_MASK; +static ui::Dataspace extractTransfer(ui::Dataspace dataspace) { + return static_cast<ui::Dataspace>(dataspace & HAL_DATASPACE_TRANSFER_MASK); } static bool isHdrDataspace(ui::Dataspace dataspace) { const auto transfer = extractTransfer(dataspace); - return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG; + return transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG; } static ui::Dataspace getDataspace(const SkImageInfo& image) { return static_cast<ui::Dataspace>( ColorSpaceToADataSpace(image.colorSpace(), image.colorType())); } +#endif } // namespace @@ -80,6 +86,8 @@ static ui::Dataspace getDataspace(const SkImageInfo& image) { // shader and tag it on the supplied paint. void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits, SkPaint& paint) { +// custom tonemapping only exists on Android devices +#ifdef __ANDROID__ const auto sourceDataspace = getDataspace(source); const auto destinationDataspace = getDataspace(destination); @@ -102,6 +110,9 @@ void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, flo paint.setColorFilter(colorFilter); } } +#else + return; +#endif } } // namespace android::uirenderer diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 616f21cbe1a4..5a3d28a4dfad 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -120,6 +120,15 @@ </intent-filter> </activity> + <activity android:name="ColorBitmapActivity" + android:label="Bitmaps/BitmapColors" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + <activity android:name="PathOffsetActivity" android:label="Path/Offset" android:exported="true"> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java new file mode 100644 index 000000000000..017de605fe39 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java @@ -0,0 +1,196 @@ +/* + * 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.hwui; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorSpace; +import android.graphics.HardwareBufferRenderer; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.RenderNode; +import android.graphics.Shader; +import android.hardware.HardwareBuffer; +import android.media.Image; +import android.media.ImageWriter; +import android.os.Bundle; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.Spinner; + +import java.time.Duration; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; + +@SuppressWarnings({"UnusedDeclaration"}) +public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callback, + AdapterView.OnItemSelectedListener { + + private static final int WIDTH = 512; + private static final int HEIGHT = 512; + + private ImageView mImageView; + private SurfaceView mSurfaceView; + private HardwareBuffer mGradientBuffer; + private ImageWriter mImageWriter; + private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + private String[] mColorNames = {"sRGB", "BT2020_HLG", "BT2020_PQ"}; + private String mCurrentColorName = "sRGB"; + + private FutureTask<HardwareBuffer> authorGradientBuffer(HardwareBuffer buffer) { + HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer); + RenderNode node = new RenderNode("content"); + node.setPosition(0, 0, buffer.getWidth(), buffer.getHeight()); + + Canvas canvas = node.beginRecording(); + LinearGradient gradient = new LinearGradient( + 0, 0, buffer.getWidth(), buffer.getHeight(), 0xFF000000, + 0xFFFFFFFF, Shader.TileMode.CLAMP); + Paint paint = new Paint(); + paint.setShader(gradient); + canvas.drawRect(0f, 0f, buffer.getWidth(), buffer.getHeight(), paint); + node.endRecording(); + + renderer.setContentRoot(node); + + ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + FutureTask<HardwareBuffer> resolvedBuffer = new FutureTask<>(() -> buffer); + renderer.obtainRenderRequest() + .setColorSpace(colorSpace) + .draw(Executors.newSingleThreadExecutor(), result -> { + result.getFence().await(Duration.ofSeconds(3)); + resolvedBuffer.run(); + }); + return resolvedBuffer; + } + + private FutureTask<HardwareBuffer> getGradientBuffer() { + HardwareBuffer buffer = HardwareBuffer.create( + WIDTH, HEIGHT, PixelFormat.RGBA_8888, 1, + HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE + | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT); + return authorGradientBuffer(buffer); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + try { + + mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + + ArrayAdapter<String> adapter = new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, mColorNames); + + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + Spinner spinner = new Spinner(this); + spinner.setAdapter(adapter); + spinner.setOnItemSelectedListener(this); + + mGradientBuffer = getGradientBuffer().get(); + + LinearLayout linearLayout = new LinearLayout(this); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + mImageView = new ImageView(this); + + mSurfaceView = new SurfaceView(this); + mSurfaceView.getHolder().addCallback(this); + + linearLayout.addView(spinner, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + linearLayout.addView(mImageView, new LinearLayout.LayoutParams(WIDTH, HEIGHT)); + linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT)); + + setContentView(linearLayout); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private ColorSpace getFromName(String name) { + if (name.equals("sRGB")) { + return ColorSpace.get(ColorSpace.Named.SRGB); + } else if (name.equals("BT2020_HLG")) { + return ColorSpace.get(ColorSpace.Named.BT2020_HLG); + } else if (name.equals("BT2020_PQ")) { + return ColorSpace.get(ColorSpace.Named.BT2020_PQ); + } + + throw new RuntimeException("Unrecognized Colorspace!"); + } + + private void populateBuffers() { + Bitmap bitmap = Bitmap.wrapHardwareBuffer( + mGradientBuffer, ColorSpace.get(ColorSpace.Named.SRGB)); + Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false); + copy.setColorSpace(mColorSpace); + mImageView.setImageBitmap(copy); + + try (Image image = mImageWriter.dequeueInputImage()) { + authorGradientBuffer(image.getHardwareBuffer()).get(); + image.setDataSpace(mColorSpace.getDataSpace()); + mImageWriter.queueInputImage(image); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + mImageWriter = new ImageWriter.Builder(holder.getSurface()) + .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE + | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT + | HardwareBuffer.USAGE_COMPOSER_OVERLAY) + .build(); + populateBuffers(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + mImageWriter.close(); + mImageWriter = null; + } + + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + mCurrentColorName = mColorNames[position]; + mColorSpace = getFromName(mCurrentColorName); + populateBuffers(); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + + } +} |