cpp/spectrum/plugins/jpeg/LibJpegDctTransformer.cpp (146 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include "LibJpegDctTransformer.h"
#include <spectrum/codecs/ICompressor.h>
#include <spectrum/core/Constants.h>
#include <spectrum/core/SpectrumEnforce.h>
#include <spectrum/core/utils/Numeric.h>
#include <spectrum/io/IImageSink.h>
#include <spectrum/io/IImageSource.h>
#include <spectrum/plugins/jpeg/LibJpegSinkManager.h>
#include <spectrum/plugins/jpeg/LibJpegSourceManager.h>
#include <spectrum/plugins/jpeg/LibJpegUtilities.h>
#include <array>
#include <memory>
#include <mozjpeg/jpegint.h>
namespace facebook {
namespace spectrum {
namespace plugins {
namespace jpeg {
namespace /* anonymous */ {
void libJpegErrorToSpectrumExecption(j_common_ptr libJpegCompressInfo) {
// create and throw the jpeg-turbo error message
char buffer[JMSG_LENGTH_MAX];
(*libJpegCompressInfo->err->format_message)(libJpegCompressInfo, buffer);
buffer[JMSG_LENGTH_MAX - 1] = '\0';
SPECTRUM_ERROR_STRING(codecs::error::CompressorFailure, std::string(buffer));
}
} // namespace
LibJpegDctTransformer::LibJpegDctTransformer(
io::IImageSource& source,
io::IImageSink& sink)
: sourceManager(source), sinkManager(sink) {
// set error handling
libJpegErrorManager.error_exit = libJpegErrorToSpectrumExecption;
libJpegDecompressInfo.err = jpeg_std_error(&libJpegErrorManager);
libJpegCompressInfo.err = jpeg_std_error(&libJpegErrorManager);
// initialize decompress struct
jpeg_create_decompress(&libJpegDecompressInfo);
libJpegDecompressInfo.src = sourceManager.getLibJpegSourceManagerPointer();
// initialize compress struct
jpeg_create_compress(&libJpegCompressInfo);
libJpegCompressInfo.dest = sinkManager.getLibJpegDestinationManagerPointer();
// initialize transform info
libJpegTransformInfo.perfect = false; // allow partial MCUs
libJpegTransformInfo.trim = true; // allow trimming partial MCUs
libJpegTransformInfo.force_grayscale = false;
libJpegTransformInfo.crop = false;
libJpegTransformInfo.slow_hflip = false;
}
LibJpegDctTransformer::~LibJpegDctTransformer() {
jpeg_destroy_compress(&libJpegCompressInfo);
jpeg_destroy_decompress(&libJpegDecompressInfo);
}
void LibJpegDctTransformer::ensureHeaderIsRead() {
if (libJpegDecompressInfo.global_state < DSTATE_INHEADER) {
jcopy_markers_setup(&libJpegDecompressInfo, JCOPYOPT_ALL);
const auto result = jpeg_read_header(&libJpegDecompressInfo, true);
SPECTRUM_ERROR_CSTR_IF_NOT(
JPEG_HEADER_OK == result,
codecs::error::CompressorFailure,
"jpeg_read_header_failed");
}
}
void LibJpegDctTransformer::setRotateRequirement(
const folly::Optional<requirements::Rotate>& rotateRequirement) {
ensureNotFinished();
this->rotateRequirement = rotateRequirement;
}
void LibJpegDctTransformer::setCropRequirement(
const folly::Optional<requirements::Crop>& cropRequirement) {
ensureNotFinished();
SPECTRUM_ENFORCE_IF(
cropRequirement.hasValue() && cropRequirement->mustBeExact);
this->cropRequirement = cropRequirement;
}
void LibJpegDctTransformer::applyAndFinish() {
ensureNotFinished();
ensureHeaderIsRead();
// apply rotation and cropping to libJpeg objects
applyRotationToTransformInfo();
applyCroppingToTransformInfo();
// intialize and setup temporary buffers - also verifies arguments
jtransform_request_workspace(&libJpegDecompressInfo, &libJpegTransformInfo);
jvirt_barray_ptr* srccoefs = jpeg_read_coefficients(&libJpegDecompressInfo);
// e.g. quant tables
jpeg_copy_critical_parameters(&libJpegDecompressInfo, &libJpegCompressInfo);
jvirt_barray_ptr* dstcoefs = jtransform_adjust_parameters(
&libJpegDecompressInfo,
&libJpegCompressInfo,
srccoefs,
&libJpegTransformInfo);
jpeg_write_coefficients(&libJpegCompressInfo, dstcoefs);
// we re-write parsed metadata to control the specific elements copied; in
// particular to avoid copying thumbnail images
writeMetadata(libJpegCompressInfo, readMetadata(libJpegDecompressInfo));
// will finally apply the transformations (e.g. rotating in place)
jtransform_execute_transformation(
&libJpegDecompressInfo,
&libJpegCompressInfo,
srccoefs,
&libJpegTransformInfo);
// finish
jpeg_finish_compress(&libJpegCompressInfo);
jpeg_finish_decompress(&libJpegDecompressInfo);
isFinished = true;
}
void LibJpegDctTransformer::applyRotationToTransformInfo() {
if (rotateRequirement.hasValue()) {
if (rotateRequirement->sanitisedDegrees() == 90) {
libJpegTransformInfo.transform = JXFORM_ROT_90;
} else if (rotateRequirement->sanitisedDegrees() == 180) {
libJpegTransformInfo.transform = JXFORM_ROT_180;
} else if (rotateRequirement->sanitisedDegrees() == 270) {
libJpegTransformInfo.transform = JXFORM_ROT_270;
} else {
libJpegTransformInfo.transform = JXFORM_NONE;
}
} else {
libJpegTransformInfo.transform = JXFORM_NONE;
}
}
void LibJpegDctTransformer::applyCroppingToTransformInfo() {
// LibJpeg first rotates and then crops. However in Spectrum we have the
// cropping coordinates refer to the input image. Therefore we need to rotate
// them in these cases.
if (cropRequirement.hasValue()) {
image::Size imageSize{
static_cast<std::uint32_t>(libJpegDecompressInfo.image_width),
static_cast<std::uint32_t>(libJpegDecompressInfo.image_height)};
auto cropRect = image::rectZero;
if (rotateRequirement.hasValue()) {
const auto rotatedCropRequirement =
cropRequirement->rotated(*rotateRequirement, imageSize);
const auto rotatedImageSize =
imageSize.rotated(rotateRequirement->sanitisedDegrees());
cropRect = rotatedCropRequirement.apply(rotatedImageSize);
} else {
cropRect = cropRequirement->apply(imageSize);
}
libJpegTransformInfo.crop = true;
libJpegTransformInfo.crop_xoffset_set = JCROP_POS;
libJpegTransformInfo.crop_yoffset_set = JCROP_POS;
libJpegTransformInfo.crop_width_set = JCROP_POS;
libJpegTransformInfo.crop_height_set = JCROP_POS;
libJpegTransformInfo.crop_xoffset = cropRect.topLeft.x;
libJpegTransformInfo.crop_yoffset = cropRect.topLeft.y;
libJpegTransformInfo.crop_width = cropRect.size.width;
libJpegTransformInfo.crop_height = cropRect.size.height;
}
}
image::Size LibJpegDctTransformer::getOutputSize() const {
SPECTRUM_ENFORCE_IF_NOT(isFinished);
return image::Size{
(std::uint32_t)libJpegTransformInfo.output_width,
(std::uint32_t)libJpegTransformInfo.output_height};
}
void LibJpegDctTransformer::ensureNotFinished() {
SPECTRUM_ENFORCE_IF(isFinished);
}
} // namespace jpeg
} // namespace plugins
} // namespace spectrum
} // namespace facebook