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

Commit 0db53ee3 authored by Nick Deakin's avatar Nick Deakin
Browse files

libultrahdr: correct srgb, p3 calculations and jpeg yuv handling

* Correct luminance calculation for sRGB to utilize actual luminance
  coefficients for the gamut, rather than 601 luma coefficients.
* Correct YUV<->RGB conversion for sRGB to utilize Rec.709 coefficients
  rather than Rec.601 coefficients as it was previously.
* New P3 YUV<->RGB conversion, which uses Rec.601 coefficients.
* Also ICC Profile fixes to make things work; more below.
* Update things to correctly convert to and from Rec.601 YUV for jpeg
  encoding; more below.

This setup for YUV<->RGB coefficients is chosen to match the
expectations of DataSpace when it comes to interpretting YUV encoding of
data. Generally, the interpretation is cued off of the color primaries,
since the specifications around color primaries generally also specify a
YUV interpretation.

Display-P3 is a bit of an outlier; the best specification of Display-P3
is in SMPTE EG 432-1, but EG 432-1 doesn't cover YUV interpretation. So,
since DataSpace interprets Display-P3 YUV data via the Rec.601
coefficients, we should do the same here.

ICC Profile fixes; ICC profiles we wrote were broken before this for a
variety of reasons:
* The endianness macro wasn't actually swapping endiannesas to provide
  the correct encoding in our output.
* We weren't writing out the identifier for the app segment, including
  the chunk count and ID.
* We were assuming input JPEGs have ICC data, which may not be the case.
* We also need to read in the ICC profile during decode to apply the map
  properly, and we didn't have any mechanism previously to read the ICC
  profile and determine the gamut of the encoded JPEGR file.
* Upon adding ICC reading code to our JPEG decoding, also remove some
  dead code from previous EXIF reading.
* Add a number of tests to verify all of this stuff stays fixed.

YUV interpretation and Rec.601:
* Previously, we were feeding YUV right into the JPEG encoder; this is
  problematic because JPEG encoders usually (and definitely in our
  specific case) expect Rec.601 YUV encoded input data, since this is by
  definition the format of JPEG YUV data according to ECMA TR/98.
* Now properly convert from Rec.709 or Rec.2100 YUV encoding to Rec.601
  (when necessary) prior to passing YUV data to the jpeg encoder.
* Also make sure we properly interpret decoded YUV output as Rec.601
  after decode.
* This involved added some new methods to facilitate these conversions.
* Added some new tests to verify these conversions.
* Note that to do these YUV conversions for subsampled 420 data, we take
  each set of 4 Y and 1 UV, and calculate the result against each
  combination. The new Y values each get the corresponding result, and
  the new UV value is equal to the average of the set.
* Note that none of this is a concern for gain map encoding/decoding via
  JPEG because gain maps are single channel.

Bug: 283143961
Test: added new tests, all tests pass
Change-Id: Ibc7b1779fc3a8244f85abb581c554963f57dc5a4
parent fcb80a22
Loading
Loading
Loading
Loading
+132 −17
Original line number Diff line number Diff line
@@ -119,34 +119,39 @@ static float clampPixelFloat(float value) {
    return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value;
}

// See IEC 61966-2-1, Equation F.7.
// See IEC 61966-2-1/Amd 1:2003, Equation F.7.
static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f;

float srgbLuminance(Color e) {
  return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b;
}

// See ECMA TR/98, Section 7.
static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f;
// See ITU-R BT.709-6, Section 3.
// Uses the same coefficients for deriving luma signal as
// IEC 61966-2-1/Amd 1:2003 states for luminance, so we reuse the luminance
// function above.
static const float kSrgbCb = 1.8556f, kSrgbCr = 1.5748f;

Color srgbYuvToRgb(Color e_gamma) {
  return {{{ clampPixelFloat(e_gamma.y + kSrgbRCr * e_gamma.v),
             clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v),
             clampPixelFloat(e_gamma.y + kSrgbBCb * e_gamma.u) }}};
Color srgbRgbToYuv(Color e_gamma) {
  float y_gamma = srgbLuminance(e_gamma);
  return {{{ y_gamma,
             (e_gamma.b - y_gamma) / kSrgbCb,
             (e_gamma.r - y_gamma) / kSrgbCr }}};
}

