• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.graphics;
18 
19 import android.annotation.FlaggedApi;
20 import android.annotation.FloatRange;
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 
27 import com.android.graphics.hwui.flags.Flags;
28 
29 import libcore.util.NativeAllocationRegistry;
30 
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 
34 /**
35  * Gainmap represents a mechanism for augmenting an SDR image to produce an HDR one with variable
36  * display adjustment capability. It is a combination of a set of metadata describing how to apply
37  * the gainmap, as well as either a 1 (such as {@link android.graphics.Bitmap.Config#ALPHA_8} or 3
38  * (such as {@link android.graphics.Bitmap.Config#ARGB_8888} with the alpha channel ignored)
39  * channel Bitmap that represents the gainmap data itself.
40  * <p>
41  * When rendering to an {@link android.content.pm.ActivityInfo#COLOR_MODE_HDR} activity, the
42  * hardware accelerated {@link Canvas} will automatically apply the gainmap when sufficient
43  * HDR headroom is available.
44  *
45  * <h3>Gainmap Structure</h3>
46  *
47  * The logical whole of a gainmap'd image consists of a base Bitmap that represents the original
48  * image as would be displayed without gainmap support in addition to a gainmap with a second
49  * enhancement image. In the case of a JPEG, the base image would be the typical 8-bit SDR image
50  * that the format is commonly associated with. The gainmap image is embedded alongside the base
51  * image, often at a lower resolution (such as 1/4th), along with some metadata to describe
52  * how to apply the gainmap. The gainmap image itself is then a greyscale image representing
53  * the transformation to apply onto the base image to reconstruct an HDR rendition of it.
54  * <p>
55  * As such these "gainmap images" consist of 3 parts - a base {@link Bitmap} with a
56  * {@link Bitmap#getGainmap()} that returns an instance of this class which in turn contains
57  * the enhancement layer represented as another Bitmap, accessible via {@link #getGainmapContents()}
58  *
59  * <h3>Applying a gainmap manually</h3>
60  *
61  * When doing custom rendering such as to an OpenGL ES or Vulkan context, the gainmap is not
62  * automatically applied. In such situations, the following steps are appropriate to render the
63  * gainmap in combination with the base image.
64  * <p>
65  * Suppose our display has HDR to SDR ratio of H, and we wish to display an image with gainmap on
66  * this display. Let B be the pixel value from the base image in a color space that has the
67  * primaries of the base image and a linear transfer function. Let G be the pixel value from the
68  * gainmap. Let D be the output pixel in the same color space as B. The value of D is computed
69  * as follows:
70  * <p>
71  * First, let W be a weight parameter determining how much the gainmap will be applied.
72  * <pre class="prettyprint">
73  *   W = clamp((log(H)                      - log(minDisplayRatioForHdrTransition)) /
74  *             (log(displayRatioForFullHdr) - log(minDisplayRatioForHdrTransition), 0, 1)</pre>
75  *
76  * Next, let L be the gainmap value in log space. We compute this from the value G that was
77  * sampled from the texture as follows:
78  * <pre class="prettyprint">
79  *   L = mix(log(ratioMin), log(ratioMax), pow(G, gamma))</pre>
80  * Finally, apply the gainmap to compute D, the displayed pixel. If the base image is SDR then
81  * compute:
82  * <pre class="prettyprint">
83  *   D = (B + epsilonSdr) * exp(L * W) - epsilonHdr</pre>
84  * <p>
85  * In the above math, log() is a natural logarithm and exp() is natural exponentiation. The base
86  * for these functions cancels out and does not affect the result, so other bases may be used
87  * if preferred.
88  */
89 @android.ravenwood.annotation.RavenwoodKeepWholeClass
90 public final class Gainmap implements Parcelable {
91 
92     /** @hide */
93     @Retention(RetentionPolicy.SOURCE)
94     @IntDef(prefix = {"GAINMAP_DIRECTION_"},
95             value = {GAINMAP_DIRECTION_SDR_TO_HDR,
96                      GAINMAP_DIRECTION_HDR_TO_SDR})
97     public @interface GainmapDirection {}
98 
99     /**
100      * The gainmap will be applied as if the base image were SDR, and fully applying the gainmap
101      * results in an HDR image.
102      */
103     @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS)
104     public static final int GAINMAP_DIRECTION_SDR_TO_HDR = 0;
105 
106     /**
107      * The gainmap will be applied as if the base image were HDR, and fully applying the gainmap
108      * results in an SDR image.
109      */
110     @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS)
111     public static final int GAINMAP_DIRECTION_HDR_TO_SDR = 1;
112 
113     // Use a Holder to allow static initialization of Gainmap in the boot image.
114     private static class NoImagePreloadHolder {
115         public static final NativeAllocationRegistry sRegistry =
116                 NativeAllocationRegistry.createMalloced(
117                         Gainmap.class.getClassLoader(), nGetFinalizer());
118     }
119 
120     final long mNativePtr;
121     private Bitmap mGainmapContents;
122 
123     // called from JNI
Gainmap(Bitmap gainmapContents, long nativeGainmap)124     private Gainmap(Bitmap gainmapContents, long nativeGainmap) {
125         if (nativeGainmap == 0) {
126             throw new RuntimeException("internal error: native gainmap is 0");
127         }
128 
129         mNativePtr = nativeGainmap;
130         setGainmapContents(gainmapContents);
131 
132         NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, nativeGainmap);
133     }
134 
135     /**
136      * Creates a gainmap from a given Bitmap. The caller is responsible for setting the various
137      * fields to the desired values. The defaults are as follows:
138      * <ul>
139      *     <li>Ratio min is 1f, 1f, 1f</li>
140      *     <li>Ratio max is 2f, 2f, 2f</li>
141      *     <li>Gamma is 1f, 1f, 1f</li>
142      *     <li>Epsilon SDR is 0f, 0f, 0f</li>
143      *     <li>Epsilon HDR is 0f, 0f, 0f</li>
144      *     <li>Display ratio SDR is 1f</li>
145      *     <li>Display ratio HDR is 2f</li>
146      * </ul>
147      * It is strongly recommended that at least the ratio max and display ratio HDR are adjusted
148      * to better suit the given gainmap contents.
149      */
Gainmap(@onNull Bitmap gainmapContents)150     public Gainmap(@NonNull Bitmap gainmapContents) {
151         this(gainmapContents, nCreateEmpty());
152     }
153 
154     /**
155      * Creates a new gainmap using the provided gainmap as the metadata source and the provided
156      * bitmap as the replacement for the gainmapContents
157      */
158     @FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA)
Gainmap(@onNull Gainmap gainmap, @NonNull Bitmap gainmapContents)159     public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) {
160         this(gainmapContents, nCreateCopy(gainmap.mNativePtr));
161     }
162 
163     /**
164      * @hide
165      */
asShared()166     public Gainmap asShared() {
167         final Bitmap sharedContents = mGainmapContents.asShared();
168         if (sharedContents == mGainmapContents) {
169             return this;
170         } else {
171             return new Gainmap(sharedContents, nCreateCopy(mNativePtr));
172         }
173     }
174 
175     /**
176      * @return Returns the image data of the gainmap represented as a Bitmap. This is represented
177      * as a Bitmap for broad API compatibility, however certain aspects of the Bitmap are ignored
178      * such as {@link Bitmap#getColorSpace()} or {@link Bitmap#getGainmap()} as they are not
179      * relevant to the gainmap's enhancement layer.
180      */
181     @NonNull
getGainmapContents()182     public Bitmap getGainmapContents() {
183         return mGainmapContents;
184     }
185 
186     /**
187      * Sets the image data of the gainmap. This is the 1 or 3 channel enhancement layer to apply
188      * to the base image. This is represented as a Bitmap for broad API compatibility, however
189      * certain aspects of the Bitmap are ignored such as {@link Bitmap#getColorSpace()} or
190      * {@link Bitmap#getGainmap()} as they are not relevant to the gainmap's enhancement layer.
191      *
192      * @param bitmap The non-null bitmap to set as the gainmap's contents
193      */
setGainmapContents(@onNull Bitmap bitmap)194     public void setGainmapContents(@NonNull Bitmap bitmap) {
195         // TODO: Validate here or leave native-side?
196         if (bitmap.isRecycled()) throw new IllegalArgumentException("Bitmap is recycled");
197         nSetBitmap(mNativePtr, bitmap);
198         mGainmapContents = bitmap;
199     }
200 
201     /**
202      * Sets the gainmap ratio min. For single-plane gainmaps, r, g, and b should be the same.
203      */
setRatioMin(float r, float g, float b)204     public void setRatioMin(float r, float g, float b) {
205         nSetRatioMin(mNativePtr, r, g, b);
206     }
207 
208     /**
209      * Gets the gainmap ratio max. For single-plane gainmaps, all 3 components should be the
210      * same. The components are in r, g, b order.
211      */
212     @NonNull
getRatioMin()213     public float[] getRatioMin() {
214         float[] ret = new float[3];
215         nGetRatioMin(mNativePtr, ret);
216         return ret;
217     }
218 
219     /**
220      * Sets the gainmap ratio max. For single-plane gainmaps, r, g, and b should be the same.
221      */
setRatioMax(float r, float g, float b)222     public void setRatioMax(float r, float g, float b) {
223         nSetRatioMax(mNativePtr, r, g, b);
224     }
225 
226     /**
227      * Gets the gainmap ratio max. For single-plane gainmaps, all 3 components should be the
228      * same. The components are in r, g, b order.
229      */
230     @NonNull
getRatioMax()231     public float[] getRatioMax() {
232         float[] ret = new float[3];
233         nGetRatioMax(mNativePtr, ret);
234         return ret;
235     }
236 
237     /**
238      * Sets the gainmap gamma. For single-plane gainmaps, r, g, and b should be the same.
239      */
setGamma(float r, float g, float b)240     public void setGamma(float r, float g, float b) {
241         nSetGamma(mNativePtr, r, g, b);
242     }
243 
244     /**
245      * Gets the gainmap gamma. For single-plane gainmaps, all 3 components should be the
246      * same. The components are in r, g, b order.
247      */
248     @NonNull
getGamma()249     public float[] getGamma() {
250         float[] ret = new float[3];
251         nGetGamma(mNativePtr, ret);
252         return ret;
253     }
254 
255     /**
256      * Sets the sdr epsilon which is used to avoid numerical instability.
257      * For single-plane gainmaps, r, g, and b should be the same.
258      */
setEpsilonSdr(float r, float g, float b)259     public void setEpsilonSdr(float r, float g, float b) {
260         nSetEpsilonSdr(mNativePtr, r, g, b);
261     }
262 
263     /**
264      * Gets the sdr epsilon. For single-plane gainmaps, all 3 components should be the
265      * same. The components are in r, g, b order.
266      */
267     @NonNull
getEpsilonSdr()268     public float[] getEpsilonSdr() {
269         float[] ret = new float[3];
270         nGetEpsilonSdr(mNativePtr, ret);
271         return ret;
272     }
273 
274     /**
275      * Sets the hdr epsilon which is used to avoid numerical instability.
276      * For single-plane gainmaps, r, g, and b should be the same.
277      */
setEpsilonHdr(float r, float g, float b)278     public void setEpsilonHdr(float r, float g, float b) {
279         nSetEpsilonHdr(mNativePtr, r, g, b);
280     }
281 
282     /**
283      * Gets the hdr epsilon. For single-plane gainmaps, all 3 components should be the
284      * same. The components are in r, g, b order.
285      */
286     @NonNull
getEpsilonHdr()287     public float[] getEpsilonHdr() {
288         float[] ret = new float[3];
289         nGetEpsilonHdr(mNativePtr, ret);
290         return ret;
291     }
292 
293     /**
294      * Sets the hdr/sdr ratio at which point applying the gainmap results in an HDR rendition.
295      * @param max The hdr/sdr ratio at which point applying the gainmap results in an HDR rendition.
296      * Must be >= 1.0f
297      */
setDisplayRatioForFullHdr(@loatRangefrom = 1.0f) float max)298     public void setDisplayRatioForFullHdr(@FloatRange(from = 1.0f) float max) {
299         if (!Float.isFinite(max) || max < 1f) {
300             throw new IllegalArgumentException(
301                     "setDisplayRatioForFullHdr must be >= 1.0f, got = " + max);
302         }
303         nSetDisplayRatioHdr(mNativePtr, max);
304     }
305 
306     /**
307      * Gets the hdr/sdr ratio at which point applying the gainmap results in an HDR rendition
308      */
309     @NonNull
getDisplayRatioForFullHdr()310     public float getDisplayRatioForFullHdr() {
311         return nGetDisplayRatioHdr(mNativePtr);
312     }
313 
314     /**
315      * Sets the hdr/sdr ratio below which applying the gainmap results in an SDR rendition.
316      * @param min The minimum hdr/sdr ratio at which point applying the gainmap results in an SDR
317      * rendition. Must be >= 1.0f
318      */
setMinDisplayRatioForHdrTransition(@loatRangefrom = 1.0f) float min)319     public void setMinDisplayRatioForHdrTransition(@FloatRange(from = 1.0f) float min) {
320         if (!Float.isFinite(min) || min < 1f) {
321             throw new IllegalArgumentException(
322                     "setMinDisplayRatioForHdrTransition must be >= 1.0f, got = " + min);
323         }
324         nSetDisplayRatioSdr(mNativePtr, min);
325     }
326 
327     /**
328      * Gets the hdr/sdr ratio below which applying the gainmap results in an SDR rendition.
329      */
330     @NonNull
getMinDisplayRatioForHdrTransition()331     public float getMinDisplayRatioForHdrTransition() {
332         return nGetDisplayRatioSdr(mNativePtr);
333     }
334 
335     /**
336      * Sets the colorspace that the gainmap math should be applied in.
337      * Only the primaries are what is relevant for applying the gainmap. The transfer and range
338      * characteritics are ignored.
339      *
340      * If the supplied ColorSpace is null, then applying the gainmap will be done using the color
341      * gamut of the base image.
342      */
343     @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS)
setAlternativeImagePrimaries(@ullable ColorSpace colorSpace)344     public void setAlternativeImagePrimaries(@Nullable ColorSpace colorSpace) {
345         long colorSpaceInstance = colorSpace == null ? 0 : colorSpace.getNativeInstance();
346         nSetAlternativeColorSpace(mNativePtr, colorSpaceInstance);
347     }
348 
349     /**
350      * Gets the colorspace that the gainmap math should be applied in.
351      * Only the primaries are what is relevant for applying the gainmap. The transfer and range
352      * characteritics are ignored.
353      *
354      * If the returned ColorSpace is null, then applying the gainmap will be done using the color
355      * gamut of the base image.
356      */
357     @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS)
358     @Nullable
getAlternativeImagePrimaries()359     public ColorSpace getAlternativeImagePrimaries() {
360         return nGetAlternativeColorSpace(mNativePtr);
361     }
362 
363     /**
364      * Sets the direction that the gainmap math should be applied in.
365      */
366     @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS)
setGainmapDirection(@ainmapDirection int direction)367     public void setGainmapDirection(@GainmapDirection int direction) {
368         if (direction != GAINMAP_DIRECTION_SDR_TO_HDR
369                 && direction != GAINMAP_DIRECTION_HDR_TO_SDR) {
370             throw new IllegalArgumentException("Invalid gainmap direction: " + direction);
371         }
372         nSetDirection(mNativePtr, direction);
373     }
374 
375     /**
376      * Gets the direction that the gainmap math should be applied in.
377      */
378     @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS)
getGainmapDirection()379     public @GainmapDirection int getGainmapDirection() {
380         return nGetDirection(mNativePtr);
381     }
382 
383 
384     /**
385      * No special parcel contents.
386      */
387     @Override
describeContents()388     public int describeContents() {
389         return 0;
390     }
391 
392     /**
393      * Write the gainmap to the parcel.
394      *
395      * @param dest Parcel object to write the gainmap data into
396      * @param flags Additional flags about how the object should be written.
397      */
398     @Override
writeToParcel(@onNull Parcel dest, int flags)399     public void writeToParcel(@NonNull Parcel dest, int flags) {
400         if (mNativePtr == 0) {
401             throw new IllegalStateException("Cannot be written to a parcel");
402         }
403         dest.writeTypedObject(mGainmapContents, flags);
404         // write gainmapinfo into parcel
405         nWriteGainmapToParcel(mNativePtr, dest);
406     }
407 
408     public static final @NonNull Parcelable.Creator<Gainmap> CREATOR =
409             new Parcelable.Creator<Gainmap>() {
410             /**
411              * Rebuilds a gainmap previously stored with writeToParcel().
412              *
413              * @param in Parcel object to read the gainmap from
414              * @return a new gainmap created from the data in the parcel
415              */
416             public Gainmap createFromParcel(Parcel in) {
417                 Gainmap gm = new Gainmap(in.readTypedObject(Bitmap.CREATOR));
418                 // read gainmapinfo from parcel
419                 nReadGainmapFromParcel(gm.mNativePtr, in);
420                 return gm;
421             }
422 
423             public Gainmap[] newArray(int size) {
424                 return new Gainmap[size];
425             }
426         };
427 
nGetFinalizer()428     private static native long nGetFinalizer();
nCreateEmpty()429     private static native long nCreateEmpty();
nCreateCopy(long source)430     private static native long nCreateCopy(long source);
431 
nSetBitmap(long ptr, Bitmap bitmap)432     private static native void nSetBitmap(long ptr, Bitmap bitmap);
433 
nSetRatioMin(long ptr, float r, float g, float b)434     private static native void nSetRatioMin(long ptr, float r, float g, float b);
nGetRatioMin(long ptr, float[] components)435     private static native void nGetRatioMin(long ptr, float[] components);
436 
nSetRatioMax(long ptr, float r, float g, float b)437     private static native void nSetRatioMax(long ptr, float r, float g, float b);
nGetRatioMax(long ptr, float[] components)438     private static native void nGetRatioMax(long ptr, float[] components);
439 
nSetGamma(long ptr, float r, float g, float b)440     private static native void nSetGamma(long ptr, float r, float g, float b);
nGetGamma(long ptr, float[] components)441     private static native void nGetGamma(long ptr, float[] components);
442 
nSetEpsilonSdr(long ptr, float r, float g, float b)443     private static native void nSetEpsilonSdr(long ptr, float r, float g, float b);
nGetEpsilonSdr(long ptr, float[] components)444     private static native void nGetEpsilonSdr(long ptr, float[] components);
445 
nSetEpsilonHdr(long ptr, float r, float g, float b)446     private static native void nSetEpsilonHdr(long ptr, float r, float g, float b);
nGetEpsilonHdr(long ptr, float[] components)447     private static native void nGetEpsilonHdr(long ptr, float[] components);
448 
nSetDisplayRatioHdr(long ptr, float max)449     private static native void nSetDisplayRatioHdr(long ptr, float max);
nGetDisplayRatioHdr(long ptr)450     private static native float nGetDisplayRatioHdr(long ptr);
451 
nSetDisplayRatioSdr(long ptr, float min)452     private static native void nSetDisplayRatioSdr(long ptr, float min);
nGetDisplayRatioSdr(long ptr)453     private static native float nGetDisplayRatioSdr(long ptr);
nSetAlternativeColorSpace(long ptr, long colorSpacePtr)454     private static native void nSetAlternativeColorSpace(long ptr, long colorSpacePtr);
nGetAlternativeColorSpace(long ptr)455     private static native ColorSpace nGetAlternativeColorSpace(long ptr);
nSetDirection(long ptr, int direction)456     private static native void nSetDirection(long ptr, int direction);
nGetDirection(long ptr)457     private static native int nGetDirection(long ptr);
nWriteGainmapToParcel(long ptr, Parcel dest)458     private static native void nWriteGainmapToParcel(long ptr, Parcel dest);
nReadGainmapFromParcel(long ptr, Parcel src)459     private static native void nReadGainmapFromParcel(long ptr, Parcel src);
460 }
461