From dbceb0e260f708ed4a0aafb7deed2198b4590b39 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Fri, 14 Apr 2023 19:03:18 +0000 Subject: JPEG/R refactor: rename jpegrecoverymap library to ultrahdr Test: build Bug: b/264715926 Change-Id: I227fb5960f8fc7e13aae354bf77ec033850faf10 --- libs/ultrahdr/jpegencoderhelper.cpp | 239 ++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 libs/ultrahdr/jpegencoderhelper.cpp (limited to 'libs/ultrahdr/jpegencoderhelper.cpp') diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp new file mode 100644 index 0000000000..fc6e4d1e64 --- /dev/null +++ b/libs/ultrahdr/jpegencoderhelper.cpp @@ -0,0 +1,239 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include + +namespace android::ultrahdr { + +// The destination manager that can access |mResultBuffer| in JpegEncoderHelper. +struct destination_mgr { +public: + struct jpeg_destination_mgr mgr; + JpegEncoderHelper* encoder; +}; + +JpegEncoderHelper::JpegEncoderHelper() { +} + +JpegEncoderHelper::~JpegEncoderHelper() { +} + +bool JpegEncoderHelper::compressImage(const void* image, int width, int height, int quality, + const void* iccBuffer, unsigned int iccSize, + bool isSingleChannel) { + if (width % 8 != 0 || height % 2 != 0) { + ALOGE("Image size can not be handled: %dx%d", width, height); + return false; + } + + mResultBuffer.clear(); + if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) { + return false; + } + ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes", + (width * height * 12) / 8, width, height, mResultBuffer.size()); + return true; +} + +void* JpegEncoderHelper::getCompressedImagePtr() { + return mResultBuffer.data(); +} + +size_t JpegEncoderHelper::getCompressedImageSize() { + return mResultBuffer.size(); +} + +void JpegEncoderHelper::initDestination(j_compress_ptr cinfo) { + destination_mgr* dest = reinterpret_cast(cinfo->dest); + std::vector& buffer = dest->encoder->mResultBuffer; + buffer.resize(kBlockSize); + dest->mgr.next_output_byte = &buffer[0]; + dest->mgr.free_in_buffer = buffer.size(); +} + +boolean JpegEncoderHelper::emptyOutputBuffer(j_compress_ptr cinfo) { + destination_mgr* dest = reinterpret_cast(cinfo->dest); + std::vector& buffer = dest->encoder->mResultBuffer; + size_t oldsize = buffer.size(); + buffer.resize(oldsize + kBlockSize); + dest->mgr.next_output_byte = &buffer[oldsize]; + dest->mgr.free_in_buffer = kBlockSize; + return true; +} + +void JpegEncoderHelper::terminateDestination(j_compress_ptr cinfo) { + destination_mgr* dest = reinterpret_cast(cinfo->dest); + std::vector& buffer = dest->encoder->mResultBuffer; + buffer.resize(buffer.size() - dest->mgr.free_in_buffer); +} + +void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + ALOGE("%s\n", buffer); +} + +bool JpegEncoderHelper::encode(const void* image, int width, int height, int jpegQuality, + const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) { + jpeg_compress_struct cinfo; + jpeg_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + // Override output_message() to print error log with ALOGE(). + cinfo.err->output_message = &outputErrorMessage; + jpeg_create_compress(&cinfo); + setJpegDestination(&cinfo); + + setJpegCompressStruct(width, height, jpegQuality, &cinfo, isSingleChannel); + jpeg_start_compress(&cinfo, TRUE); + + if (iccBuffer != nullptr && iccSize > 0) { + jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast(iccBuffer), iccSize); + } + + if (!compress(&cinfo, static_cast(image), isSingleChannel)) { + return false; + } + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + return true; +} + +void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) { + destination_mgr* dest = static_cast((*cinfo->mem->alloc_small) ( + (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(destination_mgr))); + dest->encoder = this; + dest->mgr.init_destination = &initDestination; + dest->mgr.empty_output_buffer = &emptyOutputBuffer; + dest->mgr.term_destination = &terminateDestination; + cinfo->dest = reinterpret_cast(dest); +} + +void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality, + jpeg_compress_struct* cinfo, bool isSingleChannel) { + cinfo->image_width = width; + cinfo->image_height = height; + if (isSingleChannel) { + cinfo->input_components = 1; + cinfo->in_color_space = JCS_GRAYSCALE; + } else { + cinfo->input_components = 3; + cinfo->in_color_space = JCS_YCbCr; + } + jpeg_set_defaults(cinfo); + + jpeg_set_quality(cinfo, quality, TRUE); + jpeg_set_colorspace(cinfo, isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr); + cinfo->raw_data_in = TRUE; + cinfo->dct_method = JDCT_IFAST; + + if (!isSingleChannel) { + // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the + // source format is YUV420. + cinfo->comp_info[0].h_samp_factor = 2; + cinfo->comp_info[0].v_samp_factor = 2; + cinfo->comp_info[1].h_samp_factor = 1; + cinfo->comp_info[1].v_samp_factor = 1; + cinfo->comp_info[2].h_samp_factor = 1; + cinfo->comp_info[2].v_samp_factor = 1; + } +} + +bool JpegEncoderHelper::compress( + jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) { + if (isSingleChannel) { + return compressSingleChannel(cinfo, image); + } + return compressYuv(cinfo, image); +} + +bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) { + JSAMPROW y[kCompressBatchSize]; + JSAMPROW cb[kCompressBatchSize / 2]; + JSAMPROW cr[kCompressBatchSize / 2]; + JSAMPARRAY planes[3] {y, cb, cr}; + + size_t y_plane_size = cinfo->image_width * cinfo->image_height; + size_t uv_plane_size = y_plane_size / 4; + uint8_t* y_plane = const_cast(yuv); + uint8_t* u_plane = const_cast(yuv + y_plane_size); + uint8_t* v_plane = const_cast(yuv + y_plane_size + uv_plane_size); + std::unique_ptr empty(new uint8_t[cinfo->image_width]); + memset(empty.get(), 0, cinfo->image_width); + + while (cinfo->next_scanline < cinfo->image_height) { + for (int i = 0; i < kCompressBatchSize; ++i) { + size_t scanline = cinfo->next_scanline + i; + if (scanline < cinfo->image_height) { + y[i] = y_plane + scanline * cinfo->image_width; + } else { + y[i] = empty.get(); + } + } + // cb, cr only have half scanlines + for (int i = 0; i < kCompressBatchSize / 2; ++i) { + size_t scanline = cinfo->next_scanline / 2 + i; + if (scanline < cinfo->image_height / 2) { + int offset = scanline * (cinfo->image_width / 2); + cb[i] = u_plane + offset; + cr[i] = v_plane + offset; + } else { + cb[i] = cr[i] = empty.get(); + } + } + + int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); + if (processed != kCompressBatchSize) { + ALOGE("Number of processed lines does not equal input lines."); + return false; + } + } + return true; +} + +bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) { + JSAMPROW y[kCompressBatchSize]; + JSAMPARRAY planes[1] {y}; + + uint8_t* y_plane = const_cast(image); + std::unique_ptr empty(new uint8_t[cinfo->image_width]); + memset(empty.get(), 0, cinfo->image_width); + + while (cinfo->next_scanline < cinfo->image_height) { + for (int i = 0; i < kCompressBatchSize; ++i) { + size_t scanline = cinfo->next_scanline + i; + if (scanline < cinfo->image_height) { + y[i] = y_plane + scanline * cinfo->image_width; + } else { + y[i] = empty.get(); + } + } + int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); + if (processed != kCompressBatchSize / 2) { + ALOGE("Number of processed lines does not equal input lines."); + return false; + } + } + return true; +} + +} // namespace ultrahdr -- cgit v1.2.3-59-g8ed1b From 56a7d594d98466b21d5ec499b817d55874bb8063 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Fri, 14 Apr 2023 16:57:34 +0000 Subject: JPEG/R: lift the checking criteria for width 8-alignment JPEG/R library uses jpeg-turbo for JPEG encoding, which runs DCT transform on block size of 16x16 for luma, and 8x8 for chroma. The resolution in the bug report is not 16-aligned and it results in null pointer dereference for the last line in jpeg-turbo. The original checking for 8-alignment width was wrong (should check 16-alignment). jpeg-turbo has some edge case handling for this case, and it requires some extra room at the end of input. This change removed the checking criteria by adding a padding zero method. A reason size of the padding zeros is a CB block, which is 8x8, 64 bytes. Bug: 277982036 Test: CTS: ImageReaderTest#testJpegR, uint test: jpegrencoderhelper_test.cpp Change-Id: I1313a002db6d4bc63b32dc3dd3d6ccdf06779149 --- libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h | 8 +-- libs/ultrahdr/jpegencoderhelper.cpp | 5 -- libs/ultrahdr/jpegr.cpp | 25 +++++----- libs/ultrahdr/tests/jpegencoderhelper_test.cpp | 58 +++++++++++++--------- 4 files changed, 52 insertions(+), 44 deletions(-) (limited to 'libs/ultrahdr/jpegencoderhelper.cpp') diff --git a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h index ac0215559d..2c6778e299 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h +++ b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h @@ -61,6 +61,11 @@ public: */ size_t getCompressedImageSize(); + /* + * Process 16 lines of Y and 16 lines of U/V each time. + * We must pass at least 16 scanlines according to libjpeg documentation. + */ + static const int kCompressBatchSize = 16; private: // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be // passed into jpeg library. @@ -82,9 +87,6 @@ private: // The block size for encoded jpeg image buffer. static const int kBlockSize = 16384; - // Process 16 lines of Y and 16 lines of U/V each time. - // We must pass at least 16 scanlines according to libjpeg documentation. - static const int kCompressBatchSize = 16; // The buffer that holds the compressed result. std::vector mResultBuffer; diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp index fc6e4d1e64..10a763035f 100644 --- a/libs/ultrahdr/jpegencoderhelper.cpp +++ b/libs/ultrahdr/jpegencoderhelper.cpp @@ -38,11 +38,6 @@ JpegEncoderHelper::~JpegEncoderHelper() { bool JpegEncoderHelper::compressImage(const void* image, int width, int height, int quality, const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) { - if (width % 8 != 0 || height % 2 != 0) { - ALOGE("Image size can not be handled: %dx%d", width, height); - return false; - } - mResultBuffer.clear(); if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) { return false; diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index 8e1dc8c529..24d1911853 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -66,9 +66,12 @@ static const uint32_t kJpegrVersion = 1; // Map is quarter res / sixteenth size static const size_t kMapDimensionScaleFactor = 4; // JPEG block size. -// JPEG encoding / decoding will require 8 x 8 DCT transform. -// Width must be 8 dividable, and height must be 2 dividable. -static const size_t kJpegBlock = 8; +// JPEG encoding / decoding will require block based DCT transform 16 x 16 for luma, +// and 8 x 8 for chroma. +// Width must be 16 dividable for luma, and 8 dividable for chroma. +// If this criteria is not ficilitated, we will pad zeros based on the required block size. +static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize; +static const size_t kJpegBlockSquare = kJpegBlock * kJpegBlock; // JPEG compress quality (0 ~ 100) for gain map static const int kMapCompressQuality = 85; @@ -92,13 +95,6 @@ status_t JpegR::areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image, return ERROR_JPEGR_INVALID_NULL_PTR; } - if (uncompressed_p010_image->width % kJpegBlock != 0 - || uncompressed_p010_image->height % 2 != 0) { - ALOGE("Image size can not be handled: %dx%d.", - uncompressed_p010_image->width, uncompressed_p010_image->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (uncompressed_p010_image->luma_stride != 0 && uncompressed_p010_image->luma_stride < uncompressed_p010_image->width) { ALOGE("Image stride can not be smaller than width, stride=%d, width=%d", @@ -157,8 +153,13 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, metadata.version = kJpegrVersion; jpegr_uncompressed_struct uncompressed_yuv_420_image; - unique_ptr uncompressed_yuv_420_image_data = make_unique( - uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2); + size_t gain_map_length = uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2; + // Pad a pseudo chroma block (kJpegBlock / 2) x (kJpegBlock / 2) + // if width is not kJpegBlock aligned. + if (uncompressed_p010_image->width % kJpegBlock != 0) { + gain_map_length += kJpegBlockSquare / 4; + } + unique_ptr uncompressed_yuv_420_image_data = make_unique(gain_map_length); uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get(); JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image)); diff --git a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp index b9a2d84807..8f18ac0004 100644 --- a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp +++ b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp @@ -22,15 +22,15 @@ namespace android::ultrahdr { -#define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12" -#define VALID_IMAGE_WIDTH 320 -#define VALID_IMAGE_HEIGHT 240 +#define ALIGNED_IMAGE "/sdcard/Documents/minnie-320x240.yu12" +#define ALIGNED_IMAGE_WIDTH 320 +#define ALIGNED_IMAGE_HEIGHT 240 #define SINGLE_CHANNEL_IMAGE "/sdcard/Documents/minnie-320x240.y" -#define SINGLE_CHANNEL_IMAGE_WIDTH VALID_IMAGE_WIDTH -#define SINGLE_CHANNEL_IMAGE_HEIGHT VALID_IMAGE_HEIGHT -#define INVALID_SIZE_IMAGE "/sdcard/Documents/minnie-318x240.yu12" -#define INVALID_SIZE_IMAGE_WIDTH 318 -#define INVALID_SIZE_IMAGE_HEIGHT 240 +#define SINGLE_CHANNEL_IMAGE_WIDTH ALIGNED_IMAGE_WIDTH +#define SINGLE_CHANNEL_IMAGE_HEIGHT ALIGNED_IMAGE_HEIGHT +#define UNALIGNED_IMAGE "/sdcard/Documents/minnie-318x240.yu12" +#define UNALIGNED_IMAGE_WIDTH 318 +#define UNALIGNED_IMAGE_HEIGHT 240 #define JPEG_QUALITY 90 class JpegEncoderHelperTest : public testing::Test { @@ -46,7 +46,7 @@ protected: virtual void SetUp(); virtual void TearDown(); - Image mValidImage, mInvalidSizeImage, mSingleChannelImage; + Image mAlignedImage, mUnalignedImage, mSingleChannelImage; }; JpegEncoderHelperTest::JpegEncoderHelperTest() {} @@ -82,16 +82,16 @@ static bool loadFile(const char filename[], JpegEncoderHelperTest::Image* result } void JpegEncoderHelperTest::SetUp() { - if (!loadFile(VALID_IMAGE, &mValidImage)) { - FAIL() << "Load file " << VALID_IMAGE << " failed"; + if (!loadFile(ALIGNED_IMAGE, &mAlignedImage)) { + FAIL() << "Load file " << ALIGNED_IMAGE << " failed"; } - mValidImage.width = VALID_IMAGE_WIDTH; - mValidImage.height = VALID_IMAGE_HEIGHT; - if (!loadFile(INVALID_SIZE_IMAGE, &mInvalidSizeImage)) { - FAIL() << "Load file " << INVALID_SIZE_IMAGE << " failed"; + mAlignedImage.width = ALIGNED_IMAGE_WIDTH; + mAlignedImage.height = ALIGNED_IMAGE_HEIGHT; + if (!loadFile(UNALIGNED_IMAGE, &mUnalignedImage)) { + FAIL() << "Load file " << UNALIGNED_IMAGE << " failed"; } - mInvalidSizeImage.width = INVALID_SIZE_IMAGE_WIDTH; - mInvalidSizeImage.height = INVALID_SIZE_IMAGE_HEIGHT; + mUnalignedImage.width = UNALIGNED_IMAGE_WIDTH; + mUnalignedImage.height = UNALIGNED_IMAGE_HEIGHT; if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) { FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed"; } @@ -101,20 +101,30 @@ void JpegEncoderHelperTest::SetUp() { void JpegEncoderHelperTest::TearDown() {} -TEST_F(JpegEncoderHelperTest, validImage) { +TEST_F(JpegEncoderHelperTest, encodeAlignedImage) { JpegEncoderHelper encoder; - EXPECT_TRUE(encoder.compressImage(mValidImage.buffer.get(), mValidImage.width, - mValidImage.height, JPEG_QUALITY, NULL, 0)); + EXPECT_TRUE(encoder.compressImage(mAlignedImage.buffer.get(), mAlignedImage.width, + mAlignedImage.height, JPEG_QUALITY, NULL, 0)); ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } -TEST_F(JpegEncoderHelperTest, invalidSizeImage) { +// The width of the "unaligned" image is not 16-aligned, and will fail if encoded directly. +// Should pass with the padding zero method. +TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) { JpegEncoderHelper encoder; - EXPECT_FALSE(encoder.compressImage(mInvalidSizeImage.buffer.get(), mInvalidSizeImage.width, - mInvalidSizeImage.height, JPEG_QUALITY, NULL, 0)); + const size_t paddingZeroLength = JpegEncoderHelper::kCompressBatchSize + * JpegEncoderHelper::kCompressBatchSize / 4; + std::unique_ptr imageWithPaddingZeros( + new uint8_t[UNALIGNED_IMAGE_WIDTH * UNALIGNED_IMAGE_HEIGHT * 3 / 2 + + paddingZeroLength]); + memcpy(imageWithPaddingZeros.get(), mUnalignedImage.buffer.get(), + UNALIGNED_IMAGE_WIDTH * UNALIGNED_IMAGE_HEIGHT * 3 / 2); + EXPECT_TRUE(encoder.compressImage(imageWithPaddingZeros.get(), mUnalignedImage.width, + mUnalignedImage.height, JPEG_QUALITY, NULL, 0)); + ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } -TEST_F(JpegEncoderHelperTest, singleChannelImage) { +TEST_F(JpegEncoderHelperTest, encodeSingleChannelImage) { JpegEncoderHelper encoder; EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width, mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true)); -- cgit v1.2.3-59-g8ed1b From 4a72c19f8861c59f23bd075ac665fe49e3dd0a40 Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Mon, 22 May 2023 19:05:06 +0530 Subject: ultrahdr: compress image reads outside bounds for non aligned widths jpeg_write_raw_data() processes one MCU row per call, and thus one must pass buffer of at least max_v_samp_factor * DCTSIZE scanlines. The buffer must be large enough to hold the actual data plus padding to DCT-block boundaries. Bug: 284117683 Test: ./ultrahdr_enc_fuzzer Change-Id: I993773817bf3805463bb21bb977624d6c2d45a0b --- libs/ultrahdr/jpegencoderhelper.cpp | 67 ++++++++++++++++++++++++-- libs/ultrahdr/jpegr.cpp | 13 ++--- libs/ultrahdr/tests/jpegencoderhelper_test.cpp | 11 +---- 3 files changed, 69 insertions(+), 22 deletions(-) (limited to 'libs/ultrahdr/jpegencoderhelper.cpp') diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp index 10a763035f..ab2f8c7b5a 100644 --- a/libs/ultrahdr/jpegencoderhelper.cpp +++ b/libs/ultrahdr/jpegencoderhelper.cpp @@ -22,6 +22,8 @@ namespace android::ultrahdr { +#define ALIGNM(x, m) ((((x) + ((m) - 1)) / (m)) * (m)) + // The destination manager that can access |mResultBuffer| in JpegEncoderHelper. struct destination_mgr { public: @@ -175,6 +177,37 @@ bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* std::unique_ptr empty(new uint8_t[cinfo->image_width]); memset(empty.get(), 0, cinfo->image_width); + const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); + const bool is_width_aligned = (aligned_width == cinfo->image_width); + std::unique_ptr buffer_intrm = nullptr; + uint8_t* y_plane_intrm = nullptr; + uint8_t* u_plane_intrm = nullptr; + uint8_t* v_plane_intrm = nullptr; + JSAMPROW y_intrm[kCompressBatchSize]; + JSAMPROW cb_intrm[kCompressBatchSize / 2]; + JSAMPROW cr_intrm[kCompressBatchSize / 2]; + JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm}; + if (!is_width_aligned) { + size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2; + buffer_intrm = std::make_unique(mcu_row_size); + y_plane_intrm = buffer_intrm.get(); + u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize); + v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4; + for (int i = 0; i < kCompressBatchSize; ++i) { + y_intrm[i] = y_plane_intrm + i * aligned_width; + memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width); + } + for (int i = 0; i < kCompressBatchSize / 2; ++i) { + int offset_intrm = i * (aligned_width / 2); + cb_intrm[i] = u_plane_intrm + offset_intrm; + cr_intrm[i] = v_plane_intrm + offset_intrm; + memset(cb_intrm[i] + cinfo->image_width / 2, 0, + (aligned_width - cinfo->image_width) / 2); + memset(cr_intrm[i] + cinfo->image_width / 2, 0, + (aligned_width - cinfo->image_width) / 2); + } + } + while (cinfo->next_scanline < cinfo->image_height) { for (int i = 0; i < kCompressBatchSize; ++i) { size_t scanline = cinfo->next_scanline + i; @@ -183,6 +216,9 @@ bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* } else { y[i] = empty.get(); } + if (!is_width_aligned) { + memcpy(y_intrm[i], y[i], cinfo->image_width); + } } // cb, cr only have half scanlines for (int i = 0; i < kCompressBatchSize / 2; ++i) { @@ -194,9 +230,13 @@ bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* } else { cb[i] = cr[i] = empty.get(); } + if (!is_width_aligned) { + memcpy(cb_intrm[i], cb[i], cinfo->image_width / 2); + memcpy(cr_intrm[i], cr[i], cinfo->image_width / 2); + } } - - int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); + int processed = jpeg_write_raw_data(cinfo, is_width_aligned ? planes : planes_intrm, + kCompressBatchSize); if (processed != kCompressBatchSize) { ALOGE("Number of processed lines does not equal input lines."); return false; @@ -213,6 +253,23 @@ bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const std::unique_ptr empty(new uint8_t[cinfo->image_width]); memset(empty.get(), 0, cinfo->image_width); + const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); + bool is_width_aligned = (aligned_width == cinfo->image_width); + std::unique_ptr buffer_intrm = nullptr; + uint8_t* y_plane_intrm = nullptr; + uint8_t* u_plane_intrm = nullptr; + JSAMPROW y_intrm[kCompressBatchSize]; + JSAMPARRAY planes_intrm[]{y_intrm}; + if (!is_width_aligned) { + size_t mcu_row_size = aligned_width * kCompressBatchSize; + buffer_intrm = std::make_unique(mcu_row_size); + y_plane_intrm = buffer_intrm.get(); + for (int i = 0; i < kCompressBatchSize; ++i) { + y_intrm[i] = y_plane_intrm + i * aligned_width; + memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width); + } + } + while (cinfo->next_scanline < cinfo->image_height) { for (int i = 0; i < kCompressBatchSize; ++i) { size_t scanline = cinfo->next_scanline + i; @@ -221,8 +278,12 @@ bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const } else { y[i] = empty.get(); } + if (!is_width_aligned) { + memcpy(y_intrm[i], y[i], cinfo->image_width); + } } - int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); + int processed = jpeg_write_raw_data(cinfo, is_width_aligned ? planes : planes_intrm, + kCompressBatchSize); if (processed != kCompressBatchSize / 2) { ALOGE("Number of processed lines does not equal input lines."); return false; diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index c250aa02fd..9aadb74ca6 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -76,9 +76,9 @@ static const int kMinHeight = 2 * kMapDimensionScaleFactor; // JPEG encoding / decoding will require block based DCT transform 16 x 16 for luma, // and 8 x 8 for chroma. // Width must be 16 dividable for luma, and 8 dividable for chroma. -// If this criteria is not ficilitated, we will pad zeros based on the required block size. +// If this criteria is not facilitated, we will pad zeros based to each line on the +// required block size. static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize; -static const size_t kJpegBlockSquare = kJpegBlock * kJpegBlock; // JPEG compress quality (0 ~ 100) for gain map static const int kMapCompressQuality = 85; @@ -228,13 +228,8 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, metadata.version = kJpegrVersion; jpegr_uncompressed_struct uncompressed_yuv_420_image; - size_t gain_map_length = uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2; - // Pad a pseudo chroma block (kJpegBlock / 2) x (kJpegBlock / 2) - // if width is not kJpegBlock aligned. - if (uncompressed_p010_image->width % kJpegBlock != 0) { - gain_map_length += kJpegBlockSquare / 4; - } - unique_ptr uncompressed_yuv_420_image_data = make_unique(gain_map_length); + unique_ptr uncompressed_yuv_420_image_data = make_unique( + uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2); uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get(); JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image)); diff --git a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp index 8f18ac0004..f0e1fa4968 100644 --- a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp +++ b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp @@ -108,18 +108,9 @@ TEST_F(JpegEncoderHelperTest, encodeAlignedImage) { ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } -// The width of the "unaligned" image is not 16-aligned, and will fail if encoded directly. -// Should pass with the padding zero method. TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) { JpegEncoderHelper encoder; - const size_t paddingZeroLength = JpegEncoderHelper::kCompressBatchSize - * JpegEncoderHelper::kCompressBatchSize / 4; - std::unique_ptr imageWithPaddingZeros( - new uint8_t[UNALIGNED_IMAGE_WIDTH * UNALIGNED_IMAGE_HEIGHT * 3 / 2 - + paddingZeroLength]); - memcpy(imageWithPaddingZeros.get(), mUnalignedImage.buffer.get(), - UNALIGNED_IMAGE_WIDTH * UNALIGNED_IMAGE_HEIGHT * 3 / 2); - EXPECT_TRUE(encoder.compressImage(imageWithPaddingZeros.get(), mUnalignedImage.width, + EXPECT_TRUE(encoder.compressImage(mUnalignedImage.buffer.get(), mUnalignedImage.width, mUnalignedImage.height, JPEG_QUALITY, NULL, 0)); ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } -- cgit v1.2.3-59-g8ed1b From e69d9d2e7fde9dff70ba36dbc46b60bdd000cebb Mon Sep 17 00:00:00 2001 From: Ram Mohan Date: Fri, 2 Jun 2023 17:44:45 +0530 Subject: ultrahdr: release memory if encode/decode fails If calls to encode/decode failed, release the allocated memory before returning the control to caller Bug: 285546217 Test: ./ultrahdr_dec_fuzzer Test: ./ultrahdr_enc_fuzzer Change-Id: I276c31cc56656aa41845a16f5d28783bc3adc772 --- libs/ultrahdr/icc.cpp | 18 +++++++++--------- libs/ultrahdr/jpegdecoderhelper.cpp | 20 +++++++++++++------- libs/ultrahdr/jpegencoderhelper.cpp | 11 +++++------ libs/ultrahdr/multipictureformat.cpp | 2 +- 4 files changed, 28 insertions(+), 23 deletions(-) (limited to 'libs/ultrahdr/jpegencoderhelper.cpp') diff --git a/libs/ultrahdr/icc.cpp b/libs/ultrahdr/icc.cpp index c807705528..32d08aa525 100644 --- a/libs/ultrahdr/icc.cpp +++ b/libs/ultrahdr/icc.cpp @@ -180,7 +180,7 @@ sp IccHelper::write_text_tag(const char* text) { uint32_t total_length = text_length * 2 + sizeof(header); total_length = (((total_length + 2) >> 2) << 2); // 4 aligned - sp dataStruct = new DataStruct(total_length); + sp dataStruct = sp::make(total_length); if (!dataStruct->write(header, sizeof(header))) { ALOGE("write_text_tag(): error in writing data"); @@ -204,7 +204,7 @@ sp IccHelper::write_xyz_tag(float x, float y, float z) { static_cast(Endian_SwapBE32(float_round_to_fixed(y))), static_cast(Endian_SwapBE32(float_round_to_fixed(z))), }; - sp dataStruct = new DataStruct(sizeof(data)); + sp dataStruct = sp::make(sizeof(data)); dataStruct->write(&data, sizeof(data)); return dataStruct; } @@ -212,7 +212,7 @@ sp IccHelper::write_xyz_tag(float x, float y, float z) { sp IccHelper::write_trc_tag(const int table_entries, const void* table_16) { int total_length = 4 + 4 + 4 + table_entries * 2; total_length = (((total_length + 2) >> 2) << 2); // 4 aligned - sp dataStruct = new DataStruct(total_length); + sp dataStruct = sp::make(total_length); dataStruct->write32(Endian_SwapBE32(kTAG_CurveType)); // Type dataStruct->write32(0); // Reserved dataStruct->write32(Endian_SwapBE32(table_entries)); // Value count @@ -225,7 +225,7 @@ sp IccHelper::write_trc_tag(const int table_entries, const void* tab sp IccHelper::write_trc_tag_for_linear() { int total_length = 16; - sp dataStruct = new DataStruct(total_length); + sp dataStruct = sp::make(total_length); dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type dataStruct->write32(0); // Reserved dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType)); @@ -263,7 +263,7 @@ float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, floa sp IccHelper::write_cicp_tag(uint32_t color_primaries, uint32_t transfer_characteristics) { int total_length = 12; // 4 + 4 + 1 + 1 + 1 + 1 - sp dataStruct = new DataStruct(total_length); + sp dataStruct = sp::make(total_length); dataStruct->write32(Endian_SwapBE32(kTAG_cicp)); // Type signature dataStruct->write32(0); // Reserved dataStruct->write8(color_primaries); // Color primaries @@ -314,7 +314,7 @@ sp IccHelper::write_clut(const uint8_t* grid_points, const uint8_t* int total_length = 20 + 2 * value_count; total_length = (((total_length + 2) >> 2) << 2); // 4 aligned - sp dataStruct = new DataStruct(total_length); + sp dataStruct = sp::make(total_length); for (size_t i = 0; i < 16; ++i) { dataStruct->write8(i < kNumChannels ? grid_points[i] : 0); // Grid size @@ -372,7 +372,7 @@ sp IccHelper::write_mAB_or_mBA_tag(uint32_t type, total_length += a_curves_data[i]->getLength(); } } - sp dataStruct = new DataStruct(total_length); + sp dataStruct = sp::make(total_length); dataStruct->write32(Endian_SwapBE32(type)); // Type signature dataStruct->write32(0); // Reserved dataStruct->write8(kNumChannels); // Input channels @@ -421,7 +421,7 @@ sp IccHelper::writeIccProfile(ultrahdr_transfer_function tf, break; default: // Should not fall here. - return new DataStruct(0); + return nullptr; } // Compute primaries. @@ -546,7 +546,7 @@ sp IccHelper::writeIccProfile(ultrahdr_transfer_function tf, header.size = Endian_SwapBE32(profile_size); header.tag_count = Endian_SwapBE32(tags.size()); - sp dataStruct = new DataStruct(profile_size); + sp dataStruct = sp::make(profile_size); if (!dataStruct->write(&header, sizeof(header))) { ALOGE("writeIccProfile(): error in header"); return dataStruct; diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp index 2a9bc9ac1e..0bad4a4de0 100644 --- a/libs/ultrahdr/jpegdecoderhelper.cpp +++ b/libs/ultrahdr/jpegdecoderhelper.cpp @@ -150,6 +150,7 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) jpeg_decompress_struct cinfo; jpegr_source_mgr mgr(static_cast(image), length); jpegrerror_mgr myerr; + bool status = true; cinfo.err = jpeg_std_error(&myerr.pub); myerr.pub.error_exit = jpegrerror_exit; @@ -216,7 +217,8 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) if (cinfo.image_width > kMaxWidth || cinfo.image_height > kMaxHeight) { // constraint on max width and max height is only due to alloc constraints // tune these values basing on the target device - return false; + status = false; + goto CleanUp; } mWidth = cinfo.image_width; @@ -225,7 +227,8 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) if (decodeToRGBA) { if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { // We don't intend to support decoding grayscale to RGBA - return false; + status = false; + goto CleanUp; } // 4 bytes per pixel mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4); @@ -238,7 +241,8 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) cinfo.comp_info[0].v_samp_factor != 2 || cinfo.comp_info[1].v_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) { - return false; + status = false; + goto CleanUp; } mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0); } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { @@ -254,13 +258,15 @@ bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) if (!decompress(&cinfo, static_cast(mResultBuffer.data()), cinfo.jpeg_color_space == JCS_GRAYSCALE)) { - return false; + status = false; + goto CleanUp; } +CleanUp: jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); - return true; + return status; } bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, @@ -367,7 +373,7 @@ bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8 uint8_t* y_plane = const_cast(dest); uint8_t* u_plane = const_cast(dest + y_plane_size); uint8_t* v_plane = const_cast(dest + y_plane_size + uv_plane_size); - std::unique_ptr empty(new uint8_t[cinfo->image_width]); + std::unique_ptr empty = std::make_unique(cinfo->image_width); memset(empty.get(), 0, cinfo->image_width); const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); @@ -441,7 +447,7 @@ bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, c JSAMPARRAY planes[1] {y}; uint8_t* y_plane = const_cast(dest); - std::unique_ptr empty(new uint8_t[cinfo->image_width]); + std::unique_ptr empty = std::make_unique(cinfo->image_width); memset(empty.get(), 0, cinfo->image_width); int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp index ab2f8c7b5a..a03547b538 100644 --- a/libs/ultrahdr/jpegencoderhelper.cpp +++ b/libs/ultrahdr/jpegencoderhelper.cpp @@ -107,12 +107,11 @@ bool JpegEncoderHelper::encode(const void* image, int width, int height, int jpe jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast(iccBuffer), iccSize); } - if (!compress(&cinfo, static_cast(image), isSingleChannel)) { - return false; - } + bool status = compress(&cinfo, static_cast(image), isSingleChannel); jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); - return true; + + return status; } void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) { @@ -174,7 +173,7 @@ bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* uint8_t* y_plane = const_cast(yuv); uint8_t* u_plane = const_cast(yuv + y_plane_size); uint8_t* v_plane = const_cast(yuv + y_plane_size + uv_plane_size); - std::unique_ptr empty(new uint8_t[cinfo->image_width]); + std::unique_ptr empty = std::make_unique(cinfo->image_width); memset(empty.get(), 0, cinfo->image_width); const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); @@ -250,7 +249,7 @@ bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const JSAMPARRAY planes[1] {y}; uint8_t* y_plane = const_cast(image); - std::unique_ptr empty(new uint8_t[cinfo->image_width]); + std::unique_ptr empty = std::make_unique(cinfo->image_width); memset(empty.get(), 0, cinfo->image_width); const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); diff --git a/libs/ultrahdr/multipictureformat.cpp b/libs/ultrahdr/multipictureformat.cpp index 7a265c61b7..f1679ef1b3 100644 --- a/libs/ultrahdr/multipictureformat.cpp +++ b/libs/ultrahdr/multipictureformat.cpp @@ -30,7 +30,7 @@ size_t calculateMpfSize() { sp generateMpf(int primary_image_size, int primary_image_offset, int secondary_image_size, int secondary_image_offset) { size_t mpf_size = calculateMpfSize(); - sp dataStruct = new DataStruct(mpf_size); + sp dataStruct = sp::make(mpf_size); dataStruct->write(static_cast(kMpfSig), sizeof(kMpfSig)); #if USE_BIG_ENDIAN -- cgit v1.2.3-59-g8ed1b