// See ECMA TR/98, Section 7.
static const float kSrgbYR = 0.299f, kSrgbYG = 0.587f, kSrgbYB = 0.114f;
static const float kSrgbUR = -0.1687f, kSrgbUG = -0.3313f, kSrgbUB = 0.5f;
static const float kSrgbVR = 0.5f, kSrgbVG = -0.4187f, kSrgbVB = -0.0813f;
// See ITU-R BT.709-6, Section 3.
// Same derivation to BT.2100's YUV->RGB, below. Similar to srgbRgbToYuv, we
// can reuse the luminance coefficients since they are the same.
static const float kSrgbGCb = kSrgbB * kSrgbCb / kSrgbG;
static const float kSrgbGCr = kSrgbR * kSrgbCr / kSrgbG;

Color srgbRgbToYuv(Color e_gamma) {
  return {{{ kSrgbYR * e_gamma.r + kSrgbYG * e_gamma.g + kSrgbYB * e_gamma.b,
             kSrgbUR * e_gamma.r + kSrgbUG * e_gamma.g + kSrgbUB * e_gamma.b,
             kSrgbVR * e_gamma.r + kSrgbVG * e_gamma.g + kSrgbVB * e_gamma.b }}};
Color srgbYuvToRgb(Color e_gamma) {
  return {{{ clampPixelFloat(e_gamma.y + kSrgbCr * e_gamma.v),
             clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v),
             clampPixelFloat(e_gamma.y + kSrgbCb * e_gamma.u) }}};
}

// See IEC 61966-2-1, Equations F.5 and F.6.
// See IEC 61966-2-1/Amd 1:2003, Equations F.5 and F.6.
float srgbInvOetf(float e_gamma) {
  if (e_gamma <= 0.04045f) {
    return e_gamma / 12.92f;
@@ -178,13 +183,38 @@ Color srgbInvOetfLUT(Color e_gamma) {
////////////////////////////////////////////////////////////////////////////////
// Display-P3 transformations

// See SMPTE EG 432-1, Table 7-2.
// See SMPTE EG 432-1, Equation 7-8.
static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f;

float p3Luminance(Color e) {
  return kP3R * e.r + kP3G * e.g + kP3B * e.b;
}

// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
// Unfortunately, calculation of luma signal differs from calculation of
// luminance for Display-P3, so we can't reuse p3Luminance here.
static const float kP3YR = 0.299f, kP3YG = 0.587f, kP3YB = 0.114f;
static const float kP3Cb = 1.772f, kP3Cr = 1.402f;

Color p3RgbToYuv(Color e_gamma) {
  float y_gamma = kP3YR * e_gamma.r + kP3YG * e_gamma.g + kP3YB * e_gamma.b;
  return {{{ y_gamma,
             (e_gamma.b - y_gamma) / kP3Cb,
             (e_gamma.r - y_gamma) / kP3Cr }}};
}

// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
// Same derivation to BT.2100's YUV->RGB, below. Similar to p3RgbToYuv, we must
// use luma signal coefficients rather than the luminance coefficients.
static const float kP3GCb = kP3YB * kP3Cb / kP3YG;
static const float kP3GCr = kP3YR * kP3Cr / kP3YG;

Color p3YuvToRgb(Color e_gamma) {
  return {{{ clampPixelFloat(e_gamma.y + kP3Cr * e_gamma.v),
             clampPixelFloat(e_gamma.y - kP3GCb * e_gamma.u - kP3GCr * e_gamma.v),
             clampPixelFloat(e_gamma.y + kP3Cb * e_gamma.u) }}};
}


////////////////////////////////////////////////////////////////////////////////
// BT.2100 transformations - according to ITU-R BT.2100-2
@@ -197,6 +227,8 @@ float bt2100Luminance(Color e) {
}

// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
// BT.2100 uses the same coefficients for calculating luma signal and luminance,
// so we reuse the luminance function here.
static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f;

Color bt2100RgbToYuv(Color e_gamma) {
@@ -206,6 +238,10 @@ Color bt2100RgbToYuv(Color e_gamma) {
             (e_gamma.r - y_gamma) / kBt2100Cr }}};
}

// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
//
// Similar to bt2100RgbToYuv above, we can reuse the luminance coefficients.
//
// Derived by inversing bt2100RgbToYuv. The derivation for R and B are  pretty
// straight forward; we just invert the formulas for U and V above. But deriving
// the formula for G is a bit more complicated:
@@ -440,6 +476,85 @@ ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut,
  }
}

// All of these conversions are derived from the respective input YUV->RGB conversion followed by
// the RGB->YUV for the receiving encoding. They are consistent with the RGB<->YUV functions in this
// file, given that we uses BT.709 encoding for sRGB and BT.601 encoding for Display-P3, to match
// DataSpace.

