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

Commit 6b3cd9ac authored by Dichen Zhang's avatar Dichen Zhang
Browse files

UltraHDR: reorder MPF and EXIF

For encode API-2, 3 and 4 where input has a JPEG, we always want
the EXIF package appears in front of MPF.

This change also fixed "double null terminator" string problem in
the decoder helper.

Bug: 292561235
Test: jpegr_test.cpp
Change-Id: If6890cc499e179ba01c4e7775796111edb7a5d2f
parent ca78def7
Loading
Loading
Loading
Loading
+16 −1
Original line number Diff line number Diff line
@@ -75,15 +75,27 @@ public:
     * calling decompressImage() or getCompressedImageParameters().
     */
    size_t getXMPSize();
    /*
     * Extracts EXIF package and updates the EXIF position / length without decoding the image.
     */
    bool extractEXIF(const void* image, int length);
    /*
     * Returns the EXIF data from the image.
     * This method must be called after extractEXIF() or decompressImage().
     */
    void* getEXIFPtr();
    /*
     * Returns the decompressed EXIF buffer size. This method must be called only after
     * calling decompressImage() or getCompressedImageParameters().
     * calling decompressImage(), extractEXIF() or getCompressedImageParameters().
     */
    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.
     * This method must be called after extractEXIF() or decompressImage().
     */
    int getEXIFPos() { return mExifPos; }
    /*
     * Returns the ICC data from the image.
     */
@@ -122,6 +134,9 @@ private:
    // 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  */

