summaryrefslogtreecommitdiffstats
path: root/vcl/source/filter/jpeg/jpegc.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vcl/source/filter/jpeg/jpegc.cxx520
1 files changed, 520 insertions, 0 deletions
diff --git a/vcl/source/filter/jpeg/jpegc.cxx b/vcl/source/filter/jpeg/jpegc.cxx
new file mode 100644
index 000000000..8807927a8
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpegc.cxx
@@ -0,0 +1,520 @@
+/* -*- 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 <com/sun/star/task/XStatusIndicator.hpp>
+
+extern "C" {
+#include "transupp.h"
+}
+
+#include "jpeg.h"
+#include "JpegReader.hxx"
+#include "JpegWriter.hxx"
+#include <memory>
+#include <unotools/configmgr.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;
+};
+
+}
+
+#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);
+}
+
+}
+
+static int GetWarningLimit()
+{
+ return utl::ConfigManager::IsFuzzing() ? 5 : 1000;
+}
+
+extern "C" {
+
+static void emitMessage (j_common_ptr cinfo, int msg_level)
+{
+ if (msg_level < 0)
+ {
+ // ofz#3002 try to retain some degree of recoverability up to some
+ // reasonable limit (initially using ImageMagick's current limit of
+ // 1000), then bail.
+ // https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf
+ if (++cinfo->err->num_warnings > GetWarningLimit())
+ 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;
+ ErrorManagerStruct jerr;
+ JpegDecompressOwner aOwner;
+ std::unique_ptr<BitmapScopedWriteAccess> pScopedAccess;
+ std::vector<sal_uInt8> pScanLineBuffer;
+ std::vector<sal_uInt8> pCYMKBuffer;
+};
+
+}
+
+static void ReadJPEG(JpegStuff& rContext, JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines,
+ 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);
+ 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;
+
+ tools::Long nResult = 0;
+ if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000))
+ 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 (*pLines = 0; *pLines < nHeight && source->no_data_available_failures <= 1; (*pLines)++)
+ {
+ size_t yIndex = *pLines;
+
+ 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, tools::Long* pLines,
+ GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* ppAccess )
+{
+ JpegStuff aContext;
+ ReadJPEG(aContext, pJPEGReader, pInputStream, pLines, 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.getX(), 65535) && o3tl::convertsToAtMost(rPPI.getY(), 65535))
+ {
+ cinfo.density_unit = 1;
+ cinfo.X_density = rPPI.getX();
+ cinfo.Y_density = rPPI.getY();
+ }
+ else
+ {
+ SAL_WARN("vcl.filter", "ignoring too large PPI " << rPPI);
+ }
+
+ 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: */