Color yuv709To601(Color e_gamma) {
  return {{{ 1.0f * e_gamma.y +  0.101579f * e_gamma.u +  0.196076f * e_gamma.v,
             0.0f * e_gamma.y +  0.989854f * e_gamma.u + -0.110653f * e_gamma.v,
             0.0f * e_gamma.y + -0.072453f * e_gamma.u +  0.983398f * e_gamma.v }}};
}

Color yuv709To2100(Color e_gamma) {
  return {{{ 1.0f * e_gamma.y + -0.016969f * e_gamma.u +  0.096312f * e_gamma.v,
             0.0f * e_gamma.y +  0.995306f * e_gamma.u + -0.051192f * e_gamma.v,
             0.0f * e_gamma.y +  0.011507f * e_gamma.u +  1.002637f * e_gamma.v }}};
}

Color yuv601To709(Color e_gamma) {
  return {{{ 1.0f * e_gamma.y + -0.118188f * e_gamma.u + -0.212685f * e_gamma.v,
             0.0f * e_gamma.y +  1.018640f * e_gamma.u +  0.114618f * e_gamma.v,
             0.0f * e_gamma.y +  0.075049f * e_gamma.u +  1.025327f * e_gamma.v }}};
}

Color yuv601To2100(Color e_gamma) {
  return {{{ 1.0f * e_gamma.y + -0.128245f * e_gamma.u + -0.115879f * e_gamma.v,
             0.0f * e_gamma.y +  1.010016f * e_gamma.u +  0.061592f * e_gamma.v,
             0.0f * e_gamma.y +  0.086969f * e_gamma.u +  1.029350f * e_gamma.v }}};
}

Color yuv2100To709(Color e_gamma) {
  return {{{ 1.0f * e_gamma.y +  0.018149f * e_gamma.u + -0.095132f * e_gamma.v,
             0.0f * e_gamma.y +  1.004123f * e_gamma.u +  0.051267f * e_gamma.v,
             0.0f * e_gamma.y + -0.011524f * e_gamma.u +  0.996782f * e_gamma.v }}};
}

Color yuv2100To601(Color e_gamma) {
  return {{{ 1.0f * e_gamma.y +  0.117887f * e_gamma.u +  0.105521f * e_gamma.v,
             0.0f * e_gamma.y +  0.995211f * e_gamma.u + -0.059549f * e_gamma.v,
             0.0f * e_gamma.y + -0.084085f * e_gamma.u +  0.976518f * e_gamma.v }}};
}

void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma,
                     ColorTransformFn fn) {
  Color yuv1 = getYuv420Pixel(image, x_chroma * 2,     y_chroma * 2    );
  Color yuv2 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2    );
  Color yuv3 = getYuv420Pixel(image, x_chroma * 2,     y_chroma * 2 + 1);
  Color yuv4 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 + 1);

  yuv1 = fn(yuv1);
  yuv2 = fn(yuv2);
  yuv3 = fn(yuv3);
  yuv4 = fn(yuv4);

  Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f;

  size_t pixel_y1_idx =  x_chroma * 2      +  y_chroma * 2      * image->width;
  size_t pixel_y2_idx = (x_chroma * 2 + 1) +  y_chroma * 2      * image->width;
  size_t pixel_y3_idx =  x_chroma * 2      + (y_chroma * 2 + 1) * image->width;
  size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->width;

  uint8_t& y1_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y1_idx];
  uint8_t& y2_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y2_idx];
  uint8_t& y3_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y3_idx];
  uint8_t& y4_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y4_idx];

  size_t pixel_count = image->width * image->height;
  size_t pixel_uv_idx = x_chroma + y_chroma * (image->width / 2);

  uint8_t& u_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count + pixel_uv_idx];
  uint8_t& v_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count * 5 / 4 + pixel_uv_idx];

  y1_uint = static_cast<uint8_t>(floor(yuv1.y * 255.0f + 0.5f));
  y2_uint = static_cast<uint8_t>(floor(yuv2.y * 255.0f + 0.5f));
  y3_uint = static_cast<uint8_t>(floor(yuv3.y * 255.0f + 0.5f));
  y4_uint = static_cast<uint8_t>(floor(yuv4.y * 255.0f + 0.5f));

  u_uint = static_cast<uint8_t>(floor(new_uv.u * 255.0f + 128.0f + 0.5f));
  v_uint = static_cast<uint8_t>(floor(new_uv.v * 255.0f + 128.0f + 0.5f));
}

