1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
28 #include <com/sun/star/task/XStatusIndicator.hpp>
35 #include "JpegReader.hxx"
36 #include "JpegWriter.hxx"
38 #include <unotools/configmgr.hxx>
39 #include <vcl/graphicfilter.hxx>
43 #pragma warning (disable: 4324) /* disable to __declspec(align()) aligned warning */
46 struct ErrorManagerStruct
49 jmp_buf setjmp_buffer
;
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;
83 static void emitMessage (j_common_ptr cinfo
, int msg_level
)
87 // ofz#3002 try to retain some degree of recoverability up to some
88 // reasonable limit (initially using ImageMagick's current limit of
90 // https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf
91 if (++cinfo
->err
->num_warnings
> GetWarningLimit())
92 cinfo
->err
->error_exit(cinfo
);
94 cinfo
->err
->output_message(cinfo
);
96 else if (cinfo
->err
->trace_level
>= msg_level
)
97 cinfo
->err
->output_message(cinfo
);
102 class JpegDecompressOwner
105 void set(jpeg_decompress_struct
*cinfo
)
109 ~JpegDecompressOwner()
111 if (m_cinfo
!= nullptr)
113 jpeg_destroy_decompress(m_cinfo
);
117 jpeg_decompress_struct
*m_cinfo
= nullptr;
120 class JpegCompressOwner
123 void set(jpeg_compress_struct
*cinfo
)
129 if (m_cinfo
!= nullptr)
131 jpeg_destroy_compress(m_cinfo
);
135 jpeg_compress_struct
*m_cinfo
= nullptr;
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
))
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 )
187 else if( nPreviewHeight
== 0 )
189 nPreviewHeight
= (rContext
.cinfo
.image_height
* nPreviewWidth
) / rContext
.cinfo
.image_width
;
190 if( nPreviewHeight
<= 0 )
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
)
200 if (rContext
.cinfo
.image_height
< nPreviewHeight
* rContext
.cinfo
.scale_denom
)
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
;
218 if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth
, nHeight
, nResult
) || nResult
> 4000000))
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
;
237 bBitmapCreated
= pJPEGReader
->CreateBitmap(aCreateBitmapParam
);
239 if (bBitmapCreated
&& !bOnlyCreateBitmap
)
241 if (nImportFlags
& GraphicFilterImportFlags::UseExistingBitmap
)
242 // ppAccess must be set if this flag is used.
245 rContext
.pScopedAccess
.reset(new BitmapScopedWriteAccess(pJPEGReader
->GetBitmap()));
247 BitmapScopedWriteAccess
& pAccess
= bUseExistingBitmap
? *ppAccess
: *rContext
.pScopedAccess
;
252 J_COLOR_SPACE best_out_color_space
= JCS_RGB
;
253 ScanlineFormat eScanlineFormat
= ScanlineFormat::N24BitTcRgb
;
254 ScanlineFormat eFinalFormat
= pAccess
->GetScanlineFormat();
258 best_out_color_space
= JCS_GRAYSCALE
;
259 eScanlineFormat
= ScanlineFormat::N8BitPal
;
262 #if defined(JCS_EXTENSIONS)
263 else if (eFinalFormat
== ScanlineFormat::N32BitTcBgra
)
265 best_out_color_space
= JCS_EXT_BGRA
;
266 eScanlineFormat
= eFinalFormat
;
269 else if (eFinalFormat
== ScanlineFormat::N32BitTcRgba
)
271 best_out_color_space
= JCS_EXT_RGBA
;
272 eScanlineFormat
= eFinalFormat
;
275 else if (eFinalFormat
== ScanlineFormat::N32BitTcArgb
)
277 best_out_color_space
= JCS_EXT_ARGB
;
278 eScanlineFormat
= eFinalFormat
;
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
));
326 pAccess
->CopyScanline(yIndex
, rContext
.pScanLineBuffer
.data(), eScanlineFormat
, rContext
.pScanLineBuffer
.size());
330 if (rContext
.cinfo
.err
->msg_code
== 113)
334 rContext
.pScanLineBuffer
.clear();
335 rContext
.pCYMKBuffer
.clear();
337 rContext
.pScopedAccess
.reset();
340 if (bBitmapCreated
&& !bOnlyCreateBitmap
)
342 jpeg_finish_decompress(&rContext
.cinfo
);
346 jpeg_abort_decompress(&rContext
.cinfo
);
350 void ReadJPEG( JPEGReader
* pJPEGReader
, void* pInputStream
, long* pLines
,
351 Size
const & previewSize
, GraphicFilterImportFlags nImportFlags
,
352 BitmapScopedWriteAccess
* ppAccess
)
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
;
368 JpegCompressOwner aOwner
;
370 if ( setjmp( jerr
.setjmp_buffer
) )
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
);
381 jpeg_svstream_dest( &cinfo
, pOutputStream
);
383 cinfo
.image_width
= static_cast<JDIMENSION
>(nWidth
);
384 cinfo
.image_height
= static_cast<JDIMENSION
>(nHeight
);
387 cinfo
.input_components
= 1;
388 cinfo
.in_color_space
= JCS_GRAYSCALE
;
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
);
430 jpeg_write_scanlines( &cinfo
, reinterpret_cast<JSAMPARRAY
>(&pScanline
), 1 );
435 status
->setValue( nY
* 100L / nHeight
);
439 jpeg_finish_compress(&cinfo
);
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
467 aTransformOption
.transform
= JXFORM_ROT_90
;
470 aTransformOption
.transform
= JXFORM_ROT_180
;
473 aTransformOption
.transform
= JXFORM_ROT_270
;
476 aTransformOption
.transform
= JXFORM_NONE
;
480 aSourceInfo
.err
= jpeg_std_error(&aSourceError
.pub
);
481 aSourceInfo
.err
->error_exit
= errorExit
;
482 aSourceInfo
.err
->output_message
= outputMessage
;
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
);
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: */