/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you 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 .
*/
#include <sal/config.h>
#include <sal/log.hxx>
#include <o3tl/float_int_conversion.hxx>
#include <o3tl/safeint.hxx>
#include <stdio.h>
#include <setjmp.h>
#include <jpeglib.h>
#include <jerror.h>
#include <com/sun/star/task/XStatusIndicator.hpp>
extern "C" {
#include "transupp.h"
}
#include "jpeg.h"
#include "JpegReader.hxx"
#include "JpegWriter.hxx"
#include <memory>
#include <comphelper/configuration.hxx>
#include <vcl/graphicfilter.hxx>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning (disable: 4324) /* disable to __declspec(align()) aligned warning */
#endif
namespace {
struct ErrorManagerStruct
{
jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
ErrorManagerStruct()
{
pub = {};
memset(&setjmp_buffer, 0, sizeof(setjmp_buffer));
}
};
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
extern "C" {
static void errorExit (j_common_ptr cinfo)
{
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message) (cinfo, buffer);
SAL_WARN("vcl.filter", "fatal failure reading JPEG: " << buffer);
ErrorManagerStruct * error = reinterpret_cast<ErrorManagerStruct *>(cinfo->err);
longjmp(error->setjmp_buffer, 1);
}
static void outputMessage (j_common_ptr cinfo)
{
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message) (cinfo, buffer);
SAL_WARN("vcl.filter", "failure reading JPEG: " << buffer);
}
}
extern "C" {
// see also external/libtiff/0001-ofz-54685-Timeout.patch
static void emitMessage (j_common_ptr cinfo, int msg_level)
{
if (msg_level < 0)
{
// https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf
// try to retain some degree of recoverability up to some reasonable
// limit (initially using ImageMagick's current limit of 1000), then
// bail.
constexpr int WarningLimit = 1000;
static bool bFuzzing = comphelper::IsFuzzing();
// ofz#50452 due to Timeouts, just abandon fuzzing on any
// JWRN_NOT_SEQUENTIAL
if (bFuzzing && cinfo->err->msg_code == JWRN_NOT_SEQUENTIAL)
{
cinfo->err->error_exit(cinfo);
return;
}
if (++cinfo->err->num_warnings > WarningLimit)
cinfo->err->error_exit(cinfo);
else
cinfo->err->output_message(cinfo);
}
else if (cinfo->err->trace_level >= msg_level)
cinfo->err->output_message(cinfo);
}
}
namespace {
class JpegDecompressOwner
{
public:
void set(jpeg_decompress_struct *cinfo)
{
m_cinfo = cinfo;
}
~JpegDecompressOwner()
{
if (m_cinfo != nullptr)
{
jpeg_destroy_decompress(m_cinfo);
}
}
private:
jpeg_decompress_struct *m_cinfo = nullptr;
};
class JpegCompressOwner
{
public:
void set(jpeg_compress_struct *cinfo)
{
m_cinfo = cinfo;
}
~JpegCompressOwner()
{
if (m_cinfo != nullptr)
{
jpeg_destroy_compress(m_cinfo);
}
}
private:
jpeg_compress_struct *m_cinfo = nullptr;
};
struct JpegStuff
{
jpeg_decompress_struct cinfo;
jpeg_progress_mgr progress;
ErrorManagerStruct jerr;
JpegDecompressOwner aOwner;
std::unique_ptr<BitmapScopedWriteAccess> pScopedAccess;
std::vector<sal_uInt8> pScanLineBuffer;
std::vector<sal_uInt8> pCYMKBuffer;
};
// https://github.com/libjpeg-turbo/libjpeg-turbo/issues/284
// https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf
#define LIMITSCANS 100
void progress_monitor(j_common_ptr cinfo)
{
if (cinfo->is_decompressor)
{
jpeg_decompress_struct* decompressor = reinterpret_cast<j_decompress_ptr>(cinfo);
if (decompressor->input_scan_number >= LIMITSCANS)
{
SAL_WARN("vcl.filter", "too many progressive scans, cancelling import after: " << decompressor->input_scan_number << " scans");
errorExit(cinfo);
}
}
}
}
static void ReadJPEG(JpegStuff& rContext, JPEGReader* pJPEGReader, void* pInputStream,
GraphicFilterImportFlags nImportFlags,
BitmapScopedWriteAccess* ppAccess)
{
if (setjmp(rContext.jerr.setjmp_buffer))
{
return;
}
rContext.cinfo.err = jpeg_std_error(&rContext.jerr.pub);
rContext.jerr.pub.error_exit = errorExit;
rContext.jerr.pub.output_message = outputMessage;
rContext.jerr.pub.emit_message = emitMessage;
jpeg_create_decompress(&rContext.cinfo);
rContext.aOwner.set(&rContext.cinfo);
jpeg_svstream_src(&rContext.cinfo, pInputStream);
rContext.cinfo.progress = &rContext.progress;
rContext.progress.progress_monitor = progress_monitor;
SourceManagerStruct *source = reinterpret_cast<SourceManagerStruct*>(rContext.cinfo.src);
jpeg_read_header(&rContext.cinfo, TRUE);
rContext.cinfo.scale_num = 1;
rContext.cinfo.scale_denom = 1;
rContext.cinfo.output_gamma = 1.0;
rContext.cinfo.raw_data_out = FALSE;
rContext.cinfo.quantize_colors = FALSE;
jpeg_calc_output_dimensions(&rContext.cinfo);
tools::Long nWidth = rContext.cinfo.output_width;
tools::Long nHeight = rContext.cinfo.output_height;
if (comphelper::IsFuzzing())
{
tools::Long nResult = 0;
if (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000)
return;
if (rContext.cinfo.err->num_warnings && (nWidth > 8192 || nHeight > 8192))
return;
}
bool bGray = (rContext.cinfo.output_components == 1);
JPEGCreateBitmapParam aCreateBitmapParam;
aCreateBitmapParam.nWidth = nWidth;
aCreateBitmapParam.nHeight = nHeight;
aCreateBitmapParam.density_unit = rContext.cinfo.density_unit;
aCreateBitmapParam.X_density = rContext.cinfo.X_density;
aCreateBitmapParam.Y_density = rContext.cinfo.Y_density;
aCreateBitmapParam.bGray = bGray;
const auto bOnlyCreateBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap);
const auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
bool bBitmapCreated = bUseExistingBitmap;
if (!bBitmapCreated)
bBitmapCreated = pJPEGReader->CreateBitmap(aCreateBitmapParam);
if (bBitmapCreated && !bOnlyCreateBitmap)
{
if (nImportFlags & GraphicFilterImportFlags::UseExistingBitmap)
// ppAccess must be set if this flag is used.
assert(ppAccess);
else
rContext.pScopedAccess.reset(new BitmapScopedWriteAccess(pJPEGReader->GetBitmap()));
BitmapScopedWriteAccess& pAccess = bUseExistingBitmap ? *ppAccess : *rContext.pScopedAccess;
if (pAccess)
{
int nPixelSize = 3;
J_COLOR_SPACE best_out_color_space = JCS_RGB;
ScanlineFormat eScanlineFormat = ScanlineFormat::N24BitTcRgb;
ScanlineFormat eFinalFormat = pAccess->GetScanlineFormat();
if (bGray)
{
best_out_color_space = JCS_GRAYSCALE;
eScanlineFormat = ScanlineFormat::N8BitPal;
nPixelSize = 1;
}
#if defined(JCS_EXTENSIONS)
else if (eFinalFormat == ScanlineFormat::N24BitTcBgr)
{
best_out_color_space = JCS_EXT_BGR;
eScanlineFormat = eFinalFormat;
nPixelSize = 3;
}
else if (eFinalFormat == ScanlineFormat::N32BitTcBgra)
{
best_out_color_space = JCS_EXT_BGRA;
eScanlineFormat = eFinalFormat;
nPixelSize = 4;
}
else if (eFinalFormat == ScanlineFormat::N32BitTcRgba)
{
best_out_color_space = JCS_EXT_RGBA;
eScanlineFormat = eFinalFormat;
nPixelSize = 4;
}
else if (eFinalFormat == ScanlineFormat::N32BitTcArgb)
{
best_out_color_space = JCS_EXT_ARGB;
eScanlineFormat = eFinalFormat;
nPixelSize = 4;
}
#endif
if (rContext.cinfo.jpeg_color_space == JCS_YCCK)
rContext.cinfo.out_color_space = JCS_CMYK;
if (rContext.cinfo.out_color_space != JCS_CMYK)
rContext.cinfo.out_color_space = best_out_color_space;
jpeg_start_decompress(&rContext.cinfo);
JSAMPLE* aRangeLimit = rContext.cinfo.sample_range_limit;
rContext.pScanLineBuffer.resize(nWidth * nPixelSize);
if (rContext.cinfo.out_color_space == JCS_CMYK)
{
rContext.pCYMKBuffer.resize(nWidth * 4);
}
// tdf#138950 allow up to one short read (no_data_available_failures <= 1) to not trigger cancelling import
for (tools::Long nLine = 0; nLine < nHeight && source->no_data_available_failures <= 1; nLine++)
{
size_t yIndex = nLine;
sal_uInt8* p = (rContext.cinfo.out_color_space == JCS_CMYK) ? rContext.pCYMKBuffer.data() : rContext.pScanLineBuffer.data();
jpeg_read_scanlines(&rContext.cinfo, reinterpret_cast<JSAMPARRAY>(&p), 1);
if (rContext.cinfo.out_color_space == JCS_CMYK)
{
// convert CMYK to RGB
Scanline pScanline = pAccess->GetScanline(yIndex);
for (tools::Long cmyk = 0, x = 0; cmyk < nWidth * 4; cmyk += 4, ++x)
{
int color_C = 255 - rContext.pCYMKBuffer[cmyk + 0];
int color_M = 255 - rContext.pCYMKBuffer[cmyk + 1];
int color_Y = 255 - rContext.pCYMKBuffer[cmyk + 2];
int color_K = 255 - rContext.pCYMKBuffer[cmyk + 3];
sal_uInt8 cRed = aRangeLimit[255L - (color_C + color_K)];
sal_uInt8 cGreen = aRangeLimit[255L - (color_M + color_K)];
sal_uInt8 cBlue = aRangeLimit[255L - (color_Y + color_K)];
pAccess->SetPixelOnData(pScanline, x, BitmapColor(cRed, cGreen, cBlue));
}
}
else
{
pAccess->CopyScanline(yIndex, rContext.pScanLineBuffer.data(), eScanlineFormat, rContext.pScanLineBuffer.size());
}
/* PENDING ??? */
if (rContext.cinfo.err->msg_code == 113)
break;
}
rContext.pScanLineBuffer.clear();
rContext.pCYMKBuffer.clear();
}
rContext.pScopedAccess.reset();
}
if (bBitmapCreated && !bOnlyCreateBitmap)
{
jpeg_finish_decompress(&rContext.cinfo);
}
else
{
jpeg_abort_decompress(&rContext.cinfo);
}
}
void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream,
GraphicFilterImportFlags nImportFlags,
BitmapScopedWriteAccess* ppAccess )
{
JpegStuff aContext;
ReadJPEG(aContext, pJPEGReader, pInputStream, nImportFlags, ppAccess);
}
bool WriteJPEG( JPEGWriter* pJPEGWriter, void* pOutputStream,
tools::Long nWidth, tools::Long nHeight, basegfx::B2DSize const & rPPI, bool bGreys,
tools::Long nQualityPercent, tools::Long aChromaSubsampling,
css::uno::Reference<css::task::XStatusIndicator> const & status )
{
jpeg_compress_struct cinfo;
ErrorManagerStruct jerr;
void* pScanline;
tools::Long nY;
JpegCompressOwner aOwner;
if ( setjmp( jerr.setjmp_buffer ) )
{
return false;
}
cinfo.err = jpeg_std_error( &jerr.pub );
jerr.pub.error_exit = errorExit;
jerr.pub.output_message = outputMessage;
jpeg_create_compress( &cinfo );
aOwner.set(&cinfo);
jpeg_svstream_dest( &cinfo, pOutputStream );
cinfo.image_width = static_cast<JDIMENSION>(nWidth);
cinfo.image_height = static_cast<JDIMENSION>(nHeight);
if ( bGreys )
{
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 );
jpeg_set_quality( &cinfo, static_cast<int>(nQualityPercent), FALSE );
if (o3tl::convertsToAtMost(rPPI.getWidth(), 65535) && o3tl::convertsToAtMost(rPPI.getHeight(), 65535))
{
cinfo.density_unit = 1;
cinfo.X_density = rPPI.getWidth();
cinfo.Y_density = rPPI.getHeight();
}
else
{
SAL_WARN("vcl.filter", "ignoring too large PPI (" << rPPI.getWidth() << ", " << rPPI.getHeight() << ")");
}
if ( ( nWidth > 128 ) || ( nHeight > 128 ) )
jpeg_simple_progression( &cinfo );
if (aChromaSubsampling == 1) // YUV 4:4:4
{
cinfo.comp_info[0].h_samp_factor = 1;
cinfo.comp_info[0].v_samp_factor = 1;
}
else if (aChromaSubsampling == 2) // YUV 4:2:2
{
cinfo.comp_info[0].h_samp_factor = 2;
cinfo.comp_info[0].v_samp_factor = 1;
}
else if (aChromaSubsampling == 3) // YUV 4:2:0
{
cinfo.comp_info[0].h_samp_factor = 2;
cinfo.comp_info[0].v_samp_factor = 2;
}
jpeg_start_compress( &cinfo, TRUE );
for( nY = 0; nY < nHeight; nY++ )
{
pScanline = pJPEGWriter->GetScanline( nY );
if( pScanline )
{
jpeg_write_scanlines( &cinfo, reinterpret_cast<JSAMPARRAY>(&pScanline), 1 );
}
if( status.is() )
{
status->setValue( nY * 100L / nHeight );
}
}
jpeg_finish_compress(&cinfo);
return true;
}
void Transform(void* pInputStream, void* pOutputStream, Degree10 nAngle)
{
jpeg_transform_info aTransformOption;
JCOPY_OPTION aCopyOption = JCOPYOPT_ALL;
jpeg_decompress_struct aSourceInfo;
jpeg_compress_struct aDestinationInfo;
ErrorManagerStruct aSourceError;
ErrorManagerStruct aDestinationError;
jvirt_barray_ptr* aSourceCoefArrays = nullptr;
jvirt_barray_ptr* aDestinationCoefArrays = nullptr;
aTransformOption.force_grayscale = FALSE;
aTransformOption.trim = FALSE;
aTransformOption.perfect = FALSE;
aTransformOption.crop = FALSE;
// Angle to transform option
// 90 Clockwise = 270 Counterclockwise
switch (nAngle.get())
{
case 2700:
aTransformOption.transform = JXFORM_ROT_90;
break;
case 1800:
aTransformOption.transform = JXFORM_ROT_180;
break;
case 900:
aTransformOption.transform = JXFORM_ROT_270;
break;
default:
aTransformOption.transform = JXFORM_NONE;
}
// Decompression
aSourceInfo.err = jpeg_std_error(&aSourceError.pub);
aSourceInfo.err->error_exit = errorExit;
aSourceInfo.err->output_message = outputMessage;
// Compression
aDestinationInfo.err = jpeg_std_error(&aDestinationError.pub);
aDestinationInfo.err->error_exit = errorExit;
aDestinationInfo.err->output_message = outputMessage;
aDestinationInfo.optimize_coding = TRUE;
JpegDecompressOwner aDecompressOwner;
JpegCompressOwner aCompressOwner;
if (setjmp(aSourceError.setjmp_buffer))
{
jpeg_destroy_decompress(&aSourceInfo);
jpeg_destroy_compress(&aDestinationInfo);
return;
}
if (setjmp(aDestinationError.setjmp_buffer))
{
jpeg_destroy_decompress(&aSourceInfo);
jpeg_destroy_compress(&aDestinationInfo);
return;
}
jpeg_create_decompress(&aSourceInfo);
aDecompressOwner.set(&aSourceInfo);
jpeg_create_compress(&aDestinationInfo);
aCompressOwner.set(&aDestinationInfo);
jpeg_svstream_src (&aSourceInfo, pInputStream);
jcopy_markers_setup(&aSourceInfo, aCopyOption);
jpeg_read_header(&aSourceInfo, TRUE);
jtransform_request_workspace(&aSourceInfo, &aTransformOption);
aSourceCoefArrays = jpeg_read_coefficients(&aSourceInfo);
jpeg_copy_critical_parameters(&aSourceInfo, &aDestinationInfo);
aDestinationCoefArrays = jtransform_adjust_parameters(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
jpeg_svstream_dest (&aDestinationInfo, pOutputStream);
// Compute optimal Huffman coding tables instead of precomputed tables
aDestinationInfo.optimize_coding = TRUE;
jpeg_write_coefficients(&aDestinationInfo, aDestinationCoefArrays);
jcopy_markers_execute(&aSourceInfo, &aDestinationInfo, aCopyOption);
jtransform_execute_transformation(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
jpeg_finish_compress(&aDestinationInfo);
jpeg_finish_decompress(&aSourceInfo);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V1048 The 'nPixelSize' variable was assigned the same value.