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

Commit e817e7b2 authored by Dichen Zhang's avatar Dichen Zhang Committed by Android (Google) Code Review
Browse files

Merge changes I6a13438a,I36f9d563

* changes:
  libjpegrecoverymap: add JPEG encoder for single channel image
  libjpegrecoverymap: add jpeg encoder with YUV input
parents 2f749438 a4819140
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -36,3 +36,17 @@ cc_library_static {
        "libutils",
    ],
}

cc_library_static {
    name: "libjpegencoder",

    shared_libs: [
        "libjpeg",
    ],

    export_include_dirs: ["include"],

    srcs: [
        "jpegencoder.cpp",
    ],
}
 No newline at end of file
+90 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
#include <cstdio>

extern "C" {
#include <jerror.h>
#include <jpeglib.h>
}

#include <utils/Errors.h>
#include <vector>

namespace android::recoverymap {

/*
 * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
 * This class is not thread-safe.
 */
class JpegEncoder {
public:
    JpegEncoder();
    ~JpegEncoder();

    /*
     * Compresses YUV420Planer image to JPEG format. After calling this method, call
     * getCompressedImage() to get the image. |quality| is the jpeg image quality parameter to use.
     * It ranges from 1 (poorest quality) to 100 (highest quality). |iccBuffer| is the buffer of
     * ICC segment which will be added to the compressed image.
     * Returns false if errors occur during compression.
     */
    bool compressImage(const void* image, int width, int height, int quality,
                       const void* iccBuffer, unsigned int iccSize, bool isSingleChannel = false);

    /*
     * Returns the compressed JPEG buffer pointer. This method must be called only after calling
     * compressImage().
     */
    const void* getCompressedImagePtr();

    /*
     * Returns the compressed JPEG buffer size. This method must be called only after calling
     * compressImage().
     */
    size_t getCompressedImageSize();

private:
    // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be
    // passed into jpeg library.
    static void initDestination(j_compress_ptr cinfo);
    static boolean emptyOutputBuffer(j_compress_ptr cinfo);
    static void terminateDestination(j_compress_ptr cinfo);
    static void outputErrorMessage(j_common_ptr cinfo);

    // Returns false if errors occur.
    bool encode(const void* inYuv, int width, int height, int jpegQuality,
                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,
                               bool isSingleChannel);
    // Returns false if errors occur.
    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;
    // 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<JOCTET> mResultBuffer;
};

} /* namespace android  */
 No newline at end of file
+239 −0
Original line number Diff line number Diff line
/*
 * 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 <jpegrecoverymap/jpegencoder.h>

#include <cutils/log.h>

#include <errno.h>

namespace android::recoverymap {

// The destination manager that can access |mResultBuffer| in JpegEncoder.
struct destination_mgr {
public:
    struct jpeg_destination_mgr mgr;
    JpegEncoder* encoder;
};

JpegEncoder::JpegEncoder() {
}

JpegEncoder::~JpegEncoder() {
}

bool JpegEncoder::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;
}

const void* JpegEncoder::getCompressedImagePtr() {
    return mResultBuffer.data();
}

size_t JpegEncoder::getCompressedImageSize() {
    return mResultBuffer.size();
}

void JpegEncoder::initDestination(j_compress_ptr cinfo) {
    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
    buffer.resize(kBlockSize);
    dest->mgr.next_output_byte = &buffer[0];
    dest->mgr.free_in_buffer = buffer.size();
}

boolean JpegEncoder::emptyOutputBuffer(j_compress_ptr cinfo) {
    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
    std::vector<JOCTET>& 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 JpegEncoder::terminateDestination(j_compress_ptr cinfo) {
    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
    buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
}

void JpegEncoder::outputErrorMessage(j_common_ptr cinfo) {
    char buffer[JMSG_LENGTH_MAX];

    /* Create the message */
    (*cinfo->err->format_message) (cinfo, buffer);
    ALOGE("%s\n", buffer);
}

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;

    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<const JOCTET*>(iccBuffer), iccSize);
    }

    if (!compress(&cinfo, static_cast<const uint8_t*>(image), isSingleChannel)) {
        return false;
    }
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    return true;
}

void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) {
    destination_mgr* dest = static_cast<struct destination_mgr *>((*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<struct jpeg_destination_mgr*>(dest);
}

void JpegEncoder::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 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];
    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<uint8_t*>(yuv);
    uint8_t* u_plane = const_cast<uint8_t*>(yuv + y_plane_size);
    uint8_t* v_plane = const_cast<uint8_t*>(yuv + y_plane_size + uv_plane_size);
    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();
            }
        }
        // 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 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
+16 −0
Original line number Diff line number Diff line
@@ -31,3 +31,19 @@ cc_test {
        "libjpegrecoverymap",
    ],
}

cc_test {
    name: "libjpegencoder_test",
    test_suites: ["device-tests"],
    srcs: [
        "jpegencoder_test.cpp",
    ],
    shared_libs: [
        "libjpeg",
        "liblog",
    ],
    static_libs: [
        "libjpegencoder",
        "libgtest",
    ],
}
 No newline at end of file
+1930 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading