Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3e98bce9 authored by John Reck's avatar John Reck
Browse files

Add Gainmap API

Bug: 266628247
Test: CtsGraphicsTestCases:GainmapTest
Change-Id: I288516df8ba2bb72525462699d2704bb43a0fd90
parent 1cee73cc
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -14779,6 +14779,7 @@ package android.graphics {
    method @Nullable public android.graphics.ColorSpace getColorSpace();
    method @NonNull public android.graphics.Bitmap.Config getConfig();
    method public int getDensity();
    method @Nullable public android.graphics.Gainmap getGainmap();
    method public int getGenerationId();
    method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer();
    method public int getHeight();
@@ -14794,6 +14795,7 @@ package android.graphics {
    method public int getScaledWidth(int);
    method public int getWidth();
    method public boolean hasAlpha();
    method public boolean hasGainmap();
    method public boolean hasMipMap();
    method public boolean isMutable();
    method public boolean isPremultiplied();
@@ -14805,6 +14807,7 @@ package android.graphics {
    method public void setColorSpace(@NonNull android.graphics.ColorSpace);
    method public void setConfig(@NonNull android.graphics.Bitmap.Config);
    method public void setDensity(int);
    method public void setGainmap(@Nullable android.graphics.Gainmap);
    method public void setHasAlpha(boolean);
    method public void setHasMipMap(boolean);
    method public void setHeight(int);
@@ -15338,6 +15341,26 @@ package android.graphics {
    ctor @Deprecated public EmbossMaskFilter(float[], float, float, float);
  }
  public final class Gainmap {
    ctor public Gainmap(@NonNull android.graphics.Bitmap);
    method @NonNull public float getDisplayRatioForFullHdr();
    method @NonNull public float[] getEpsilonHdr();
    method @NonNull public float[] getEpsilonSdr();
    method @NonNull public android.graphics.Bitmap getGainmapContents();
    method @NonNull public float[] getGamma();
    method @NonNull public float getMinDisplayRatioForHdrTransition();
    method @NonNull public float[] getRatioMax();
    method @NonNull public float[] getRatioMin();
    method @NonNull public void setDisplayRatioForFullHdr(float);
    method @NonNull public void setEpsilonHdr(float, float, float);
    method @NonNull public void setEpsilonSdr(float, float, float);
    method public void setGainmapContents(@NonNull android.graphics.Bitmap);
    method @NonNull public void setGamma(float, float, float);
    method @NonNull public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float);
    method @NonNull public void setRatioMax(float, float, float);
    method @NonNull public void setRatioMin(float, float, float);
  }
  public class HardwareBufferRenderer implements java.lang.AutoCloseable {
    ctor public HardwareBufferRenderer(@NonNull android.hardware.HardwareBuffer);
    method public void close();
+9 −2
Original line number Diff line number Diff line
@@ -1899,7 +1899,6 @@ public final class Bitmap implements Parcelable {

    /**
     * Returns whether or not this Bitmap contains a Gainmap.
     * @hide
     */
    public boolean hasGainmap() {
        checkRecycled("Bitmap is recycled");
@@ -1908,7 +1907,6 @@ public final class Bitmap implements Parcelable {

    /**
     * Returns the gainmap or null if the bitmap doesn't contain a gainmap
     * @hide
     */
    public @Nullable Gainmap getGainmap() {
        checkRecycled("Bitmap is recycled");
@@ -1918,6 +1916,14 @@ public final class Bitmap implements Parcelable {
        return mGainmap;
    }

    /**
     * Sets a gainmap on this bitmap, or removes the gainmap if null
     */
    public void setGainmap(@Nullable Gainmap gainmap) {
        checkRecycled("Bitmap is recycled");
        nativeSetGainmap(mNativePtr, gainmap == null ? 0 : gainmap.mNativePtr);
    }

    /**
     * Fills the bitmap's pixels with the specified {@link Color}.
     *
@@ -2403,6 +2409,7 @@ public final class Bitmap implements Parcelable {
    private static native void nativeSetImmutable(long nativePtr);

    private static native Gainmap nativeExtractGainmap(long nativePtr);
    private static native void nativeSetGainmap(long bitmapPtr, long gainmapPtr);

    // ---------------- @CriticalNative -------------------

+231 −46
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.graphics;

import android.annotation.FloatRange;
import android.annotation.NonNull;

import libcore.util.NativeAllocationRegistry;
@@ -24,104 +25,288 @@ 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 the gainmap, as well as either a 1 or 3
 * 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.
 *
 * @hide
 * 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.
 *
 * 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.
 *
 * 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:
 *
 * First, let W be a weight parameter determining how much the gainmap will be applied.
 *   W = clamp((log(H)               - log(displayRatioHdr)) /
 *             (log(displayRatioHdr) - log(displayRatioSdr), 0, 1)
 *
 * 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:
 *   L = mix(log(gainmapRatioMin), log(gainmapRatioMax), pow(G, gainmapGamma))
 *
 * Finally, apply the gainmap to compute D, the displayed pixel. If the base image is SDR then
 * compute:
 *   D = (B + epsilonSdr) * exp(L * W) - epsilonHdr
 * If the base image is HDR then compute:
 *   D = (B + epsilonHdr) * exp(L * (W - 1)) - epsilonSdr
 *
 * In the above math, log() is a natural logarithm and exp() is natural exponentiation.
 */
public class Gainmap {
    private final long mNativePtr;
    private final Bitmap mGainmapImage;
public final class Gainmap {

    // called from JNI and Bitmap_Delegate.
    private Gainmap(Bitmap gainmapImage, long nativeGainmap, int allocationByteCount,
            boolean fromMalloc) {
    // 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");
        }

        mGainmapImage = gainmapImage;
        mGainmapContents = gainmapContents;
        mNativePtr = nativeGainmap;

        final NativeAllocationRegistry registry;
        if (fromMalloc) {
            registry = NativeAllocationRegistry.createMalloced(
                    Bitmap.class.getClassLoader(), nGetFinalizer(), allocationByteCount);
        } else {
            registry = NativeAllocationRegistry.createNonmalloced(
                    Bitmap.class.getClassLoader(), nGetFinalizer(), allocationByteCount);
        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());
    }

    /**
     * @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.
     */
    @NonNull
    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.
     */
    @NonNull
    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.
     */
    @NonNull
    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;
    }
        registry.registerNativeAllocation(this, nativeGainmap);

    /**
     * Sets the sdr epsilon which is used to avoid numerical instability.
     * For single-plane gainmaps, r, g, and b should be the same.
     */
    @NonNull
    public void setEpsilonSdr(float r, float g, float b) {
        nSetEpsilonSdr(mNativePtr, r, g, b);
    }

    /**
     * Returns the image data of the gainmap represented as a Bitmap
     * @return
     * 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 Bitmap getGainmapImage() {
        return mGainmapImage;
    public float[] getEpsilonSdr() {
        float[] ret = new float[3];
        nGetEpsilonSdr(mNativePtr, ret);
        return ret;
    }

    /**
     * Sets the gainmap max metadata. For single-plane gainmaps, r, g, and b should be the same.
     * Sets the hdr epsilon which is used to avoid numerical instability.
     * For single-plane gainmaps, r, g, and b should be the same.
     */
    @NonNull
    public void setGainmapMax(float r, float g, float b) {
        nSetGainmapMax(mNativePtr, r, g, b);
    public void setEpsilonHdr(float r, float g, float b) {
        nSetEpsilonHdr(mNativePtr, r, g, b);
    }

    /**
     * Gets the gainmap max metadata. For single-plane gainmaps, all 3 components should be the
     * 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[] getGainmapMax() {
    public float[] getEpsilonHdr() {
        float[] ret = new float[3];
        nGetGainmapMax(mNativePtr, ret);
        nGetEpsilonHdr(mNativePtr, ret);
        return ret;
    }

    /**
     * Sets the maximum HDR ratio for the gainmap
     * 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
     */
    @NonNull
    public void setHdrRatioMax(float max) {
        nSetHdrRatioMax(mNativePtr, max);
    public void setDisplayRatioForFullHdr(float max) {
        if (!Float.isFinite(max) || max < 1f) {
            throw new IllegalArgumentException(
                    "setDisplayRatioForFullHdr must be >= 1.0f, got = " + max);
        }
        nSetDisplayRatioHdr(mNativePtr, max);
    }

    /**
     * Gets the maximum HDR ratio for the gainmap
     * Gets the hdr/sdr ratio at which point the gainmap is fully applied.
     */
    @NonNull
    public float getHdrRatioMax() {
        return nGetHdrRatioMax(mNativePtr);
    public float getDisplayRatioForFullHdr() {
        return nGetDisplayRatioHdr(mNativePtr);
    }

    /**
     * Sets the maximum HDR ratio for the gainmap
     * 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
     */
    @NonNull
    public void setHdrRatioMin(float min) {
        nSetHdrRatioMin(mNativePtr, min);
    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 maximum HDR ratio for the gainmap
     * Gets the hdr/sdr ratio below which only the SDR image is displayed.
     */
    @NonNull
    public float getHdrRatioMin() {
        return nGetHdrRatioMin(mNativePtr);
    public float getMinDisplayRatioForHdrTransition() {
        return nGetDisplayRatioSdr(mNativePtr);
    }

    private static native long nGetFinalizer();
    private static native long nCreateEmpty();

    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 nSetGainmapMax(long ptr, float r, float g, float b);
    private static native void nGetGainmapMax(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 nSetHdrRatioMax(long ptr, float max);
    private static native float nGetHdrRatioMax(long ptr);
    private static native void nSetDisplayRatioHdr(long ptr, float max);
    private static native float nGetDisplayRatioHdr(long ptr);

    private static native void nSetHdrRatioMin(long ptr, float min);
    private static native float nGetHdrRatioMin(long ptr);
    private static native void nSetDisplayRatioSdr(long ptr, float min);
    private static native float nGetDisplayRatioSdr(long ptr);
}
+6 −0
Original line number Diff line number Diff line
@@ -506,6 +506,9 @@ SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) {
    decoder.mOverrideOrigin.emplace(getOrigin());
    // Update mDecodeSize / mTargetSize for the overridden origin
    decoder.setTargetSize(decoder.width(), decoder.height());
    if (decoder.gray()) {
        decoder.setOutColorType(kGray_8_SkColorType);
    }

    const bool isScaled = width() != mTargetSize.width() || height() != mTargetSize.height();

@@ -528,6 +531,9 @@ SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) {
    }

    SkImageInfo bitmapInfo = decoder.getOutputInfo();
    if (bitmapInfo.colorType() == kGray_8_SkColorType) {
        bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
    }

    SkBitmap bm;
    if (!bm.setInfo(bitmapInfo)) {
+8 −0
Original line number Diff line number Diff line
@@ -1269,6 +1269,13 @@ static jobject Bitmap_extractGainmap(JNIEnv* env, jobject, jlong bitmapHandle) {
    return Gainmap_extractFromBitmap(env, bitmapHolder->bitmap());
}

static void Bitmap_setGainmap(JNIEnv*, jobject, jlong bitmapHandle, jlong gainmapPtr) {
    LocalScopedBitmap bitmapHolder(bitmapHandle);
    if (!bitmapHolder.valid()) return;
    uirenderer::Gainmap* gainmap = reinterpret_cast<uirenderer::Gainmap*>(gainmapPtr);
    bitmapHolder->bitmap().setGainmap(sp<uirenderer::Gainmap>::fromExisting(gainmap));
}

///////////////////////////////////////////////////////////////////////////////

static const JNINativeMethod gBitmapMethods[] = {
@@ -1320,6 +1327,7 @@ static const JNINativeMethod gBitmapMethods[] = {
        {"nativeIsSRGBLinear", "(J)Z", (void*)Bitmap_isSRGBLinear},
        {"nativeSetImmutable", "(J)V", (void*)Bitmap_setImmutable},
        {"nativeExtractGainmap", "(J)Landroid/graphics/Gainmap;", (void*)Bitmap_extractGainmap},
        {"nativeSetGainmap", "(JJ)V", (void*)Bitmap_setGainmap},

        // ------------ @CriticalNative ----------------
        {"nativeIsImmutable", "(J)Z", (void*)Bitmap_isImmutable},
Loading