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

Commit a4819140 authored by Dichen Zhang's avatar Dichen Zhang
Browse files

libjpegrecoverymap: add JPEG encoder for single channel image

test: jpegencoder_test
bug: b/252835416
Change-Id: I6a13438ae4695569058645ad13c1182b7a626524
parent 4a66b3cd
Loading
Loading
Loading
Loading
+9 −5
Original line number Diff line number Diff line
@@ -28,7 +28,8 @@ extern "C" {
namespace android::recoverymap {

/*
 * Encapsulates a converter from YUV420Planer to JPEG format. This class is not thread-safe.
 * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
 * This class is not thread-safe.
 */
class JpegEncoder {
public:
@@ -43,7 +44,7 @@ public:
     * Returns false if errors occur during compression.
     */
    bool compressImage(const void* image, int width, int height, int quality,
                       const void* iccBuffer, unsigned int iccSize);
                       const void* iccBuffer, unsigned int iccSize, bool isSingleChannel = false);

    /*
     * Returns the compressed JPEG buffer pointer. This method must be called only after calling
@@ -67,11 +68,14 @@ private:

    // Returns false if errors occur.
    bool encode(const void* inYuv, int width, int height, int jpegQuality,
                const void* iccBuffer, unsigned int iccSize);
                const void* iccBuffer, unsigned int iccSize, bool isSingleChannel);
    void setJpegDestination(jpeg_compress_struct* cinfo);
    void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo);
    void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo,
                               bool isSingleChannel);
    // Returns false if errors occur.
    bool compress(jpeg_compress_struct* cinfo, const uint8_t* yuv);
    bool compress(jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel);
    bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv);
    bool compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image);

    // The block size for encoded jpeg image buffer.
    static const int kBlockSize = 16384;
+61 −19
Original line number Diff line number Diff line
@@ -36,14 +36,15 @@ JpegEncoder::~JpegEncoder() {
}

bool JpegEncoder::compressImage(const void* image, int width, int height, int quality,
                                   const void* iccBuffer, unsigned int iccSize) {
                                   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)) {
    if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) {
        return false;
    }
    ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes",
@@ -91,8 +92,8 @@ void JpegEncoder::outputErrorMessage(j_common_ptr cinfo) {
    ALOGE("%s\n", buffer);
}

bool JpegEncoder::encode(const void* inYuv, int width, int height, int jpegQuality,
                           const void* iccBuffer, unsigned int iccSize) {
bool JpegEncoder::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;

@@ -102,14 +103,14 @@ bool JpegEncoder::encode(const void* inYuv, int width, int height, int jpegQuali
    jpeg_create_compress(&cinfo);
    setJpegDestination(&cinfo);

    setJpegCompressStruct(width, height, jpegQuality, &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<const JOCTET*>(iccBuffer), iccSize);
    }

    if (!compress(&cinfo, static_cast<const uint8_t*>(inYuv))) {
    if (!compress(&cinfo, static_cast<const uint8_t*>(image), isSingleChannel)) {
        return false;
    }
    jpeg_finish_compress(&cinfo);
@@ -128,18 +129,24 @@ void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) {
}

void JpegEncoder::setJpegCompressStruct(int width, int height, int quality,
                                               jpeg_compress_struct* cinfo) {
                                        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, JCS_YCbCr);
    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;
@@ -149,8 +156,17 @@ void JpegEncoder::setJpegCompressStruct(int width, int height, int quality,
        cinfo->comp_info[2].h_samp_factor = 1;
        cinfo->comp_info[2].v_samp_factor = 1;
    }
}

bool JpegEncoder::compress(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
bool JpegEncoder::compress(
        jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) {
    if (isSingleChannel) {
        return compressSingleChannel(cinfo, image);
    }
    return compressYuv(cinfo, image);
}

bool JpegEncoder::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
    JSAMPROW y[kCompressBatchSize];
    JSAMPROW cb[kCompressBatchSize / 2];
    JSAMPROW cr[kCompressBatchSize / 2];
@@ -194,4 +210,30 @@ bool JpegEncoder::compress(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
    return true;
}

bool JpegEncoder::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) {
    JSAMPROW y[kCompressBatchSize];
    JSAMPARRAY planes[1] {y};

    uint8_t* y_plane = const_cast<uint8_t*>(image);
    std::unique_ptr<uint8_t[]> 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 android
 No newline at end of file
+1930 −0

File added.

Preview size limit exceeded, changes collapsed.

+16 −1
Original line number Diff line number Diff line
@@ -25,6 +25,9 @@ namespace android::recoverymap {
#define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12"
#define VALID_IMAGE_WIDTH 320
#define VALID_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
@@ -43,7 +46,7 @@ protected:
    virtual void SetUp();
    virtual void TearDown();

    Image mValidImage, mInvalidSizeImage;
    Image mValidImage, mInvalidSizeImage, mSingleChannelImage;
};

JpegEncoderTest::JpegEncoderTest() {}
@@ -89,6 +92,11 @@ void JpegEncoderTest::SetUp() {
    }
    mInvalidSizeImage.width = INVALID_SIZE_IMAGE_WIDTH;
    mInvalidSizeImage.height = INVALID_SIZE_IMAGE_HEIGHT;
    if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) {
        FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed";
    }
    mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH;
    mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT;
}

void JpegEncoderTest::TearDown() {}
@@ -106,5 +114,12 @@ TEST_F(JpegEncoderTest, invalidSizeImage) {
                                          mInvalidSizeImage.height, JPEG_QUALITY, NULL, 0));
}

TEST_F(JpegEncoderTest, singleChannelImage) {
    JpegEncoder encoder;
    EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width,
                                         mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true));
    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
}

}