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

Commit 9b5c5259 authored by John Reck's avatar John Reck Committed by Android (Google) Code Review
Browse files

Merge "Add Gainmap API"

parents 6b365a84 3e98bce9
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -14933,6 +14933,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();
@@ -14948,6 +14949,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();
@@ -14959,6 +14961,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);
@@ -15494,6 +15497,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
@@ -1300,6 +1300,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[] = {
@@ -1351,6 +1358,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