+9 −7
Original line number Diff line number Diff line
@@ -366,22 +366,24 @@ private:
     * the compressed gain map and optionally the exif package as inputs, and generate the XMP
     * metadata, and finally append everything in the order of:
     *     SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map
     * Note that EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and
     * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as
     * the input JPEG has EXIF.
     *

     * Note that in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following
     * conditions is fulfilled:
     *  (1) EXIF package is available from outside input. I.e. pExif != nullptr.
     *  (2) Input JPEG has EXIF.
     * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE
     *
     * @param primary_jpg_image_ptr destination of primary image
     * @param gainmap_jpg_image_ptr destination of compressed gain map image
     * @param (nullable) exif EXIF package
     * @param (nullable) icc ICC package
     * @param (nullable) pExif EXIF package
     * @param (nullable) pIcc ICC package
     * @param icc_size length in bytes of ICC package
     * @param metadata JPEG/R metadata to encode in XMP of the jpeg
     * @param dest compressed JPEGR image
     * @return NO_ERROR if calculation succeeds, error code if error occurs.
     */
    status_t appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
                           jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr exif, void* icc,
                           jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc,
                           size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest);

    /*
+76 −14
Original line number Diff line number Diff line
@@ -32,12 +32,17 @@ const uint32_t kAPP0Marker = JPEG_APP0; // JFIF
const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC

const std::string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/";
const std::string kExifIdCode = "Exif";
constexpr uint32_t kICCMarkerHeaderSize = 14;
constexpr uint8_t kICCSig[] = {
        'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0',
};
constexpr uint8_t kXmpNameSpace[] = {
        'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e',
        '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0',
};
constexpr uint8_t kExifIdCode[] = {
        'E', 'x', 'i', 'f', '\0', '\0',
};

struct jpegr_source_mgr : jpeg_source_mgr {
    jpegr_source_mgr(const uint8_t* ptr, int len);
@@ -146,6 +151,58 @@ size_t JpegDecoderHelper::getDecompressedImageHeight() {
    return mHeight;
}

// Here we only handle the first EXIF package, and in theary EXIF (or JFIF) must be the first
// in the image file.
// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
// two bytes of package length which is stored in marker->original_length, and the real data
// which is stored in marker->data.
bool JpegDecoderHelper::extractEXIF(const void* image, int length) {
    jpeg_decompress_struct cinfo;
    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
    jpegrerror_mgr myerr;

    cinfo.err = jpeg_std_error(&myerr.pub);
    myerr.pub.error_exit = jpegrerror_exit;

    if (setjmp(myerr.setjmp_buffer)) {
        jpeg_destroy_decompress(&cinfo);
        return false;
    }
    jpeg_create_decompress(&cinfo);

    jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
    jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);

    cinfo.src = &mgr;
    jpeg_read_header(&cinfo, TRUE);

    size_t pos = 2;  // position after SOI
    for (jpeg_marker_struct* marker = cinfo.marker_list;
         marker;
         marker = marker->next) {

        pos += 4;
        pos += marker->original_length;

        if (marker->marker != kAPP1Marker) {
            continue;
        }

        const unsigned int len = marker->data_length;

        if (len > sizeof(kExifIdCode) &&
            !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
            mEXIFBuffer.resize(len, 0);
            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
            mExifPos = pos - marker->original_length;
            break;
        }
    }

    jpeg_destroy_decompress(&cinfo);
    return true;
}

bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
    bool status = true;
    jpeg_decompress_struct cinfo;
@@ -178,25 +235,31 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA)
    bool exifAppears = false;
    bool xmpAppears = false;
    bool iccAppears = false;
    size_t pos = 2;  // position after SOI
    for (jpeg_marker_struct* marker = cinfo.marker_list;
         marker && !(exifAppears && xmpAppears && iccAppears); marker = marker->next) {
         marker && !(exifAppears && xmpAppears && iccAppears);
         marker = marker->next) {
         pos += 4;
         pos += marker->original_length;
        if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) {
            continue;
        }
        const unsigned int len = marker->data_length;
        if (!xmpAppears && len > kXmpNameSpace.size() &&
            !strncmp(reinterpret_cast<const char*>(marker->data), kXmpNameSpace.c_str(),
                     kXmpNameSpace.size())) {
        if (!xmpAppears &&
            len > sizeof(kXmpNameSpace) &&
            !memcmp(marker->data, kXmpNameSpace, sizeof(kXmpNameSpace))) {
            mXMPBuffer.resize(len+1, 0);
            memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
            xmpAppears = true;
        } else if (!exifAppears && len > kExifIdCode.size() &&
                   !strncmp(reinterpret_cast<const char*>(marker->data), kExifIdCode.c_str(),
                            kExifIdCode.size())) {
        } else if (!exifAppears &&
                   len > sizeof(kExifIdCode) &&
                   !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
            mEXIFBuffer.resize(len, 0);
            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
            exifAppears = true;
        } else if (!iccAppears && len > sizeof(kICCSig) &&
            mExifPos = pos - marker->original_length;
        } else if (!iccAppears &&
                   len > sizeof(kICCSig) &&
                   !memcmp(marker->data, kICCSig, sizeof(kICCSig))) {
            mICCBuffer.resize(len, 0);
            memcpy(static_cast<void*>(mICCBuffer.data()), marker->data, len);
@@ -325,9 +388,8 @@ bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int leng
            }

            const unsigned int len = marker->data_length;
            if (len >= kExifIdCode.size() &&
                !strncmp(reinterpret_cast<const char*>(marker->data), kExifIdCode.c_str(),
                         kExifIdCode.size())) {
            if (len >= sizeof(kExifIdCode) &&
                !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
                exifData->resize(len, 0);
                memcpy(static_cast<void*>(exifData->data()), marker->data, len);
                exifAppears = true;
+68 −12
Original line number Diff line number Diff line
@@ -72,6 +72,32 @@ int GetCPUCoreCount() {
  return cpuCoreCount;
}

/*
 * Helper function copies the JPEG image from without EXIF.
 *
 * @param pDest destination of the data to be written.
 * @param pSource source of data being written.
 * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
 *                 (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
 * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
 */
static void copyJpegWithoutExif(jr_compressed_ptr pDest,
                                jr_compressed_ptr pSource,
                                size_t exif_pos,
                                size_t exif_size) {
  memcpy(pDest, pSource, sizeof(jpegr_compressed_struct));

  const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign
  pDest->length = pSource->length - exif_size - exif_offset;
  pDest->data = new uint8_t[pDest->length];
  std::unique_ptr<uint8_t[]> dest_data;
  dest_data.reset(reinterpret_cast<uint8_t*>(pDest->data));
  memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
  memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
         (uint8_t*)pSource->data + exif_pos + exif_size,
         pSource->length - exif_pos - exif_size);
}

