diff options
Diffstat (limited to 'media/libjxl/src/lib/extras/enc/jpg.cc')
-rw-r--r-- | media/libjxl/src/lib/extras/enc/jpg.cc | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/media/libjxl/src/lib/extras/enc/jpg.cc b/media/libjxl/src/lib/extras/enc/jpg.cc new file mode 100644 index 0000000000..83b2895756 --- /dev/null +++ b/media/libjxl/src/lib/extras/enc/jpg.cc @@ -0,0 +1,239 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "lib/extras/enc/jpg.h" + +#include <jpeglib.h> +#include <setjmp.h> +#include <stdint.h> + +#include <algorithm> +#include <iterator> +#include <numeric> +#include <utility> +#include <vector> + +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/status.h" +#include "lib/jxl/color_encoding_internal.h" +#include "lib/jxl/common.h" +#include "lib/jxl/dec_external_image.h" +#include "lib/jxl/enc_color_management.h" +#include "lib/jxl/enc_image_bundle.h" +#include "lib/jxl/exif.h" +#include "lib/jxl/image.h" +#include "lib/jxl/image_bundle.h" +#include "lib/jxl/sanitizers.h" +#if JPEGXL_ENABLE_SJPEG +#include "sjpeg.h" +#endif + +namespace jxl { +namespace extras { + +namespace { + +constexpr unsigned char kICCSignature[12] = { + 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00}; +constexpr int kICCMarker = JPEG_APP0 + 2; +constexpr size_t kMaxBytesInMarker = 65533; + +constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69, + 0x66, 0x00, 0x00}; +constexpr int kExifMarker = JPEG_APP0 + 1; + +void WriteICCProfile(jpeg_compress_struct* const cinfo, + const PaddedBytes& icc) { + constexpr size_t kMaxIccBytesInMarker = + kMaxBytesInMarker - sizeof kICCSignature - 2; + const int num_markers = + static_cast<int>(DivCeil(icc.size(), kMaxIccBytesInMarker)); + size_t begin = 0; + for (int current_marker = 0; current_marker < num_markers; ++current_marker) { + const size_t length = std::min(kMaxIccBytesInMarker, icc.size() - begin); + jpeg_write_m_header( + cinfo, kICCMarker, + static_cast<unsigned int>(length + sizeof kICCSignature + 2)); + for (const unsigned char c : kICCSignature) { + jpeg_write_m_byte(cinfo, c); + } + jpeg_write_m_byte(cinfo, current_marker + 1); + jpeg_write_m_byte(cinfo, num_markers); + for (size_t i = 0; i < length; ++i) { + jpeg_write_m_byte(cinfo, icc[begin]); + ++begin; + } + } +} +void WriteExif(jpeg_compress_struct* const cinfo, + const std::vector<uint8_t>& exif) { + jpeg_write_m_header( + cinfo, kExifMarker, + static_cast<unsigned int>(exif.size() + sizeof kExifSignature)); + for (const unsigned char c : kExifSignature) { + jpeg_write_m_byte(cinfo, c); + } + for (size_t i = 0; i < exif.size(); ++i) { + jpeg_write_m_byte(cinfo, exif[i]); + } +} + +Status SetChromaSubsampling(const YCbCrChromaSubsampling& chroma_subsampling, + jpeg_compress_struct* const cinfo) { + for (size_t i = 0; i < 3; i++) { + cinfo->comp_info[i].h_samp_factor = + 1 << (chroma_subsampling.MaxHShift() - + chroma_subsampling.HShift(i < 2 ? i ^ 1 : i)); + cinfo->comp_info[i].v_samp_factor = + 1 << (chroma_subsampling.MaxVShift() - + chroma_subsampling.VShift(i < 2 ? i ^ 1 : i)); + } + return true; +} + +} // namespace + +Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io, + size_t quality, + const YCbCrChromaSubsampling& chroma_subsampling, + PaddedBytes* bytes) { + jpeg_compress_struct cinfo; + // cinfo is initialized by libjpeg, which we are not instrumenting with + // msan. + msan::UnpoisonMemory(&cinfo, sizeof(cinfo)); + jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + unsigned char* buffer = nullptr; + unsigned long size = 0; + jpeg_mem_dest(&cinfo, &buffer, &size); + cinfo.image_width = ib->oriented_xsize(); + cinfo.image_height = ib->oriented_ysize(); + if (ib->IsGray()) { + cinfo.input_components = 1; + cinfo.in_color_space = JCS_GRAYSCALE; + } else { + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + } + jpeg_set_defaults(&cinfo); + cinfo.optimize_coding = TRUE; + if (cinfo.input_components == 3) { + JXL_RETURN_IF_ERROR(SetChromaSubsampling(chroma_subsampling, &cinfo)); + } + jpeg_set_quality(&cinfo, quality, TRUE); + jpeg_start_compress(&cinfo, TRUE); + if (!ib->IsSRGB()) { + WriteICCProfile(&cinfo, ib->c_current().ICC()); + } + if (!io->blobs.exif.empty()) { + std::vector<uint8_t> exif = io->blobs.exif; + ResetExifOrientation(exif); + WriteExif(&cinfo, exif); + } + if (cinfo.input_components > 3 || cinfo.input_components < 0) + return JXL_FAILURE("invalid numbers of components"); + + size_t stride = + ib->oriented_xsize() * cinfo.input_components * sizeof(JSAMPLE); + PaddedBytes raw_bytes(stride * ib->oriented_ysize()); + JXL_RETURN_IF_ERROR(ConvertToExternal( + *ib, BITS_IN_JSAMPLE, /*float_out=*/false, cinfo.input_components, + JXL_BIG_ENDIAN, stride, nullptr, raw_bytes.data(), raw_bytes.size(), + /*out_callback=*/{}, ib->metadata()->GetOrientation())); + + for (size_t y = 0; y < ib->oriented_ysize(); ++y) { + JSAMPROW row[] = {raw_bytes.data() + y * stride}; + + jpeg_write_scanlines(&cinfo, row, 1); + } + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + bytes->resize(size); + // Compressed image data is initialized by libjpeg, which we are not + // instrumenting with msan. + msan::UnpoisonMemory(buffer, size); + std::copy_n(buffer, size, bytes->data()); + std::free(buffer); + return true; +} + +Status EncodeWithSJpeg(const ImageBundle* ib, const CodecInOut* io, + size_t quality, + const YCbCrChromaSubsampling& chroma_subsampling, + PaddedBytes* bytes) { +#if !JPEGXL_ENABLE_SJPEG + return JXL_FAILURE("JPEG XL was built without sjpeg support"); +#else + sjpeg::EncoderParam param(quality); + if (!ib->IsSRGB()) { + param.iccp.assign(ib->metadata()->color_encoding.ICC().begin(), + ib->metadata()->color_encoding.ICC().end()); + } + std::vector<uint8_t> exif = io->blobs.exif; + if (!exif.empty()) { + ResetExifOrientation(exif); + param.exif.assign(exif.begin(), exif.end()); + } + if (chroma_subsampling.Is444()) { + param.yuv_mode = SJPEG_YUV_444; + } else if (chroma_subsampling.Is420()) { + param.yuv_mode = SJPEG_YUV_SHARP; + } else { + return JXL_FAILURE("sjpeg does not support this chroma subsampling mode"); + } + size_t stride = ib->oriented_xsize() * 3; + PaddedBytes rgb(ib->xsize() * ib->ysize() * 3); + JXL_RETURN_IF_ERROR( + ConvertToExternal(*ib, 8, /*float_out=*/false, 3, JXL_BIG_ENDIAN, stride, + nullptr, rgb.data(), rgb.size(), + /*out_callback=*/{}, ib->metadata()->GetOrientation())); + + std::string output; + JXL_RETURN_IF_ERROR(sjpeg::Encode(rgb.data(), ib->oriented_xsize(), + ib->oriented_ysize(), stride, param, + &output)); + bytes->assign( + reinterpret_cast<const uint8_t*>(output.data()), + reinterpret_cast<const uint8_t*>(output.data() + output.size())); + return true; +#endif +} + +Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality, + YCbCrChromaSubsampling chroma_subsampling, + ThreadPool* pool, PaddedBytes* bytes) { + if (io->Main().HasAlpha()) { + return JXL_FAILURE("alpha is not supported"); + } + if (quality > 100) { + return JXL_FAILURE("please specify a 0-100 JPEG quality"); + } + + const ImageBundle* ib; + ImageMetadata metadata = io->metadata.m; + ImageBundle ib_store(&metadata); + JXL_RETURN_IF_ERROR(TransformIfNeeded(io->Main(), + io->metadata.m.color_encoding, + GetJxlCms(), pool, &ib_store, &ib)); + + switch (encoder) { + case JpegEncoder::kLibJpeg: + JXL_RETURN_IF_ERROR( + EncodeWithLibJpeg(ib, io, quality, chroma_subsampling, bytes)); + break; + case JpegEncoder::kSJpeg: + JXL_RETURN_IF_ERROR( + EncodeWithSJpeg(ib, io, quality, chroma_subsampling, bytes)); + break; + default: + return JXL_FAILURE("tried to use an unknown JPEG encoder"); + } + + return true; +} + +} // namespace extras +} // namespace jxl |