367 lines
14 KiB
Java
367 lines
14 KiB
Java
![]() |
/*
|
||
|
* 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 android.graphics;
|
||
|
|
||
|
import android.annotation.FlaggedApi;
|
||
|
import android.annotation.FloatRange;
|
||
|
import android.annotation.NonNull;
|
||
|
import android.os.Parcel;
|
||
|
import android.os.Parcelable;
|
||
|
|
||
|
import com.android.graphics.hwui.flags.Flags;
|
||
|
|
||
|
import libcore.util.NativeAllocationRegistry;
|
||
|
|
||
|
/**
|
||
|
* Gainmap represents a mechanism for augmenting an SDR image to produce an HDR one with variable
|
||
|
* display adjustment capability. It is a combination of a set of metadata describing how to apply
|
||
|
* the gainmap, as well as either a 1 (such as {@link android.graphics.Bitmap.Config#ALPHA_8} or 3
|
||
|
* (such as {@link android.graphics.Bitmap.Config#ARGB_8888} with the alpha channel ignored)
|
||
|
* channel Bitmap that represents the gainmap data itself.
|
||
|
* <p>
|
||
|
* When rendering to an {@link android.content.pm.ActivityInfo#COLOR_MODE_HDR} activity, the
|
||
|
* hardware accelerated {@link Canvas} will automatically apply the gainmap when sufficient
|
||
|
* HDR headroom is available.
|
||
|
*
|
||
|
* <h3>Gainmap Structure</h3>
|
||
|
*
|
||
|
* The logical whole of a gainmap'd image consists of a base Bitmap that represents the original
|
||
|
* image as would be displayed without gainmap support in addition to a gainmap with a second
|
||
|
* enhancement image. In the case of a JPEG, the base image would be the typical 8-bit SDR image
|
||
|
* that the format is commonly associated with. The gainmap image is embedded alongside the base
|
||
|
* image, often at a lower resolution (such as 1/4th), along with some metadata to describe
|
||
|
* how to apply the gainmap. The gainmap image itself is then a greyscale image representing
|
||
|
* the transformation to apply onto the base image to reconstruct an HDR rendition of it.
|
||
|
* <p>
|
||
|
* As such these "gainmap images" consist of 3 parts - a base {@link Bitmap} with a
|
||
|
* {@link Bitmap#getGainmap()} that returns an instance of this class which in turn contains
|
||
|
* the enhancement layer represented as another Bitmap, accessible via {@link #getGainmapContents()}
|
||
|
*
|
||
|
* <h3>Applying a gainmap manually</h3>
|
||
|
*
|
||
|
* When doing custom rendering such as to an OpenGL ES or Vulkan context, the gainmap is not
|
||
|
* automatically applied. In such situations, the following steps are appropriate to render the
|
||
|
* gainmap in combination with the base image.
|
||
|
* <p>
|
||
|
* Suppose our display has HDR to SDR ratio of H, and we wish to display an image with gainmap on
|
||
|
* this display. Let B be the pixel value from the base image in a color space that has the
|
||
|
* primaries of the base image and a linear transfer function. Let G be the pixel value from the
|
||
|
* gainmap. Let D be the output pixel in the same color space as B. The value of D is computed
|
||
|
* as follows:
|
||
|
* <p>
|
||
|
* First, let W be a weight parameter determining how much the gainmap will be applied.
|
||
|
* <pre class="prettyprint">
|
||
|
* W = clamp((log(H) - log(minDisplayRatioForHdrTransition)) /
|
||
|
* (log(displayRatioForFullHdr) - log(minDisplayRatioForHdrTransition), 0, 1)</pre>
|
||
|
*
|
||
|
* Next, let L be the gainmap value in log space. We compute this from the value G that was
|
||
|
* sampled from the texture as follows:
|
||
|
* <pre class="prettyprint">
|
||
|
* L = mix(log(ratioMin), log(ratioMax), pow(G, gamma))</pre>
|
||
|
* Finally, apply the gainmap to compute D, the displayed pixel. If the base image is SDR then
|
||
|
* compute:
|
||
|
* <pre class="prettyprint">
|
||
|
* D = (B + epsilonSdr) * exp(L * W) - epsilonHdr</pre>
|
||
|
* <p>
|
||
|
* In the above math, log() is a natural logarithm and exp() is natural exponentiation. The base
|
||
|
* for these functions cancels out and does not affect the result, so other bases may be used
|
||
|
* if preferred.
|
||
|
*/
|
||
|
public final class Gainmap implements Parcelable {
|
||
|
|
||
|
// Use a Holder to allow static initialization of Gainmap in the boot image.
|
||
|
private static class NoImagePreloadHolder {
|
||
|
public static final NativeAllocationRegistry sRegistry =
|
||
|
NativeAllocationRegistry.createMalloced(
|
||
|
Gainmap.class.getClassLoader(), nGetFinalizer());
|
||
|
}
|
||
|
|
||
|
final long mNativePtr;
|
||
|
private Bitmap mGainmapContents;
|
||
|
|
||
|
// called from JNI
|
||
|
private Gainmap(Bitmap gainmapContents, long nativeGainmap) {
|
||
|
if (nativeGainmap == 0) {
|
||
|
throw new RuntimeException("internal error: native gainmap is 0");
|
||
|
}
|
||
|
|
||
|
mNativePtr = nativeGainmap;
|
||
|
setGainmapContents(gainmapContents);
|
||
|
|
||
|
NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, nativeGainmap);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a gainmap from a given Bitmap. The caller is responsible for setting the various
|
||
|
* fields to the desired values. The defaults are as follows:
|
||
|
* <ul>
|
||
|
* <li>Ratio min is 1f, 1f, 1f</li>
|
||
|
* <li>Ratio max is 2f, 2f, 2f</li>
|
||
|
* <li>Gamma is 1f, 1f, 1f</li>
|
||
|
* <li>Epsilon SDR is 0f, 0f, 0f</li>
|
||
|
* <li>Epsilon HDR is 0f, 0f, 0f</li>
|
||
|
* <li>Display ratio SDR is 1f</li>
|
||
|
* <li>Display ratio HDR is 2f</li>
|
||
|
* </ul>
|
||
|
* It is strongly recommended that at least the ratio max and display ratio HDR are adjusted
|
||
|
* to better suit the given gainmap contents.
|
||
|
*/
|
||
|
public Gainmap(@NonNull Bitmap gainmapContents) {
|
||
|
this(gainmapContents, nCreateEmpty());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new gainmap using the provided gainmap as the metadata source and the provided
|
||
|
* bitmap as the replacement for the gainmapContents
|
||
|
*/
|
||
|
@FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA)
|
||
|
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
|
||
|
* relevant to the gainmap's enhancement layer.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public Bitmap getGainmapContents() {
|
||
|
return mGainmapContents;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the image data of the gainmap. This is the 1 or 3 channel enhancement layer to apply
|
||
|
* to the base image. 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 relevant to the gainmap's enhancement layer.
|
||
|
*
|
||
|
* @param bitmap The non-null bitmap to set as the gainmap's contents
|
||
|
*/
|
||
|
public void setGainmapContents(@NonNull Bitmap bitmap) {
|
||
|
// TODO: Validate here or leave native-side?
|
||
|
if (bitmap.isRecycled()) throw new IllegalArgumentException("Bitmap is recycled");
|
||
|
nSetBitmap(mNativePtr, bitmap);
|
||
|
mGainmapContents = bitmap;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the gainmap ratio min. For single-plane gainmaps, r, g, and b should be the same.
|
||
|
*/
|
||
|
public void setRatioMin(float r, float g, float b) {
|
||
|
nSetRatioMin(mNativePtr, r, g, b);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the gainmap ratio max. For single-plane gainmaps, all 3 components should be the
|
||
|
* same. The components are in r, g, b order.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public float[] getRatioMin() {
|
||
|
float[] ret = new float[3];
|
||
|
nGetRatioMin(mNativePtr, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the gainmap ratio max. For single-plane gainmaps, r, g, and b should be the same.
|
||
|
*/
|
||
|
public void setRatioMax(float r, float g, float b) {
|
||
|
nSetRatioMax(mNativePtr, r, g, b);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the gainmap ratio max. For single-plane gainmaps, all 3 components should be the
|
||
|
* same. The components are in r, g, b order.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public float[] getRatioMax() {
|
||
|
float[] ret = new float[3];
|
||
|
nGetRatioMax(mNativePtr, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the gainmap gamma. For single-plane gainmaps, r, g, and b should be the same.
|
||
|
*/
|
||
|
public void setGamma(float r, float g, float b) {
|
||
|
nSetGamma(mNativePtr, r, g, b);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the gainmap gamma. For single-plane gainmaps, all 3 components should be the
|
||
|
* same. The components are in r, g, b order.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public float[] getGamma() {
|
||
|
float[] ret = new float[3];
|
||
|
nGetGamma(mNativePtr, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the sdr epsilon which is used to avoid numerical instability.
|
||
|
* For single-plane gainmaps, r, g, and b should be the same.
|
||
|
*/
|
||
|
public void setEpsilonSdr(float r, float g, float b) {
|
||
|
nSetEpsilonSdr(mNativePtr, r, g, b);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the sdr epsilon. For single-plane gainmaps, all 3 components should be the
|
||
|
* same. The components are in r, g, b order.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public float[] getEpsilonSdr() {
|
||
|
float[] ret = new float[3];
|
||
|
nGetEpsilonSdr(mNativePtr, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the hdr epsilon which is used to avoid numerical instability.
|
||
|
* For single-plane gainmaps, r, g, and b should be the same.
|
||
|
*/
|
||
|
public void setEpsilonHdr(float r, float g, float b) {
|
||
|
nSetEpsilonHdr(mNativePtr, r, g, b);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the hdr epsilon. For single-plane gainmaps, all 3 components should be the
|
||
|
* same. The components are in r, g, b order.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public float[] getEpsilonHdr() {
|
||
|
float[] ret = new float[3];
|
||
|
nGetEpsilonHdr(mNativePtr, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the hdr/sdr ratio at which point the gainmap is fully applied.
|
||
|
* @param max The hdr/sdr ratio at which the gainmap is fully applied. Must be >= 1.0f
|
||
|
*/
|
||
|
public void setDisplayRatioForFullHdr(@FloatRange(from = 1.0f) float max) {
|
||
|
if (!Float.isFinite(max) || max < 1f) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"setDisplayRatioForFullHdr must be >= 1.0f, got = " + max);
|
||
|
}
|
||
|
nSetDisplayRatioHdr(mNativePtr, max);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the hdr/sdr ratio at which point the gainmap is fully applied.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public float getDisplayRatioForFullHdr() {
|
||
|
return nGetDisplayRatioHdr(mNativePtr);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the hdr/sdr ratio below which only the SDR image is displayed.
|
||
|
* @param min The minimum hdr/sdr ratio at which to begin applying the gainmap. Must be >= 1.0f
|
||
|
*/
|
||
|
public void setMinDisplayRatioForHdrTransition(@FloatRange(from = 1.0f) float min) {
|
||
|
if (!Float.isFinite(min) || min < 1f) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"setMinDisplayRatioForHdrTransition must be >= 1.0f, got = " + min);
|
||
|
}
|
||
|
nSetDisplayRatioSdr(mNativePtr, min);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the hdr/sdr ratio below which only the SDR image is displayed.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public float getMinDisplayRatioForHdrTransition() {
|
||
|
return nGetDisplayRatioSdr(mNativePtr);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* No special parcel contents.
|
||
|
*/
|
||
|
@Override
|
||
|
public int describeContents() {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Write the gainmap to the parcel.
|
||
|
*
|
||
|
* @param dest Parcel object to write the gainmap data into
|
||
|
* @param flags Additional flags about how the object should be written.
|
||
|
*/
|
||
|
@Override
|
||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||
|
if (mNativePtr == 0) {
|
||
|
throw new IllegalStateException("Cannot be written to a parcel");
|
||
|
}
|
||
|
dest.writeTypedObject(mGainmapContents, flags);
|
||
|
// write gainmapinfo into parcel
|
||
|
nWriteGainmapToParcel(mNativePtr, dest);
|
||
|
}
|
||
|
|
||
|
public static final @NonNull Parcelable.Creator<Gainmap> CREATOR =
|
||
|
new Parcelable.Creator<Gainmap>() {
|
||
|
/**
|
||
|
* Rebuilds a gainmap previously stored with writeToParcel().
|
||
|
*
|
||
|
* @param in Parcel object to read the gainmap from
|
||
|
* @return a new gainmap created from the data in the parcel
|
||
|
*/
|
||
|
public Gainmap createFromParcel(Parcel in) {
|
||
|
Gainmap gm = new Gainmap(in.readTypedObject(Bitmap.CREATOR));
|
||
|
// read gainmapinfo from parcel
|
||
|
nReadGainmapFromParcel(gm.mNativePtr, in);
|
||
|
return gm;
|
||
|
}
|
||
|
|
||
|
public Gainmap[] newArray(int size) {
|
||
|
return new Gainmap[size];
|
||
|
}
|
||
|
};
|
||
|
|
||
|
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);
|
||
|
|
||
|
private static native void nSetRatioMin(long ptr, float r, float g, float b);
|
||
|
private static native void nGetRatioMin(long ptr, float[] components);
|
||
|
|
||
|
private static native void nSetRatioMax(long ptr, float r, float g, float b);
|
||
|
private static native void nGetRatioMax(long ptr, float[] components);
|
||
|
|
||
|
private static native void nSetGamma(long ptr, float r, float g, float b);
|
||
|
private static native void nGetGamma(long ptr, float[] components);
|
||
|
|
||
|
private static native void nSetEpsilonSdr(long ptr, float r, float g, float b);
|
||
|
private static native void nGetEpsilonSdr(long ptr, float[] components);
|
||
|
|
||
|
private static native void nSetEpsilonHdr(long ptr, float r, float g, float b);
|
||
|
private static native void nGetEpsilonHdr(long ptr, float[] components);
|
||
|
|
||
|
private static native void nSetDisplayRatioHdr(long ptr, float max);
|
||
|
private static native float nGetDisplayRatioHdr(long ptr);
|
||
|
|
||
|
private static native void nSetDisplayRatioSdr(long ptr, float min);
|
||
|
private static native float nGetDisplayRatioSdr(long ptr);
|
||
|
private static native void nWriteGainmapToParcel(long ptr, Parcel dest);
|
||
|
private static native void nReadGainmapFromParcel(long ptr, Parcel src);
|
||
|
}
|