status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
                                       jr_uncompressed_ptr yuv420_image_ptr,
                                       ultrahdr_transfer_function hdr_tf,
@@ -1152,7 +1178,8 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
// JPEG/R structure:
// SOI (ff d8)
//
// (Optional, only if EXIF package is from outside)
// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
// in the JPEG input (Encode API-2, API-3, API-4))
// APP1 (ff e1)
// 2 bytes of length (2 + length of exif package)
// EXIF package (this includes the first two bytes representing the package length)
@@ -1166,7 +1193,7 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
// 2 bytes of length
// MPF
//
// (Required) primary image (without the first two bytes (SOI), may have other packages)
// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
//
// SOI (ff d8)
//
@@ -1183,8 +1210,8 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
// Adobe XMP spec part 3 for XMP marker
// ICC v4.3 spec for ICC
status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
                              jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr exif, void* icc,
                              size_t icc_size, ultrahdr_metadata_ptr metadata,
                              jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif,
                              void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata,
                              jr_compressed_ptr dest) {
  if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
      dest == nullptr) {
@@ -1229,6 +1256,35 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
  // same as primary
  const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();

  // Check if EXIF package presents in the JPEG input.
  // If so, extract and remove the EXIF package.
  JpegDecoderHelper decoder;
  if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
    return ERROR_JPEGR_DECODE_ERROR;
  }
  jpegr_exif_struct exif_from_jpg;
  exif_from_jpg.data = nullptr;
  exif_from_jpg.length = 0;
  jpegr_compressed_struct new_jpg_image;
  new_jpg_image.data = nullptr;
  new_jpg_image.length = 0;
  if (decoder.getEXIFPos() != 0) {
    if (pExif != nullptr) {
      ALOGE("received EXIF from outside while the primary image already contains EXIF");
      return ERROR_JPEGR_INVALID_INPUT_TYPE;
    }
    copyJpegWithoutExif(&new_jpg_image,
                        primary_jpg_image_ptr,
                        decoder.getEXIFPos(),
                        decoder.getEXIFSize());
    exif_from_jpg.data = decoder.getEXIFPtr();
    exif_from_jpg.length = decoder.getEXIFSize();
    pExif = &exif_from_jpg;
  }

  jr_compressed_ptr final_primary_jpg_image_ptr =
          new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image;

  int pos = 0;
  // Begin primary image
  // Write SOI
@@ -1236,15 +1292,15 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));

  // Write EXIF
  if (exif != nullptr) {
    const int length = 2 + exif->length;
  if (pExif != nullptr) {
    const int length = 2 + pExif->length;
    const uint8_t lengthH = ((length >> 8) & 0xff);
    const uint8_t lengthL = (length & 0xff);
    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
    JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
    JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos));
  }

  // Prepare and write XMP
@@ -1261,7 +1317,7 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
  }

  // Write ICC
  if (icc != nullptr && icc_size > 0) {
  if (pIcc != nullptr && icc_size > 0) {
    const int length = icc_size + 2;
    const uint8_t lengthH = ((length >> 8) & 0xff);
    const uint8_t lengthL = (length & 0xff);
@@ -1269,7 +1325,7 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
    JPEGR_CHECK(Write(dest, icc, icc_size, pos));
    JPEGR_CHECK(Write(dest, pIcc, icc_size, pos));
  }

  // Prepare and write MPF
@@ -1277,7 +1333,7 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
    const int length = 2 + calculateMpfSize();
    const uint8_t lengthH = ((length >> 8) & 0xff);
    const uint8_t lengthL = (length & 0xff);
    int primary_image_size = pos + length + primary_jpg_image_ptr->length;
    int primary_image_size = pos + length + final_primary_jpg_image_ptr->length;
    // between APP2 + package size + signature
    // ff e2 00 58 4d 50 46 00
    // 2 + 2 + 4 = 8 (bytes)
@@ -1293,8 +1349,8 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
  }

  // Write primary image
  JPEGR_CHECK(Write(dest, (uint8_t*)primary_jpg_image_ptr->data + 2,
                    primary_jpg_image_ptr->length - 2, pos));
  JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
                    final_primary_jpg_image_ptr->length - 2, pos));
  // Finish primary image

  // Begin secondary image (gain map)