////////////////////////////////////////////////////////////////////////////////
// Gain map calculations
+94 −2
Original line number Diff line number Diff line
@@ -14,6 +14,10 @@
 * limitations under the License.
 */

#ifndef USE_BIG_ENDIAN
#define USE_BIG_ENDIAN true
#endif

#include <ultrahdr/icc.h>
#include <ultrahdr/gainmapmath.h>
#include <vector>
@@ -540,13 +544,21 @@ sp<DataStruct> IccHelper::writeIccProfile(ultrahdr_transfer_function tf,
    size_t tag_table_size = kICCTagTableEntrySize * tags.size();
    size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size;

    sp<DataStruct> dataStruct = sp<DataStruct>::make(profile_size + kICCIdentifierSize);

    // Write identifier, chunk count, and chunk ID
    if (!dataStruct->write(kICCIdentifier, sizeof(kICCIdentifier)) ||
        !dataStruct->write8(1) || !dataStruct->write8(1)) {
        ALOGE("writeIccProfile(): error in identifier");
        return dataStruct;
    }

    // Write the header.
    header.data_color_space = Endian_SwapBE32(Signature_RGB);
    header.pcs = Endian_SwapBE32(tf == ULTRAHDR_TF_PQ ? Signature_Lab : Signature_XYZ);
    header.size = Endian_SwapBE32(profile_size);
    header.tag_count = Endian_SwapBE32(tags.size());

    sp<DataStruct> dataStruct = sp<DataStruct>::make(profile_size);
    if (!dataStruct->write(&header, sizeof(header))) {
        ALOGE("writeIccProfile(): error in header");
        return dataStruct;
@@ -582,4 +594,84 @@ sp<DataStruct> IccHelper::writeIccProfile(ultrahdr_transfer_function tf,
    return dataStruct;
}

bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix,
                                  const uint8_t* red_tag,
                                  const uint8_t* green_tag,
                                  const uint8_t* blue_tag) {
    sp<DataStruct> red_tag_test = write_xyz_tag(matrix.vals[0][0], matrix.vals[1][0],
                                                matrix.vals[2][0]);
    sp<DataStruct> green_tag_test = write_xyz_tag(matrix.vals[0][1], matrix.vals[1][1],
                                                  matrix.vals[2][1]);
    sp<DataStruct> blue_tag_test = write_xyz_tag(matrix.vals[0][2], matrix.vals[1][2],
                                                 matrix.vals[2][2]);
    return memcmp(red_tag, red_tag_test->getData(), kColorantTagSize) == 0 &&
           memcmp(green_tag, green_tag_test->getData(), kColorantTagSize) == 0 &&
           memcmp(blue_tag, blue_tag_test->getData(), kColorantTagSize) == 0;
}

ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_size) {
    // Each tag table entry consists of 3 fields of 4 bytes each.
    static const size_t kTagTableEntrySize = 12;

    if (icc_data == nullptr || icc_size < sizeof(ICCHeader) + kICCIdentifierSize) {
        return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
    }

    if (memcmp(icc_data, kICCIdentifier, sizeof(kICCIdentifier)) != 0) {
        return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
    }

    uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc_data) + kICCIdentifierSize;

    ICCHeader* header = reinterpret_cast<ICCHeader*>(icc_bytes);

    // Use 0 to indicate not found, since offsets are always relative to start
    // of ICC data and therefore a tag offset of zero would never be valid.
    size_t red_primary_offset = 0, green_primary_offset = 0, blue_primary_offset = 0;
    size_t red_primary_size = 0, green_primary_size = 0, blue_primary_size = 0;
    for (size_t tag_idx = 0; tag_idx < Endian_SwapBE32(header->tag_count); ++tag_idx) {
        uint32_t* tag_entry_start = reinterpret_cast<uint32_t*>(
            icc_bytes + sizeof(ICCHeader) + tag_idx * kTagTableEntrySize);
        // first 4 bytes are the tag signature, next 4 bytes are the tag offset,
        // last 4 bytes are the tag length in bytes.
        if (red_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_rXYZ)) {
            red_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
            red_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
        } else if (green_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_gXYZ)) {
            green_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
            green_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
        } else if (blue_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_bXYZ)) {
            blue_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
            blue_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
        }
    }

    if (red_primary_offset == 0 || red_primary_size != kColorantTagSize ||
        kICCIdentifierSize + red_primary_offset + red_primary_size > icc_size ||
        green_primary_offset == 0 || green_primary_size != kColorantTagSize ||
        kICCIdentifierSize + green_primary_offset + green_primary_size > icc_size ||
        blue_primary_offset == 0 || blue_primary_size != kColorantTagSize ||
        kICCIdentifierSize + blue_primary_offset + blue_primary_size > icc_size) {
        return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
    }

    uint8_t* red_tag = icc_bytes + red_primary_offset;
    uint8_t* green_tag = icc_bytes + green_primary_offset;
    uint8_t* blue_tag = icc_bytes + blue_primary_offset;

    // Serialize tags as we do on encode and compare what we find to that to
    // determine the gamut (since we don't have a need yet for full deserialize).
    if (tagsEqualToMatrix(kSRGB, red_tag, green_tag, blue_tag)) {
        return ULTRAHDR_COLORGAMUT_BT709;
    } else if (tagsEqualToMatrix(kDisplayP3, red_tag, green_tag, blue_tag)) {
        return ULTRAHDR_COLORGAMUT_P3;
    } else if (tagsEqualToMatrix(kRec2020, red_tag, green_tag, blue_tag)) {
        return ULTRAHDR_COLORGAMUT_BT2100;
    }

    // Didn't find a match to one of the profiles we write; indicate the gamut
    // is unspecified since we don't understand it.
    return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
}

} // namespace android::ultrahdr
+57 −8
Original line number Diff line number Diff line
@@ -218,24 +218,30 @@ struct ShepardsIDW {
// except for those concerning transfer functions.

/*
 * Calculate the luminance of a linear RGB sRGB pixel, according to IEC 61966-2-1.
 * Calculate the luminance of a linear RGB sRGB pixel, according to
 * IEC 61966-2-1/Amd 1:2003.
 *
 * [0.0, 1.0] range in and out.
 */
float srgbLuminance(Color e);

/*
 * Convert from OETF'd srgb YUV to RGB, according to ECMA TR/98.
 * Convert from OETF'd srgb RGB to YUV, according to ITU-R BT.709-6.
 *
 * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
 */
Color srgbYuvToRgb(Color e_gamma);
Color srgbRgbToYuv(Color e_gamma);


/*
 * Convert from OETF'd srgb RGB to YUV, according to ECMA TR/98.
 * Convert from OETF'd srgb YUV to RGB, according to ITU-R BT.709-6.
 *
 * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
 */
Color srgbRgbToYuv(Color e_gamma);
Color srgbYuvToRgb(Color e_gamma);

/*
 * Convert from srgb to linear, according to IEC 61966-2-1.
 * Convert from srgb to linear, according to IEC 61966-2-1/Amd 1:2003.
 *
 * [0.0, 1.0] range in and out.
 */
@@ -257,6 +263,20 @@ constexpr size_t kSrgbInvOETFNumEntries = 1 << kSrgbInvOETFPrecision;
 */
float p3Luminance(Color e);

/*
 * Convert from OETF'd P3 RGB to YUV, according to ITU-R BT.601-7.
 *
 * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
 */
Color p3RgbToYuv(Color e_gamma);

/*
 * Convert from OETF'd P3 YUV to RGB, according to ITU-R BT.601-7.
 *
 * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
 */
Color p3YuvToRgb(Color e_gamma);


////////////////////////////////////////////////////////////////////////////////
// BT.2100 transformations - according to ITU-R BT.2100-2
@@ -269,12 +289,16 @@ float p3Luminance(Color e);
float bt2100Luminance(Color e);

/*
 * Convert from OETF'd BT.2100 RGB to YUV.
 * Convert from OETF'd BT.2100 RGB to YUV, according to ITU-R BT.2100-2.
 *
 * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
 */
Color bt2100RgbToYuv(Color e_gamma);

/*
 * Convert from OETF'd BT.2100 YUV to RGB.
 * Convert from OETF'd BT.2100 YUV to RGB, according to ITU-R BT.2100-2.
 *
 * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
 */
Color bt2100YuvToRgb(Color e_gamma);

@@ -358,6 +382,31 @@ inline Color identityConversion(Color e) { return e; }
 */
ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, ultrahdr_color_gamut hdr_gamut);

/*
 * Convert between YUV encodings, according to ITU-R BT.709-6, ITU-R BT.601-7, and ITU-R BT.2100-2.
 *
 * Bt.709 and Bt.2100 have well-defined YUV encodings; Display-P3's is less well defined, but is
 * treated as Bt.601 by DataSpace, hence we do the same.
 */
Color yuv709To601(Color e_gamma);
Color yuv709To2100(Color e_gamma);
Color yuv601To709(Color e_gamma);
Color yuv601To2100(Color e_gamma);
Color yuv2100To709(Color e_gamma);
Color yuv2100To601(Color e_gamma);

/*
 * Performs a transformation at the chroma x and y coordinates provided on a YUV420 image.
 *
 * Apply the transformation by determining transformed YUV for each of the 4 Y + 1 UV; each Y gets
 * this result, and UV gets the averaged result.
 *
 * x_chroma and y_chroma should be less than or equal to half the image's width and height
 * respecitively, since input is 4:2:0 subsampled.
 */
void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma,
                     ColorTransformFn fn);


////////////////////////////////////////////////////////////////////////////////
// Gain map calculations
+23 −2
Original line number Diff line number Diff line
@@ -56,12 +56,16 @@ enum {
    Signature_XYZ  = 0x58595A20,
};


typedef uint32_t FourByteTag;
static inline constexpr FourByteTag SetFourByteTag(char a, char b, char c, char d) {
    return (((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | (uint32_t)d);
}

static constexpr char kICCIdentifier[] = "ICC_PROFILE";
// 12 for the actual identifier, +2 for the chunk count and chunk index which
// will always follow.
static constexpr size_t kICCIdentifierSize = 14;

// This is equal to the header size according to the ICC specification (128)
// plus the size of the tag count (4).  We include the tag count since we
// always require it to be present anyway.
@@ -70,6 +74,10 @@ static constexpr size_t kICCHeaderSize = 132;
// Contains a signature (4), offset (4), and size (4).
static constexpr size_t kICCTagTableEntrySize = 12;

// size should be 20; 4 bytes for type descriptor, 4 bytes reserved, 12
// bytes for a single XYZ number type (4 bytes per coordinate).
static constexpr size_t kColorantTagSize = 20;

static constexpr uint32_t kDisplay_Profile    = SetFourByteTag('m', 'n', 't', 'r');
static constexpr uint32_t kRGB_ColorSpace     = SetFourByteTag('R', 'G', 'B', ' ');
static constexpr uint32_t kXYZ_PCSSpace       = SetFourByteTag('X', 'Y', 'Z', ' ');
@@ -225,9 +233,22 @@ private:
    static void compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]);
    static sp<DataStruct> write_clut(const uint8_t* grid_points, const uint8_t* grid_16);

    // Checks if a set of xyz tags is equivalent to a 3x3 Matrix. Each input
    // tag buffer assumed to be at least kColorantTagSize in size.
    static bool tagsEqualToMatrix(const Matrix3x3& matrix,
                                  const uint8_t* red_tag,
                                  const uint8_t* green_tag,
                                  const uint8_t* blue_tag);

