summaryrefslogtreecommitdiff
path: root/media/libjxl/src/lib/extras/enc/jpg.cc
diff options
context:
space:
mode:
Diffstat (limited to 'media/libjxl/src/lib/extras/enc/jpg.cc')
-rw-r--r--media/libjxl/src/lib/extras/enc/jpg.cc239
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