diff options
Diffstat (limited to 'gfx/skia/skia/src/encode/SkJpegEncoder.cpp')
-rw-r--r-- | gfx/skia/skia/src/encode/SkJpegEncoder.cpp | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/encode/SkJpegEncoder.cpp b/gfx/skia/skia/src/encode/SkJpegEncoder.cpp new file mode 100644 index 0000000000..d764a52ebc --- /dev/null +++ b/gfx/skia/skia/src/encode/SkJpegEncoder.cpp @@ -0,0 +1,419 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" + +#ifdef SK_ENCODE_JPEG + +#include "include/core/SkAlphaType.h" +#include "include/core/SkColorType.h" +#include "include/core/SkData.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkPixmap.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkStream.h" +#include "include/core/SkYUVAInfo.h" +#include "include/core/SkYUVAPixmaps.h" +#include "include/encode/SkEncoder.h" +#include "include/encode/SkJpegEncoder.h" +#include "include/private/base/SkNoncopyable.h" +#include "include/private/base/SkTemplates.h" +#include "src/base/SkMSAN.h" +#include "src/codec/SkJpegConstants.h" +#include "src/codec/SkJpegPriv.h" +#include "src/encode/SkImageEncoderFns.h" +#include "src/encode/SkImageEncoderPriv.h" +#include "src/encode/SkJPEGWriteUtility.h" + +#include <csetjmp> +#include <cstdint> +#include <cstring> +#include <memory> +#include <utility> + +class SkColorSpace; + +extern "C" { + #include "jpeglib.h" + #include "jmorecfg.h" +} + +class SkJpegEncoderMgr final : SkNoncopyable { +public: + /* + * Create the decode manager + * Does not take ownership of stream. + */ + static std::unique_ptr<SkJpegEncoderMgr> Make(SkWStream* stream) { + return std::unique_ptr<SkJpegEncoderMgr>(new SkJpegEncoderMgr(stream)); + } + + bool setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options); + bool setParams(const SkYUVAPixmapInfo& srcInfo, const SkJpegEncoder::Options& options); + + jpeg_compress_struct* cinfo() { return &fCInfo; } + + skjpeg_error_mgr* errorMgr() { return &fErrMgr; } + + transform_scanline_proc proc() const { return fProc; } + + ~SkJpegEncoderMgr() { + jpeg_destroy_compress(&fCInfo); + } + +private: + SkJpegEncoderMgr(SkWStream* stream) : fDstMgr(stream), fProc(nullptr) { + fCInfo.err = jpeg_std_error(&fErrMgr); + fErrMgr.error_exit = skjpeg_error_exit; + jpeg_create_compress(&fCInfo); + fCInfo.dest = &fDstMgr; + } + + jpeg_compress_struct fCInfo; + skjpeg_error_mgr fErrMgr; + skjpeg_destination_mgr fDstMgr; + transform_scanline_proc fProc; +}; + +bool SkJpegEncoderMgr::setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options) +{ + auto chooseProc8888 = [&]() { + if (kUnpremul_SkAlphaType == srcInfo.alphaType() && + options.fAlphaOption == SkJpegEncoder::AlphaOption::kBlendOnBlack) { + return transform_scanline_to_premul_legacy; + } + return (transform_scanline_proc) nullptr; + }; + + J_COLOR_SPACE jpegColorType = JCS_EXT_RGBA; + int numComponents = 0; + switch (srcInfo.colorType()) { + case kRGBA_8888_SkColorType: + fProc = chooseProc8888(); + jpegColorType = JCS_EXT_RGBA; + numComponents = 4; + break; + case kBGRA_8888_SkColorType: + fProc = chooseProc8888(); + jpegColorType = JCS_EXT_BGRA; + numComponents = 4; + break; + case kRGB_565_SkColorType: + fProc = transform_scanline_565; + jpegColorType = JCS_RGB; + numComponents = 3; + break; + case kARGB_4444_SkColorType: + if (SkJpegEncoder::AlphaOption::kBlendOnBlack == options.fAlphaOption) { + return false; + } + + fProc = transform_scanline_444; + jpegColorType = JCS_RGB; + numComponents = 3; + break; + case kGray_8_SkColorType: + case kAlpha_8_SkColorType: + case kR8_unorm_SkColorType: + jpegColorType = JCS_GRAYSCALE; + numComponents = 1; + break; + case kRGBA_F16_SkColorType: + if (kUnpremul_SkAlphaType == srcInfo.alphaType() && + options.fAlphaOption == SkJpegEncoder::AlphaOption::kBlendOnBlack) { + fProc = transform_scanline_F16_to_premul_8888; + } else { + fProc = transform_scanline_F16_to_8888; + } + jpegColorType = JCS_EXT_RGBA; + numComponents = 4; + break; + default: + return false; + } + + fCInfo.image_width = srcInfo.width(); + fCInfo.image_height = srcInfo.height(); + fCInfo.in_color_space = jpegColorType; + fCInfo.input_components = numComponents; + jpeg_set_defaults(&fCInfo); + + if (numComponents != 1) { + switch (options.fDownsample) { + case SkJpegEncoder::Downsample::k420: + SkASSERT(2 == fCInfo.comp_info[0].h_samp_factor); + SkASSERT(2 == fCInfo.comp_info[0].v_samp_factor); + SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor); + SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor); + SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor); + SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor); + break; + case SkJpegEncoder::Downsample::k422: + fCInfo.comp_info[0].h_samp_factor = 2; + fCInfo.comp_info[0].v_samp_factor = 1; + SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor); + SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor); + SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor); + SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor); + break; + case SkJpegEncoder::Downsample::k444: + fCInfo.comp_info[0].h_samp_factor = 1; + fCInfo.comp_info[0].v_samp_factor = 1; + SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor); + SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor); + SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor); + SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor); + break; + } + } + + // Tells libjpeg-turbo to compute optimal Huffman coding tables + // for the image. This improves compression at the cost of + // slower encode performance. + fCInfo.optimize_coding = TRUE; + return true; +} + +// Convert a row of an SkYUVAPixmaps to a row of Y,U,V triples. +// TODO(ccameron): This is horribly inefficient. +static void yuva_copy_row(const SkYUVAPixmaps* src, int row, uint8_t* dst) { + int width = src->plane(0).width(); + switch (src->yuvaInfo().planeConfig()) { + case SkYUVAInfo::PlaneConfig::kY_U_V: { + auto [ssWidthU, ssHeightU] = src->yuvaInfo().planeSubsamplingFactors(1); + auto [ssWidthV, ssHeightV] = src->yuvaInfo().planeSubsamplingFactors(2); + const uint8_t* srcY = reinterpret_cast<const uint8_t*>(src->plane(0).addr(0, row)); + const uint8_t* srcU = + reinterpret_cast<const uint8_t*>(src->plane(1).addr(0, row / ssHeightU)); + const uint8_t* srcV = + reinterpret_cast<const uint8_t*>(src->plane(2).addr(0, row / ssHeightV)); + for (int col = 0; col < width; ++col) { + dst[3 * col + 0] = srcY[col]; + dst[3 * col + 1] = srcU[col / ssWidthU]; + dst[3 * col + 2] = srcV[col / ssWidthV]; + } + break; + } + case SkYUVAInfo::PlaneConfig::kY_UV: { + auto [ssWidthUV, ssHeightUV] = src->yuvaInfo().planeSubsamplingFactors(1); + const uint8_t* srcY = reinterpret_cast<const uint8_t*>(src->plane(0).addr(0, row)); + const uint8_t* srcUV = + reinterpret_cast<const uint8_t*>(src->plane(1).addr(0, row / ssHeightUV)); + for (int col = 0; col < width; ++col) { + dst[3 * col + 0] = srcY[col]; + dst[3 * col + 1] = srcUV[2 * (col / ssWidthUV) + 0]; + dst[3 * col + 2] = srcUV[2 * (col / ssWidthUV) + 1]; + } + break; + } + default: + break; + } +} + +bool SkJpegEncoderMgr::setParams(const SkYUVAPixmapInfo& srcInfo, + const SkJpegEncoder::Options& options) { + fCInfo.image_width = srcInfo.yuvaInfo().width(); + fCInfo.image_height = srcInfo.yuvaInfo().height(); + fCInfo.in_color_space = JCS_YCbCr; + fCInfo.input_components = 3; + jpeg_set_defaults(&fCInfo); + + // Support no color space conversion. + if (srcInfo.yuvColorSpace() != kJPEG_Full_SkYUVColorSpace) { + return false; + } + + // Support only 8-bit data. + switch (srcInfo.dataType()) { + case SkYUVAPixmapInfo::DataType::kUnorm8: + break; + default: + return false; + } + + // Support only Y,U,V and Y,UV configurations (they are the only ones supported by + // yuva_copy_row). + switch (srcInfo.yuvaInfo().planeConfig()) { + case SkYUVAInfo::PlaneConfig::kY_U_V: + case SkYUVAInfo::PlaneConfig::kY_UV: + break; + default: + return false; + } + + // Specify to the encoder to use the same subsampling as the input image. The U and V planes + // always have a sampling factor of 1. + auto [ssHoriz, ssVert] = SkYUVAInfo::SubsamplingFactors(srcInfo.yuvaInfo().subsampling()); + fCInfo.comp_info[0].h_samp_factor = ssHoriz; + fCInfo.comp_info[0].v_samp_factor = ssVert; + + fCInfo.optimize_coding = TRUE; + return true; +} + +std::unique_ptr<SkEncoder> SkJpegEncoder::Make(SkWStream* dst, + const SkPixmap& src, + const Options& options) { + return Make(dst, &src, nullptr, nullptr, options); +} + +std::unique_ptr<SkEncoder> SkJpegEncoder::Make(SkWStream* dst, + const SkYUVAPixmaps& src, + const SkColorSpace* srcColorSpace, + const Options& options) { + return Make(dst, nullptr, &src, srcColorSpace, options); +} + +std::unique_ptr<SkEncoder> SkJpegEncoder::Make(SkWStream* dst, + const SkPixmap* src, + const SkYUVAPixmaps* srcYUVA, + const SkColorSpace* srcYUVAColorSpace, + const Options& options) { + // Exactly one of |src| or |srcYUVA| should be specified. + if (srcYUVA) { + SkASSERT(!src); + if (!srcYUVA->isValid()) { + return nullptr; + } + } else { + SkASSERT(src); + if (!src || !SkPixmapIsValid(*src)) { + return nullptr; + } + } + + std::unique_ptr<SkJpegEncoderMgr> encoderMgr = SkJpegEncoderMgr::Make(dst); + + skjpeg_error_mgr::AutoPushJmpBuf jmp(encoderMgr->errorMgr()); + if (setjmp(jmp)) { + return nullptr; + } + + if (srcYUVA) { + if (!encoderMgr->setParams(srcYUVA->pixmapsInfo(), options)) { + return nullptr; + } + } else { + if (!encoderMgr->setParams(src->info(), options)) { + return nullptr; + } + } + + jpeg_set_quality(encoderMgr->cinfo(), options.fQuality, TRUE); + jpeg_start_compress(encoderMgr->cinfo(), TRUE); + + // Write XMP metadata. This will only write the standard XMP segment. + // TODO(ccameron): Split this into a standard and extended XMP segment if needed. + if (options.xmpMetadata) { + SkDynamicMemoryWStream s; + s.write(kXMPStandardSig, sizeof(kXMPStandardSig)); + s.write(options.xmpMetadata->data(), options.xmpMetadata->size()); + auto data = s.detachAsData(); + jpeg_write_marker(encoderMgr->cinfo(), kXMPMarker, data->bytes(), data->size()); + } + + // Write the ICC profile. + // TODO(ccameron): This limits ICC profile size to a single segment's parameters (less than + // 64k). Split larger profiles into more segments. + sk_sp<SkData> icc = icc_from_color_space(srcYUVA ? srcYUVAColorSpace : src->colorSpace(), + options.fICCProfile, + options.fICCProfileDescription); + if (icc) { + // Create a contiguous block of memory with the icc signature followed by the profile. + sk_sp<SkData> markerData = + SkData::MakeUninitialized(kICCMarkerHeaderSize + icc->size()); + uint8_t* ptr = (uint8_t*) markerData->writable_data(); + memcpy(ptr, kICCSig, sizeof(kICCSig)); + ptr += sizeof(kICCSig); + *ptr++ = 1; // This is the first marker. + *ptr++ = 1; // Out of one total markers. + memcpy(ptr, icc->data(), icc->size()); + + jpeg_write_marker(encoderMgr->cinfo(), kICCMarker, markerData->bytes(), markerData->size()); + } + + if (srcYUVA) { + return std::unique_ptr<SkJpegEncoder>(new SkJpegEncoder(std::move(encoderMgr), srcYUVA)); + } + return std::unique_ptr<SkJpegEncoder>(new SkJpegEncoder(std::move(encoderMgr), *src)); +} + +SkJpegEncoder::SkJpegEncoder(std::unique_ptr<SkJpegEncoderMgr> encoderMgr, const SkPixmap& src) + : INHERITED(src, + encoderMgr->proc() ? encoderMgr->cinfo()->input_components * src.width() : 0) + , fEncoderMgr(std::move(encoderMgr)) {} + +SkJpegEncoder::SkJpegEncoder(std::unique_ptr<SkJpegEncoderMgr> encoderMgr, const SkYUVAPixmaps* src) + : INHERITED(src->plane(0), encoderMgr->cinfo()->input_components * src->yuvaInfo().width()) + , fEncoderMgr(std::move(encoderMgr)) + , fSrcYUVA(src) {} + +SkJpegEncoder::~SkJpegEncoder() {} + +bool SkJpegEncoder::onEncodeRows(int numRows) { + skjpeg_error_mgr::AutoPushJmpBuf jmp(fEncoderMgr->errorMgr()); + if (setjmp(jmp)) { + return false; + } + + if (fSrcYUVA) { + // TODO(ccameron): Consider using jpeg_write_raw_data, to avoid having to re-pack the data. + for (int i = 0; i < numRows; i++) { + yuva_copy_row(fSrcYUVA, fCurrRow + i, fStorage.get()); + JSAMPLE* jpegSrcRow = fStorage.get(); + jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1); + } + } else { + const size_t srcBytes = SkColorTypeBytesPerPixel(fSrc.colorType()) * fSrc.width(); + const size_t jpegSrcBytes = fEncoderMgr->cinfo()->input_components * fSrc.width(); + const void* srcRow = fSrc.addr(0, fCurrRow); + for (int i = 0; i < numRows; i++) { + JSAMPLE* jpegSrcRow = (JSAMPLE*)srcRow; + if (fEncoderMgr->proc()) { + sk_msan_assert_initialized(srcRow, SkTAddOffset<const void>(srcRow, srcBytes)); + fEncoderMgr->proc()((char*)fStorage.get(), + (const char*)srcRow, + fSrc.width(), + fEncoderMgr->cinfo()->input_components); + jpegSrcRow = fStorage.get(); + sk_msan_assert_initialized(jpegSrcRow, + SkTAddOffset<const void>(jpegSrcRow, jpegSrcBytes)); + } else { + // Same as above, but this repetition allows determining whether a + // proc was used when msan asserts. + sk_msan_assert_initialized(jpegSrcRow, + SkTAddOffset<const void>(jpegSrcRow, jpegSrcBytes)); + } + + jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1); + srcRow = SkTAddOffset<const void>(srcRow, fSrc.rowBytes()); + } + } + + fCurrRow += numRows; + if (fCurrRow == fSrc.height()) { + jpeg_finish_compress(fEncoderMgr->cinfo()); + } + + return true; +} + +bool SkJpegEncoder::Encode(SkWStream* dst, const SkPixmap& src, const Options& options) { + auto encoder = SkJpegEncoder::Make(dst, src, options); + return encoder.get() && encoder->encodeRows(src.height()); +} + +bool SkJpegEncoder::Encode(SkWStream* dst, + const SkYUVAPixmaps& src, + const SkColorSpace* srcColorSpace, + const Options& options) { + auto encoder = SkJpegEncoder::Make(dst, src, srcColorSpace, options); + return encoder.get() && encoder->encodeRows(src.yuvaInfo().height()); +} + +#endif |