bump product version to 6.4.0.3
[LibreOffice.git] / vcl / source / filter / jpeg / jpegc.cxx
blob8a57f55592179613564800a2e52628a7f0f88c99
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 #include <o3tl/safeint.hxx>
24 #include <stdio.h>
25 #include <setjmp.h>
26 #include <jpeglib.h>
28 #include <com/sun/star/task/XStatusIndicator.hpp>
30 extern "C" {
31 #include "transupp.h"
34 #include "jpeg.h"
35 #include "JpegReader.hxx"
36 #include "JpegWriter.hxx"
37 #include <memory>
38 #include <unotools/configmgr.hxx>
39 #include <vcl/graphicfilter.hxx>
41 #ifdef _MSC_VER
42 #pragma warning(push)
43 #pragma warning (disable: 4324) /* disable to __declspec(align()) aligned warning */
44 #endif
46 struct ErrorManagerStruct
48 jpeg_error_mgr pub;
49 jmp_buf setjmp_buffer;
52 #ifdef _MSC_VER
53 #pragma warning(pop)
54 #endif
56 extern "C" {
58 static void errorExit (j_common_ptr cinfo)
60 char buffer[JMSG_LENGTH_MAX];
61 (*cinfo->err->format_message) (cinfo, buffer);
62 SAL_WARN("vcl.filter", "fatal failure reading JPEG: " << buffer);
63 ErrorManagerStruct * error = reinterpret_cast<ErrorManagerStruct *>(cinfo->err);
64 longjmp(error->setjmp_buffer, 1);
67 static void outputMessage (j_common_ptr cinfo)
69 char buffer[JMSG_LENGTH_MAX];
70 (*cinfo->err->format_message) (cinfo, buffer);
71 SAL_WARN("vcl.filter", "failure reading JPEG: " << buffer);
76 static int GetWarningLimit()
78 return utl::ConfigManager::IsFuzzing() ? 5 : 1000;
81 extern "C" {
83 static void emitMessage (j_common_ptr cinfo, int msg_level)
85 if (msg_level < 0)
87 // ofz#3002 try to retain some degree of recoverability up to some
88 // reasonable limit (initially using ImageMagick's current limit of
89 // 1000), then bail.
90 // https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf
91 if (++cinfo->err->num_warnings > GetWarningLimit())
92 cinfo->err->error_exit(cinfo);
93 else
94 cinfo->err->output_message(cinfo);
96 else if (cinfo->err->trace_level >= msg_level)
97 cinfo->err->output_message(cinfo);
102 class JpegDecompressOwner
104 public:
105 void set(jpeg_decompress_struct *cinfo)
107 m_cinfo = cinfo;
109 ~JpegDecompressOwner()
111 if (m_cinfo != nullptr)
113 jpeg_destroy_decompress(m_cinfo);
116 private:
117 jpeg_decompress_struct *m_cinfo = nullptr;
120 class JpegCompressOwner
122 public:
123 void set(jpeg_compress_struct *cinfo)
125 m_cinfo = cinfo;
127 ~JpegCompressOwner()
129 if (m_cinfo != nullptr)
131 jpeg_destroy_compress(m_cinfo);
134 private:
135 jpeg_compress_struct *m_cinfo = nullptr;
138 struct JpegStuff
140 jpeg_decompress_struct cinfo;
141 ErrorManagerStruct jerr;
142 JpegDecompressOwner aOwner;
143 std::unique_ptr<BitmapScopedWriteAccess> pScopedAccess;
144 std::vector<sal_uInt8> pScanLineBuffer;
145 std::vector<sal_uInt8> pCYMKBuffer;
148 static void ReadJPEG(JpegStuff& rContext, JPEGReader* pJPEGReader, void* pInputStream, long* pLines,
149 Size const & previewSize, GraphicFilterImportFlags nImportFlags,
150 BitmapScopedWriteAccess* ppAccess)
152 if (setjmp(rContext.jerr.setjmp_buffer))
154 return;
157 rContext.cinfo.err = jpeg_std_error(&rContext.jerr.pub);
158 rContext.jerr.pub.error_exit = errorExit;
159 rContext.jerr.pub.output_message = outputMessage;
160 rContext.jerr.pub.emit_message = emitMessage;
162 jpeg_create_decompress(&rContext.cinfo);
163 rContext.aOwner.set(&rContext.cinfo);
164 jpeg_svstream_src(&rContext.cinfo, pInputStream);
165 SourceManagerStruct *source = reinterpret_cast<SourceManagerStruct*>(rContext.cinfo.src);
166 jpeg_read_header(&rContext.cinfo, TRUE);
168 rContext.cinfo.scale_num = 1;
169 rContext.cinfo.scale_denom = 1;
170 rContext.cinfo.output_gamma = 1.0;
171 rContext.cinfo.raw_data_out = FALSE;
172 rContext.cinfo.quantize_colors = FALSE;
174 /* change scale for preview import */
175 long nPreviewWidth = previewSize.Width();
176 long nPreviewHeight = previewSize.Height();
177 if( nPreviewWidth || nPreviewHeight )
179 if( nPreviewWidth == 0 )
181 nPreviewWidth = (rContext.cinfo.image_width * nPreviewHeight) / rContext.cinfo.image_height;
182 if( nPreviewWidth <= 0 )
184 nPreviewWidth = 1;
187 else if( nPreviewHeight == 0 )
189 nPreviewHeight = (rContext.cinfo.image_height * nPreviewWidth) / rContext.cinfo.image_width;
190 if( nPreviewHeight <= 0 )
192 nPreviewHeight = 1;
196 for (rContext.cinfo.scale_denom = 1; rContext.cinfo.scale_denom < 8; rContext.cinfo.scale_denom *= 2)
198 if (rContext.cinfo.image_width < nPreviewWidth * rContext.cinfo.scale_denom)
199 break;
200 if (rContext.cinfo.image_height < nPreviewHeight * rContext.cinfo.scale_denom)
201 break;
204 if (rContext.cinfo.scale_denom > 1)
206 rContext.cinfo.dct_method = JDCT_FASTEST;
207 rContext.cinfo.do_fancy_upsampling = FALSE;
208 rContext.cinfo.do_block_smoothing = FALSE;
212 jpeg_calc_output_dimensions(&rContext.cinfo);
214 long nWidth = rContext.cinfo.output_width;
215 long nHeight = rContext.cinfo.output_height;
217 long nResult = 0;
218 if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000))
219 return;
221 bool bGray = (rContext.cinfo.output_components == 1);
223 JPEGCreateBitmapParam aCreateBitmapParam;
225 aCreateBitmapParam.nWidth = nWidth;
226 aCreateBitmapParam.nHeight = nHeight;
228 aCreateBitmapParam.density_unit = rContext.cinfo.density_unit;
229 aCreateBitmapParam.X_density = rContext.cinfo.X_density;
230 aCreateBitmapParam.Y_density = rContext.cinfo.Y_density;
231 aCreateBitmapParam.bGray = bGray;
233 const auto bOnlyCreateBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap);
234 const auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
235 bool bBitmapCreated = bUseExistingBitmap;
236 if (!bBitmapCreated)
237 bBitmapCreated = pJPEGReader->CreateBitmap(aCreateBitmapParam);
239 if (bBitmapCreated && !bOnlyCreateBitmap)
241 if (nImportFlags & GraphicFilterImportFlags::UseExistingBitmap)
242 // ppAccess must be set if this flag is used.
243 assert(ppAccess);
244 else
245 rContext.pScopedAccess.reset(new BitmapScopedWriteAccess(pJPEGReader->GetBitmap()));
247 BitmapScopedWriteAccess& pAccess = bUseExistingBitmap ? *ppAccess : *rContext.pScopedAccess;
249 if (pAccess)
251 int nPixelSize = 3;
252 J_COLOR_SPACE best_out_color_space = JCS_RGB;
253 ScanlineFormat eScanlineFormat = ScanlineFormat::N24BitTcRgb;
254 ScanlineFormat eFinalFormat = pAccess->GetScanlineFormat();
256 if (bGray)
258 best_out_color_space = JCS_GRAYSCALE;
259 eScanlineFormat = ScanlineFormat::N8BitPal;
260 nPixelSize = 1;
262 #if defined(JCS_EXTENSIONS)
263 else if (eFinalFormat == ScanlineFormat::N32BitTcBgra)
265 best_out_color_space = JCS_EXT_BGRA;
266 eScanlineFormat = eFinalFormat;
267 nPixelSize = 4;
269 else if (eFinalFormat == ScanlineFormat::N32BitTcRgba)
271 best_out_color_space = JCS_EXT_RGBA;
272 eScanlineFormat = eFinalFormat;
273 nPixelSize = 4;
275 else if (eFinalFormat == ScanlineFormat::N32BitTcArgb)
277 best_out_color_space = JCS_EXT_ARGB;
278 eScanlineFormat = eFinalFormat;
279 nPixelSize = 4;
281 #endif
282 if (rContext.cinfo.jpeg_color_space == JCS_YCCK)
283 rContext.cinfo.out_color_space = JCS_CMYK;
285 if (rContext.cinfo.out_color_space != JCS_CMYK)
286 rContext.cinfo.out_color_space = best_out_color_space;
288 jpeg_start_decompress(&rContext.cinfo);
290 JSAMPLE* aRangeLimit = rContext.cinfo.sample_range_limit;
292 rContext.pScanLineBuffer.resize(nWidth * nPixelSize);
294 if (rContext.cinfo.out_color_space == JCS_CMYK)
296 rContext.pCYMKBuffer.resize(nWidth * 4);
299 for (*pLines = 0; *pLines < nHeight && !source->no_data_available; (*pLines)++)
301 size_t yIndex = *pLines;
303 sal_uInt8* p = (rContext.cinfo.out_color_space == JCS_CMYK) ? rContext.pCYMKBuffer.data() : rContext.pScanLineBuffer.data();
304 jpeg_read_scanlines(&rContext.cinfo, reinterpret_cast<JSAMPARRAY>(&p), 1);
306 if (rContext.cinfo.out_color_space == JCS_CMYK)
308 // convert CMYK to RGB
309 Scanline pScanline = pAccess->GetScanline(yIndex);
310 for (long cmyk = 0, x = 0; cmyk < nWidth * 4; cmyk += 4, ++x)
312 int color_C = 255 - rContext.pCYMKBuffer[cmyk + 0];
313 int color_M = 255 - rContext.pCYMKBuffer[cmyk + 1];
314 int color_Y = 255 - rContext.pCYMKBuffer[cmyk + 2];
315 int color_K = 255 - rContext.pCYMKBuffer[cmyk + 3];
317 sal_uInt8 cRed = aRangeLimit[255L - (color_C + color_K)];
318 sal_uInt8 cGreen = aRangeLimit[255L - (color_M + color_K)];
319 sal_uInt8 cBlue = aRangeLimit[255L - (color_Y + color_K)];
321 pAccess->SetPixelOnData(pScanline, x, BitmapColor(cRed, cGreen, cBlue));
324 else
326 pAccess->CopyScanline(yIndex, rContext.pScanLineBuffer.data(), eScanlineFormat, rContext.pScanLineBuffer.size());
329 /* PENDING ??? */
330 if (rContext.cinfo.err->msg_code == 113)
331 break;
334 rContext.pScanLineBuffer.clear();
335 rContext.pCYMKBuffer.clear();
337 rContext.pScopedAccess.reset();
340 if (bBitmapCreated && !bOnlyCreateBitmap)
342 jpeg_finish_decompress(&rContext.cinfo);
344 else
346 jpeg_abort_decompress(&rContext.cinfo);
350 void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, long* pLines,
351 Size const & previewSize, GraphicFilterImportFlags nImportFlags,
352 BitmapScopedWriteAccess* ppAccess )
354 JpegStuff aContext;
355 ReadJPEG(aContext, pJPEGReader, pInputStream, pLines, previewSize, nImportFlags, ppAccess);
358 bool WriteJPEG( JPEGWriter* pJPEGWriter, void* pOutputStream,
359 long nWidth, long nHeight, basegfx::B2DSize const & rPPI, bool bGreys,
360 long nQualityPercent, long aChromaSubsampling,
361 css::uno::Reference<css::task::XStatusIndicator> const & status )
363 jpeg_compress_struct cinfo;
364 ErrorManagerStruct jerr;
365 void* pScanline;
366 long nY;
368 JpegCompressOwner aOwner;
370 if ( setjmp( jerr.setjmp_buffer ) )
372 return false;
375 cinfo.err = jpeg_std_error( &jerr.pub );
376 jerr.pub.error_exit = errorExit;
377 jerr.pub.output_message = outputMessage;
379 jpeg_create_compress( &cinfo );
380 aOwner.set(&cinfo);
381 jpeg_svstream_dest( &cinfo, pOutputStream );
383 cinfo.image_width = static_cast<JDIMENSION>(nWidth);
384 cinfo.image_height = static_cast<JDIMENSION>(nHeight);
385 if ( bGreys )
387 cinfo.input_components = 1;
388 cinfo.in_color_space = JCS_GRAYSCALE;
390 else
392 cinfo.input_components = 3;
393 cinfo.in_color_space = JCS_RGB;
396 jpeg_set_defaults( &cinfo );
397 jpeg_set_quality( &cinfo, static_cast<int>(nQualityPercent), FALSE );
399 cinfo.density_unit = 1;
400 cinfo.X_density = rPPI.getX();
401 cinfo.Y_density = rPPI.getY();
403 if ( ( nWidth > 128 ) || ( nHeight > 128 ) )
404 jpeg_simple_progression( &cinfo );
406 if (aChromaSubsampling == 1) // YUV 4:4:4
408 cinfo.comp_info[0].h_samp_factor = 1;
409 cinfo.comp_info[0].v_samp_factor = 1;
411 else if (aChromaSubsampling == 2) // YUV 4:2:2
413 cinfo.comp_info[0].h_samp_factor = 2;
414 cinfo.comp_info[0].v_samp_factor = 1;
416 else if (aChromaSubsampling == 3) // YUV 4:2:0
418 cinfo.comp_info[0].h_samp_factor = 2;
419 cinfo.comp_info[0].v_samp_factor = 2;
422 jpeg_start_compress( &cinfo, TRUE );
424 for( nY = 0; nY < nHeight; nY++ )
426 pScanline = pJPEGWriter->GetScanline( nY );
428 if( pScanline )
430 jpeg_write_scanlines( &cinfo, reinterpret_cast<JSAMPARRAY>(&pScanline), 1 );
433 if( status.is() )
435 status->setValue( nY * 100L / nHeight );
439 jpeg_finish_compress(&cinfo);
441 return true;
444 void Transform(void* pInputStream, void* pOutputStream, long nAngle)
446 jpeg_transform_info aTransformOption;
447 JCOPY_OPTION aCopyOption = JCOPYOPT_ALL;
449 jpeg_decompress_struct aSourceInfo;
450 jpeg_compress_struct aDestinationInfo;
451 ErrorManagerStruct aSourceError;
452 ErrorManagerStruct aDestinationError;
454 jvirt_barray_ptr* aSourceCoefArrays = nullptr;
455 jvirt_barray_ptr* aDestinationCoefArrays = nullptr;
457 aTransformOption.force_grayscale = FALSE;
458 aTransformOption.trim = FALSE;
459 aTransformOption.perfect = FALSE;
460 aTransformOption.crop = FALSE;
462 // Angle to transform option
463 // 90 Clockwise = 270 Counterclockwise
464 switch (nAngle)
466 case 2700:
467 aTransformOption.transform = JXFORM_ROT_90;
468 break;
469 case 1800:
470 aTransformOption.transform = JXFORM_ROT_180;
471 break;
472 case 900:
473 aTransformOption.transform = JXFORM_ROT_270;
474 break;
475 default:
476 aTransformOption.transform = JXFORM_NONE;
479 // Decompression
480 aSourceInfo.err = jpeg_std_error(&aSourceError.pub);
481 aSourceInfo.err->error_exit = errorExit;
482 aSourceInfo.err->output_message = outputMessage;
484 // Compression
485 aDestinationInfo.err = jpeg_std_error(&aDestinationError.pub);
486 aDestinationInfo.err->error_exit = errorExit;
487 aDestinationInfo.err->output_message = outputMessage;
489 aDestinationInfo.optimize_coding = TRUE;
491 JpegDecompressOwner aDecompressOwner;
492 JpegCompressOwner aCompressOwner;
494 if (setjmp(aSourceError.setjmp_buffer) || setjmp(aDestinationError.setjmp_buffer))
496 jpeg_destroy_decompress(&aSourceInfo);
497 jpeg_destroy_compress(&aDestinationInfo);
498 return;
501 jpeg_create_decompress(&aSourceInfo);
502 aDecompressOwner.set(&aSourceInfo);
503 jpeg_create_compress(&aDestinationInfo);
504 aCompressOwner.set(&aDestinationInfo);
506 jpeg_svstream_src (&aSourceInfo, pInputStream);
508 jcopy_markers_setup(&aSourceInfo, aCopyOption);
509 jpeg_read_header(&aSourceInfo, TRUE);
510 jtransform_request_workspace(&aSourceInfo, &aTransformOption);
512 aSourceCoefArrays = jpeg_read_coefficients(&aSourceInfo);
513 jpeg_copy_critical_parameters(&aSourceInfo, &aDestinationInfo);
515 aDestinationCoefArrays = jtransform_adjust_parameters(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
516 jpeg_svstream_dest (&aDestinationInfo, pOutputStream);
518 // Compute optimal Huffman coding tables instead of precomputed tables
519 aDestinationInfo.optimize_coding = TRUE;
520 jpeg_write_coefficients(&aDestinationInfo, aDestinationCoefArrays);
521 jcopy_markers_execute(&aSourceInfo, &aDestinationInfo, aCopyOption);
522 jtransform_execute_transformation(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
524 jpeg_finish_compress(&aDestinationInfo);
526 jpeg_finish_decompress(&aSourceInfo);
529 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */