LibreOffice Module vcl (master) 1
jpegc.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include <sal/config.h>
21#include <sal/log.hxx>
23#include <o3tl/safeint.hxx>
24
25#include <stdio.h>
26#include <setjmp.h>
27#include <jpeglib.h>
28
29#include <com/sun/star/task/XStatusIndicator.hpp>
30
31extern "C" {
32#include "transupp.h"
33}
34
35#include "jpeg.h"
36#include "JpegReader.hxx"
37#include "JpegWriter.hxx"
38#include <memory>
40#include <vcl/graphicfilter.hxx>
41
42#ifdef _MSC_VER
43#pragma warning(push)
44#pragma warning (disable: 4324) /* disable to __declspec(align()) aligned warning */
45#endif
46
47namespace {
48
49struct ErrorManagerStruct
50{
51 jpeg_error_mgr pub;
52 jmp_buf setjmp_buffer;
53};
54
55}
56
57#ifdef _MSC_VER
58#pragma warning(pop)
59#endif
60
61extern "C" {
62
63static void errorExit (j_common_ptr cinfo)
64{
65 char buffer[JMSG_LENGTH_MAX];
66 (*cinfo->err->format_message) (cinfo, buffer);
67 SAL_WARN("vcl.filter", "fatal failure reading JPEG: " << buffer);
68 ErrorManagerStruct * error = reinterpret_cast<ErrorManagerStruct *>(cinfo->err);
69 longjmp(error->setjmp_buffer, 1);
70}
71
72static void outputMessage (j_common_ptr cinfo)
73{
74 char buffer[JMSG_LENGTH_MAX];
75 (*cinfo->err->format_message) (cinfo, buffer);
76 SAL_WARN("vcl.filter", "failure reading JPEG: " << buffer);
77}
78
79}
80
81static int GetWarningLimit()
82{
83 return utl::ConfigManager::IsFuzzing() ? 5 : 1000;
84}
85
86extern "C" {
87
88static void emitMessage (j_common_ptr cinfo, int msg_level)
89{
90 if (msg_level < 0)
91 {
92 // ofz#3002 try to retain some degree of recoverability up to some
93 // reasonable limit (initially using ImageMagick's current limit of
94 // 1000), then bail.
95 // https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf
96 if (++cinfo->err->num_warnings > GetWarningLimit())
97 cinfo->err->error_exit(cinfo);
98 else
99 cinfo->err->output_message(cinfo);
100 }
101 else if (cinfo->err->trace_level >= msg_level)
102 cinfo->err->output_message(cinfo);
103}
104
105}
106
107namespace {
108
109class JpegDecompressOwner
110{
111public:
112 void set(jpeg_decompress_struct *cinfo)
113 {
114 m_cinfo = cinfo;
115 }
116 ~JpegDecompressOwner()
117 {
118 if (m_cinfo != nullptr)
119 {
120 jpeg_destroy_decompress(m_cinfo);
121 }
122 }
123private:
124 jpeg_decompress_struct *m_cinfo = nullptr;
125};
126
127class JpegCompressOwner
128{
129public:
130 void set(jpeg_compress_struct *cinfo)
131 {
132 m_cinfo = cinfo;
133 }
134 ~JpegCompressOwner()
135 {
136 if (m_cinfo != nullptr)
137 {
138 jpeg_destroy_compress(m_cinfo);
139 }
140 }
141private:
142 jpeg_compress_struct *m_cinfo = nullptr;
143};
144
145struct JpegStuff
146{
147 jpeg_decompress_struct cinfo;
148 ErrorManagerStruct jerr;
149 JpegDecompressOwner aOwner;
150 std::unique_ptr<BitmapScopedWriteAccess> pScopedAccess;
151 std::vector<sal_uInt8> pScanLineBuffer;
152 std::vector<sal_uInt8> pCYMKBuffer;
153};
154
155}
156
157static void ReadJPEG(JpegStuff& rContext, JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines,
158 GraphicFilterImportFlags nImportFlags,
159 BitmapScopedWriteAccess* ppAccess)
160{
161 if (setjmp(rContext.jerr.setjmp_buffer))
162 {
163 return;
164 }
165
166 rContext.cinfo.err = jpeg_std_error(&rContext.jerr.pub);
167 rContext.jerr.pub.error_exit = errorExit;
168 rContext.jerr.pub.output_message = outputMessage;
169 rContext.jerr.pub.emit_message = emitMessage;
170
171 jpeg_create_decompress(&rContext.cinfo);
172 rContext.aOwner.set(&rContext.cinfo);
173 jpeg_svstream_src(&rContext.cinfo, pInputStream);
174 SourceManagerStruct *source = reinterpret_cast<SourceManagerStruct*>(rContext.cinfo.src);
175 jpeg_read_header(&rContext.cinfo, TRUE);
176
177 rContext.cinfo.scale_num = 1;
178 rContext.cinfo.scale_denom = 1;
179 rContext.cinfo.output_gamma = 1.0;
180 rContext.cinfo.raw_data_out = FALSE;
181 rContext.cinfo.quantize_colors = FALSE;
182
183 jpeg_calc_output_dimensions(&rContext.cinfo);
184
185 tools::Long nWidth = rContext.cinfo.output_width;
186 tools::Long nHeight = rContext.cinfo.output_height;
187
189 {
190 tools::Long nResult = 0;
191 if (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000)
192 return;
193 if (rContext.cinfo.err->num_warnings && (nWidth > 8192 || nHeight > 8192))
194 return;
195 }
196
197 bool bGray = (rContext.cinfo.output_components == 1);
198
199 JPEGCreateBitmapParam aCreateBitmapParam;
200
201 aCreateBitmapParam.nWidth = nWidth;
202 aCreateBitmapParam.nHeight = nHeight;
203
204 aCreateBitmapParam.density_unit = rContext.cinfo.density_unit;
205 aCreateBitmapParam.X_density = rContext.cinfo.X_density;
206 aCreateBitmapParam.Y_density = rContext.cinfo.Y_density;
207 aCreateBitmapParam.bGray = bGray;
208
209 const auto bOnlyCreateBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap);
210 const auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
211 bool bBitmapCreated = bUseExistingBitmap;
212 if (!bBitmapCreated)
213 bBitmapCreated = pJPEGReader->CreateBitmap(aCreateBitmapParam);
214
215 if (bBitmapCreated && !bOnlyCreateBitmap)
216 {
218 // ppAccess must be set if this flag is used.
219 assert(ppAccess);
220 else
221 rContext.pScopedAccess.reset(new BitmapScopedWriteAccess(pJPEGReader->GetBitmap()));
222
223 BitmapScopedWriteAccess& pAccess = bUseExistingBitmap ? *ppAccess : *rContext.pScopedAccess;
224
225 if (pAccess)
226 {
227 int nPixelSize = 3;
228 J_COLOR_SPACE best_out_color_space = JCS_RGB;
230 ScanlineFormat eFinalFormat = pAccess->GetScanlineFormat();
231
232 if (bGray)
233 {
234 best_out_color_space = JCS_GRAYSCALE;
235 eScanlineFormat = ScanlineFormat::N8BitPal;
236 nPixelSize = 1;
237 }
238#if defined(JCS_EXTENSIONS)
239 else if (eFinalFormat == ScanlineFormat::N24BitTcBgr)
240 {
241 best_out_color_space = JCS_EXT_BGR;
242 eScanlineFormat = eFinalFormat;
243 nPixelSize = 3;
244 }
245 else if (eFinalFormat == ScanlineFormat::N32BitTcBgra)
246 {
247 best_out_color_space = JCS_EXT_BGRA;
248 eScanlineFormat = eFinalFormat;
249 nPixelSize = 4;
250 }
251 else if (eFinalFormat == ScanlineFormat::N32BitTcRgba)
252 {
253 best_out_color_space = JCS_EXT_RGBA;
254 eScanlineFormat = eFinalFormat;
255 nPixelSize = 4;
256 }
257 else if (eFinalFormat == ScanlineFormat::N32BitTcArgb)
258 {
259 best_out_color_space = JCS_EXT_ARGB;
260 eScanlineFormat = eFinalFormat;
261 nPixelSize = 4;
262 }
263#endif
264 if (rContext.cinfo.jpeg_color_space == JCS_YCCK)
265 rContext.cinfo.out_color_space = JCS_CMYK;
266
267 if (rContext.cinfo.out_color_space != JCS_CMYK)
268 rContext.cinfo.out_color_space = best_out_color_space;
269
270 jpeg_start_decompress(&rContext.cinfo);
271
272 JSAMPLE* aRangeLimit = rContext.cinfo.sample_range_limit;
273
274 rContext.pScanLineBuffer.resize(nWidth * nPixelSize);
275
276 if (rContext.cinfo.out_color_space == JCS_CMYK)
277 {
278 rContext.pCYMKBuffer.resize(nWidth * 4);
279 }
280
281 // tdf#138950 allow up to one short read (no_data_available_failures <= 1) to not trigger cancelling import
282 for (*pLines = 0; *pLines < nHeight && source->no_data_available_failures <= 1; (*pLines)++)
283 {
284 size_t yIndex = *pLines;
285
286 sal_uInt8* p = (rContext.cinfo.out_color_space == JCS_CMYK) ? rContext.pCYMKBuffer.data() : rContext.pScanLineBuffer.data();
287 jpeg_read_scanlines(&rContext.cinfo, reinterpret_cast<JSAMPARRAY>(&p), 1);
288
289 if (rContext.cinfo.out_color_space == JCS_CMYK)
290 {
291 // convert CMYK to RGB
292 Scanline pScanline = pAccess->GetScanline(yIndex);
293 for (tools::Long cmyk = 0, x = 0; cmyk < nWidth * 4; cmyk += 4, ++x)
294 {
295 int color_C = 255 - rContext.pCYMKBuffer[cmyk + 0];
296 int color_M = 255 - rContext.pCYMKBuffer[cmyk + 1];
297 int color_Y = 255 - rContext.pCYMKBuffer[cmyk + 2];
298 int color_K = 255 - rContext.pCYMKBuffer[cmyk + 3];
299
300 sal_uInt8 cRed = aRangeLimit[255L - (color_C + color_K)];
301 sal_uInt8 cGreen = aRangeLimit[255L - (color_M + color_K)];
302 sal_uInt8 cBlue = aRangeLimit[255L - (color_Y + color_K)];
303
304 pAccess->SetPixelOnData(pScanline, x, BitmapColor(cRed, cGreen, cBlue));
305 }
306 }
307 else
308 {
309 pAccess->CopyScanline(yIndex, rContext.pScanLineBuffer.data(), eScanlineFormat, rContext.pScanLineBuffer.size());
310 }
311
312 /* PENDING ??? */
313 if (rContext.cinfo.err->msg_code == 113)
314 break;
315 }
316
317 rContext.pScanLineBuffer.clear();
318 rContext.pCYMKBuffer.clear();
319 }
320 rContext.pScopedAccess.reset();
321 }
322
323 if (bBitmapCreated && !bOnlyCreateBitmap)
324 {
325 jpeg_finish_decompress(&rContext.cinfo);
326 }
327 else
328 {
329 jpeg_abort_decompress(&rContext.cinfo);
330 }
331}
332
333void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines,
334 GraphicFilterImportFlags nImportFlags,
335 BitmapScopedWriteAccess* ppAccess )
336{
337 JpegStuff aContext;
338 ReadJPEG(aContext, pJPEGReader, pInputStream, pLines, nImportFlags, ppAccess);
339}
340
341bool WriteJPEG( JPEGWriter* pJPEGWriter, void* pOutputStream,
342 tools::Long nWidth, tools::Long nHeight, basegfx::B2DSize const & rPPI, bool bGreys,
343 tools::Long nQualityPercent, tools::Long aChromaSubsampling,
344 css::uno::Reference<css::task::XStatusIndicator> const & status )
345{
346 jpeg_compress_struct cinfo;
347 ErrorManagerStruct jerr;
348 void* pScanline;
349 tools::Long nY;
350
351 JpegCompressOwner aOwner;
352
353 if ( setjmp( jerr.setjmp_buffer ) )
354 {
355 return false;
356 }
357
358 cinfo.err = jpeg_std_error( &jerr.pub );
359 jerr.pub.error_exit = errorExit;
360 jerr.pub.output_message = outputMessage;
361
362 jpeg_create_compress( &cinfo );
363 aOwner.set(&cinfo);
364 jpeg_svstream_dest( &cinfo, pOutputStream );
365
366 cinfo.image_width = static_cast<JDIMENSION>(nWidth);
367 cinfo.image_height = static_cast<JDIMENSION>(nHeight);
368 if ( bGreys )
369 {
370 cinfo.input_components = 1;
371 cinfo.in_color_space = JCS_GRAYSCALE;
372 }
373 else
374 {
375 cinfo.input_components = 3;
376 cinfo.in_color_space = JCS_RGB;
377 }
378
379 jpeg_set_defaults( &cinfo );
380 jpeg_set_quality( &cinfo, static_cast<int>(nQualityPercent), FALSE );
381
382 if (o3tl::convertsToAtMost(rPPI.getX(), 65535) && o3tl::convertsToAtMost(rPPI.getY(), 65535))
383 {
384 cinfo.density_unit = 1;
385 cinfo.X_density = rPPI.getX();
386 cinfo.Y_density = rPPI.getY();
387 }
388 else
389 {
390 SAL_WARN("vcl.filter", "ignoring too large PPI " << rPPI);
391 }
392
393 if ( ( nWidth > 128 ) || ( nHeight > 128 ) )
394 jpeg_simple_progression( &cinfo );
395
396 if (aChromaSubsampling == 1) // YUV 4:4:4
397 {
398 cinfo.comp_info[0].h_samp_factor = 1;
399 cinfo.comp_info[0].v_samp_factor = 1;
400 }
401 else if (aChromaSubsampling == 2) // YUV 4:2:2
402 {
403 cinfo.comp_info[0].h_samp_factor = 2;
404 cinfo.comp_info[0].v_samp_factor = 1;
405 }
406 else if (aChromaSubsampling == 3) // YUV 4:2:0
407 {
408 cinfo.comp_info[0].h_samp_factor = 2;
409 cinfo.comp_info[0].v_samp_factor = 2;
410 }
411
412 jpeg_start_compress( &cinfo, TRUE );
413
414 for( nY = 0; nY < nHeight; nY++ )
415 {
416 pScanline = pJPEGWriter->GetScanline( nY );
417
418 if( pScanline )
419 {
420 jpeg_write_scanlines( &cinfo, reinterpret_cast<JSAMPARRAY>(&pScanline), 1 );
421 }
422
423 if( status.is() )
424 {
425 status->setValue( nY * 100L / nHeight );
426 }
427 }
428
429 jpeg_finish_compress(&cinfo);
430
431 return true;
432}
433
434void Transform(void* pInputStream, void* pOutputStream, Degree10 nAngle)
435{
436 jpeg_transform_info aTransformOption;
437 JCOPY_OPTION aCopyOption = JCOPYOPT_ALL;
438
439 jpeg_decompress_struct aSourceInfo;
440 jpeg_compress_struct aDestinationInfo;
441 ErrorManagerStruct aSourceError;
442 ErrorManagerStruct aDestinationError;
443
444 jvirt_barray_ptr* aSourceCoefArrays = nullptr;
445 jvirt_barray_ptr* aDestinationCoefArrays = nullptr;
446
447 aTransformOption.force_grayscale = FALSE;
448 aTransformOption.trim = FALSE;
449 aTransformOption.perfect = FALSE;
450 aTransformOption.crop = FALSE;
451
452 // Angle to transform option
453 // 90 Clockwise = 270 Counterclockwise
454 switch (nAngle.get())
455 {
456 case 2700:
457 aTransformOption.transform = JXFORM_ROT_90;
458 break;
459 case 1800:
460 aTransformOption.transform = JXFORM_ROT_180;
461 break;
462 case 900:
463 aTransformOption.transform = JXFORM_ROT_270;
464 break;
465 default:
466 aTransformOption.transform = JXFORM_NONE;
467 }
468
469 // Decompression
470 aSourceInfo.err = jpeg_std_error(&aSourceError.pub);
471 aSourceInfo.err->error_exit = errorExit;
472 aSourceInfo.err->output_message = outputMessage;
473
474 // Compression
475 aDestinationInfo.err = jpeg_std_error(&aDestinationError.pub);
476 aDestinationInfo.err->error_exit = errorExit;
477 aDestinationInfo.err->output_message = outputMessage;
478
479 aDestinationInfo.optimize_coding = TRUE;
480
481 JpegDecompressOwner aDecompressOwner;
482 JpegCompressOwner aCompressOwner;
483
484 if (setjmp(aSourceError.setjmp_buffer))
485 {
486 jpeg_destroy_decompress(&aSourceInfo);
487 jpeg_destroy_compress(&aDestinationInfo);
488 return;
489 }
490 if (setjmp(aDestinationError.setjmp_buffer))
491 {
492 jpeg_destroy_decompress(&aSourceInfo);
493 jpeg_destroy_compress(&aDestinationInfo);
494 return;
495 }
496
497 jpeg_create_decompress(&aSourceInfo);
498 aDecompressOwner.set(&aSourceInfo);
499 jpeg_create_compress(&aDestinationInfo);
500 aCompressOwner.set(&aDestinationInfo);
501
502 jpeg_svstream_src (&aSourceInfo, pInputStream);
503
504 jcopy_markers_setup(&aSourceInfo, aCopyOption);
505 jpeg_read_header(&aSourceInfo, TRUE);
506 jtransform_request_workspace(&aSourceInfo, &aTransformOption);
507
508 aSourceCoefArrays = jpeg_read_coefficients(&aSourceInfo);
509 jpeg_copy_critical_parameters(&aSourceInfo, &aDestinationInfo);
510
511 aDestinationCoefArrays = jtransform_adjust_parameters(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
512 jpeg_svstream_dest (&aDestinationInfo, pOutputStream);
513
514 // Compute optimal Huffman coding tables instead of precomputed tables
515 aDestinationInfo.optimize_coding = TRUE;
516 jpeg_write_coefficients(&aDestinationInfo, aDestinationCoefArrays);
517 jcopy_markers_execute(&aSourceInfo, &aDestinationInfo, aCopyOption);
518 jtransform_execute_transformation(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
519
520 jpeg_finish_compress(&aDestinationInfo);
521
522 jpeg_finish_decompress(&aSourceInfo);
523}
524
525/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
vcl::ScopedBitmapAccess< BitmapWriteAccess, Bitmap, &Bitmap::AcquireWriteAccess > BitmapScopedWriteAccess
sal_uInt8 * Scanline
Definition: Scanline.hxx:26
ScanlineFormat
Definition: Scanline.hxx:29
bool CreateBitmap(JPEGCreateBitmapParam const &param)
Definition: JpegReader.cxx:191
Bitmap & GetBitmap()
Definition: JpegReader.hxx:69
void * GetScanline(tools::Long nY)
Definition: JpegWriter.cxx:139
TYPE getX() const
TYPE getY() const
static bool IsFuzzing()
This template handles BitmapAccess the RAII way.
float x
GraphicFilterImportFlags
@ UseExistingBitmap
Read pixel data into an existing bitmap.
@ OnlyCreateBitmap
Only create a bitmap, do not read pixel data.
#define TRUE
#define FALSE
void * p
void jpeg_svstream_dest(j_compress_ptr cinfo, void *outfile)
Definition: JpegWriter.cxx:92
void jpeg_svstream_src(j_decompress_ptr cinfo, void *infile)
Definition: JpegReader.cxx:139
static void errorExit(j_common_ptr cinfo)
Definition: jpegc.cxx:63
static void ReadJPEG(JpegStuff &rContext, JPEGReader *pJPEGReader, void *pInputStream, tools::Long *pLines, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess *ppAccess)
Definition: jpegc.cxx:157
void Transform(void *pInputStream, void *pOutputStream, Degree10 nAngle)
Definition: jpegc.cxx:434
static void emitMessage(j_common_ptr cinfo, int msg_level)
Definition: jpegc.cxx:88
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)
Definition: jpegc.cxx:341
static int GetWarningLimit()
Definition: jpegc.cxx:81
static void outputMessage(j_common_ptr cinfo)
Definition: jpegc.cxx:72
#define SAL_WARN(area, stream)
void set(css::uno::UnoInterfaceReference const &value)
std::enable_if< std::is_signed< T >::value, bool >::type checked_multiply(T a, T b, T &result)
constexpr std::enable_if_t< std::is_floating_point_v< F > &&std::is_integral_v< I >, bool > convertsToAtMost(F value, I max)
long Long
tools::ULong density_unit
Definition: JpegReader.hxx:42
tools::ULong nHeight
Definition: JpegReader.hxx:41
tools::ULong Y_density
Definition: JpegReader.hxx:44
tools::ULong nWidth
Definition: JpegReader.hxx:40
tools::ULong X_density
Definition: JpegReader.hxx:43
int no_data_available_failures
Definition: jpeg.h:62
boolean force_grayscale
Definition: transupp.h:130
JXFORM_CODE transform
Definition: transupp.h:127
UNDERLYING_TYPE get() const
jcopy_markers_setup(j_decompress_ptr srcinfo, JCOPY_OPTION option)
Definition: transupp.c:1499
jcopy_markers_execute(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, JCOPY_OPTION option)
Definition: transupp.c:1526
@ JXFORM_ROT_270
Definition: transupp.h:103
@ JXFORM_NONE
Definition: transupp.h:96
@ JXFORM_ROT_180
Definition: transupp.h:102
@ JXFORM_ROT_90
Definition: transupp.h:101
JCOPY_OPTION
Definition: transupp.h:199
@ JCOPYOPT_ALL
Definition: transupp.h:202
unsigned char sal_uInt8