public:
    // Output includes JPEG embedding identifier and chunk information, but not
    // APPx information.
    static sp<DataStruct> writeIccProfile(const ultrahdr_transfer_function tf,
                                          const ultrahdr_color_gamut gamut);
    // NOTE: this function is not robust; it can infer gamuts that IccHelper
    // writes out but should not be considered a reference implementation for
    // robust parsing of ICC profiles or their gamuts.
    static ultrahdr_color_gamut readIccColorGamut(void* icc_data, size_t icc_size);
};
}  // namespace android::ultrahdr

+9 −6
Original line number Diff line number Diff line
@@ -83,11 +83,14 @@ public:
     */
    size_t getEXIFSize();
    /*
     * Returns the position offset of EXIF package
     * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>),
     * or -1  if no EXIF exists.
     * Returns the ICC data from the image.
     */
    int getEXIFPos() { return mExifPos; }
    void* getICCPtr();
    /*
     * Returns the decompressed ICC buffer size. This method must be called only after
     * calling decompressImage() or getCompressedImageParameters().
     */
    size_t getICCSize();
    /*
     * Decompresses metadata of the image. All vectors are owned by the caller.
     */
@@ -112,12 +115,12 @@ private:
    std::vector<JOCTET> mXMPBuffer;
    // The buffer that holds EXIF Data.
    std::vector<JOCTET> mEXIFBuffer;
    // The buffer that holds ICC Data.
    std::vector<JOCTET> mICCBuffer;

    // Resolution of the decompressed image.
    size_t mWidth;
    size_t mHeight;
    // Position of EXIF package, default value is -1 which means no EXIF package appears.
    size_t mExifPos;
};
} /* namespace android::ultrahdr  */

Loading