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>
22 #include <o3tl/safeint.hxx>
23 
24 #include <stdio.h>
25 #include <setjmp.h>
26 #include <jpeglib.h>
27 
28 #include <com/sun/star/task/XStatusIndicator.hpp>
29 
30 extern "C" {
31 #include "transupp.h"
32 }
33 
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>
40 
41 #ifdef _MSC_VER
42 #pragma warning(push)
43 #pragma warning (disable: 4324) /* disable to __declspec(align()) aligned warning */
44 #endif
45 
47 {
48  jpeg_error_mgr pub;
49  jmp_buf setjmp_buffer;
50 };
51 
52 #ifdef _MSC_VER
53 #pragma warning(pop)
54 #endif
55 
56 extern "C" {
57 
58 static void errorExit (j_common_ptr cinfo)
59 {
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);
65 }
66 
67 static void outputMessage (j_common_ptr cinfo)
68 {
69  char buffer[JMSG_LENGTH_MAX];
70  (*cinfo->err->format_message) (cinfo, buffer);
71  SAL_WARN("vcl.filter", "failure reading JPEG: " << buffer);
72 }
73 
74 }
75 
76 static int GetWarningLimit()
77 {
78  return utl::ConfigManager::IsFuzzing() ? 5 : 1000;
79 }
80 
81 extern "C" {
82 
83 static void emitMessage (j_common_ptr cinfo, int msg_level)
84 {
85  if (msg_level < 0)
86  {
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);
95  }
96  else if (cinfo->err->trace_level >= msg_level)
97  cinfo->err->output_message(cinfo);
98 }
99 
100 }
101 
103 {
104 public:
105  void set(jpeg_decompress_struct *cinfo)
106  {
107  m_cinfo = cinfo;
108  }
110  {
111  if (m_cinfo != nullptr)
112  {
113  jpeg_destroy_decompress(m_cinfo);
114  }
115  }
116 private:
117  jpeg_decompress_struct *m_cinfo = nullptr;
118 };
119 
121 {
122 public:
123  void set(jpeg_compress_struct *cinfo)
124  {
125  m_cinfo = cinfo;
126  }
128  {
129  if (m_cinfo != nullptr)
130  {
131  jpeg_destroy_compress(m_cinfo);
132  }
133  }
134 private:
135  jpeg_compress_struct *m_cinfo = nullptr;
136 };
137 
138 struct JpegStuff
139 {
140  jpeg_decompress_struct cinfo;
143  std::unique_ptr<BitmapScopedWriteAccess> pScopedAccess;
144  std::vector<sal_uInt8> pScanLineBuffer;
145  std::vector<sal_uInt8> pCYMKBuffer;
146 };
147 
148 static void ReadJPEG(JpegStuff& rContext, JPEGReader* pJPEGReader, void* pInputStream, long* pLines,
149  Size const & previewSize, GraphicFilterImportFlags nImportFlags,
150  BitmapScopedWriteAccess* ppAccess)
151 {
152  if (setjmp(rContext.jerr.setjmp_buffer))
153  {
154  return;
155  }
156 
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;
161 
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);
167 
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;
173 
174  /* change scale for preview import */
175  long nPreviewWidth = previewSize.Width();
176  long nPreviewHeight = previewSize.Height();
177  if( nPreviewWidth || nPreviewHeight )
178  {
179  if( nPreviewWidth == 0 )
180  {
181  nPreviewWidth = (rContext.cinfo.image_width * nPreviewHeight) / rContext.cinfo.image_height;
182  if( nPreviewWidth <= 0 )
183  {
184  nPreviewWidth = 1;
185  }
186  }
187  else if( nPreviewHeight == 0 )
188  {
189  nPreviewHeight = (rContext.cinfo.image_height * nPreviewWidth) / rContext.cinfo.image_width;
190  if( nPreviewHeight <= 0 )
191  {
192  nPreviewHeight = 1;
193  }
194  }
195 
196  for (rContext.cinfo.scale_denom = 1; rContext.cinfo.scale_denom < 8; rContext.cinfo.scale_denom *= 2)
197  {
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;
202  }
203 
204  if (rContext.cinfo.scale_denom > 1)
205  {
206  rContext.cinfo.dct_method = JDCT_FASTEST;
207  rContext.cinfo.do_fancy_upsampling = FALSE;
208  rContext.cinfo.do_block_smoothing = FALSE;
209  }
210  }
211 
212  jpeg_calc_output_dimensions(&rContext.cinfo);
213 
214  long nWidth = rContext.cinfo.output_width;
215  long nHeight = rContext.cinfo.output_height;
216 
217  long nResult = 0;
218  if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000))
219  return;
220 
221  bool bGray = (rContext.cinfo.output_components == 1);
222 
223  JPEGCreateBitmapParam aCreateBitmapParam;
224 
225  aCreateBitmapParam.nWidth = nWidth;
226  aCreateBitmapParam.nHeight = nHeight;
227 
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;
232 
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);
238 
239  if (bBitmapCreated && !bOnlyCreateBitmap)
240  {
242  // ppAccess must be set if this flag is used.
243  assert(ppAccess);
244  else
245  rContext.pScopedAccess.reset(new BitmapScopedWriteAccess(pJPEGReader->GetBitmap()));
246 
247  BitmapScopedWriteAccess& pAccess = bUseExistingBitmap ? *ppAccess : *rContext.pScopedAccess;
248 
249  if (pAccess)
250  {
251  int nPixelSize = 3;
252  J_COLOR_SPACE best_out_color_space = JCS_RGB;
254  ScanlineFormat eFinalFormat = pAccess->GetScanlineFormat();
255 
256  if (bGray)
257  {
258  best_out_color_space = JCS_GRAYSCALE;
259  eScanlineFormat = ScanlineFormat::N8BitPal;
260  nPixelSize = 1;
261  }
262 #if defined(JCS_EXTENSIONS)
263  else if (eFinalFormat == ScanlineFormat::N32BitTcBgra)
264  {
265  best_out_color_space = JCS_EXT_BGRA;
266  eScanlineFormat = eFinalFormat;
267  nPixelSize = 4;
268  }
269  else if (eFinalFormat == ScanlineFormat::N32BitTcRgba)
270  {
271  best_out_color_space = JCS_EXT_RGBA;
272  eScanlineFormat = eFinalFormat;
273  nPixelSize = 4;
274  }
275  else if (eFinalFormat == ScanlineFormat::N32BitTcArgb)
276  {
277  best_out_color_space = JCS_EXT_ARGB;
278  eScanlineFormat = eFinalFormat;
279  nPixelSize = 4;
280  }
281 #endif
282  if (rContext.cinfo.jpeg_color_space == JCS_YCCK)
283  rContext.cinfo.out_color_space = JCS_CMYK;
284 
285  if (rContext.cinfo.out_color_space != JCS_CMYK)
286  rContext.cinfo.out_color_space = best_out_color_space;
287 
288  jpeg_start_decompress(&rContext.cinfo);
289 
290  JSAMPLE* aRangeLimit = rContext.cinfo.sample_range_limit;
291 
292  rContext.pScanLineBuffer.resize(nWidth * nPixelSize);
293 
294  if (rContext.cinfo.out_color_space == JCS_CMYK)
295  {
296  rContext.pCYMKBuffer.resize(nWidth * 4);
297  }
298 
299  for (*pLines = 0; *pLines < nHeight && !source->no_data_available; (*pLines)++)
300  {
301  size_t yIndex = *pLines;
302 
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);
305 
306  if (rContext.cinfo.out_color_space == JCS_CMYK)
307  {
308  // convert CMYK to RGB
309  Scanline pScanline = pAccess->GetScanline(yIndex);
310  for (long cmyk = 0, x = 0; cmyk < nWidth * 4; cmyk += 4, ++x)
311  {
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];
316 
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)];
320 
321  pAccess->SetPixelOnData(pScanline, x, BitmapColor(cRed, cGreen, cBlue));
322  }
323  }
324  else
325  {
326  pAccess->CopyScanline(yIndex, rContext.pScanLineBuffer.data(), eScanlineFormat, rContext.pScanLineBuffer.size());
327  }
328 
329  /* PENDING ??? */
330  if (rContext.cinfo.err->msg_code == 113)
331  break;
332  }
333 
334  rContext.pScanLineBuffer.clear();
335  rContext.pCYMKBuffer.clear();
336  }
337  rContext.pScopedAccess.reset();
338  }
339 
340  if (bBitmapCreated && !bOnlyCreateBitmap)
341  {
342  jpeg_finish_decompress(&rContext.cinfo);
343  }
344  else
345  {
346  jpeg_abort_decompress(&rContext.cinfo);
347  }
348 }
349 
350 void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, long* pLines,
351  Size const & previewSize, GraphicFilterImportFlags nImportFlags,
352  BitmapScopedWriteAccess* ppAccess )
353 {
354  JpegStuff aContext;
355  ReadJPEG(aContext, pJPEGReader, pInputStream, pLines, previewSize, nImportFlags, ppAccess);
356 }
357 
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 )
362 {
363  jpeg_compress_struct cinfo;
364  ErrorManagerStruct jerr;
365  void* pScanline;
366  long nY;
367 
368  JpegCompressOwner aOwner;
369 
370  if ( setjmp( jerr.setjmp_buffer ) )
371  {
372  return false;
373  }
374 
375  cinfo.err = jpeg_std_error( &jerr.pub );
376  jerr.pub.error_exit = errorExit;
377  jerr.pub.output_message = outputMessage;
378 
379  jpeg_create_compress( &cinfo );
380  aOwner.set(&cinfo);
381  jpeg_svstream_dest( &cinfo, pOutputStream );
382 
383  cinfo.image_width = static_cast<JDIMENSION>(nWidth);
384  cinfo.image_height = static_cast<JDIMENSION>(nHeight);
385  if ( bGreys )
386  {
387  cinfo.input_components = 1;
388  cinfo.in_color_space = JCS_GRAYSCALE;
389  }
390  else
391  {
392  cinfo.input_components = 3;
393  cinfo.in_color_space = JCS_RGB;
394  }
395 
396  jpeg_set_defaults( &cinfo );
397  jpeg_set_quality( &cinfo, static_cast<int>(nQualityPercent), FALSE );
398 
399  cinfo.density_unit = 1;
400  cinfo.X_density = rPPI.getX();
401  cinfo.Y_density = rPPI.getY();
402 
403  if ( ( nWidth > 128 ) || ( nHeight > 128 ) )
404  jpeg_simple_progression( &cinfo );
405 
406  if (aChromaSubsampling == 1) // YUV 4:4:4
407  {
408  cinfo.comp_info[0].h_samp_factor = 1;
409  cinfo.comp_info[0].v_samp_factor = 1;
410  }
411  else if (aChromaSubsampling == 2) // YUV 4:2:2
412  {
413  cinfo.comp_info[0].h_samp_factor = 2;
414  cinfo.comp_info[0].v_samp_factor = 1;
415  }
416  else if (aChromaSubsampling == 3) // YUV 4:2:0
417  {
418  cinfo.comp_info[0].h_samp_factor = 2;
419  cinfo.comp_info[0].v_samp_factor = 2;
420  }
421 
422  jpeg_start_compress( &cinfo, TRUE );
423 
424  for( nY = 0; nY < nHeight; nY++ )
425  {
426  pScanline = pJPEGWriter->GetScanline( nY );
427 
428  if( pScanline )
429  {
430  jpeg_write_scanlines( &cinfo, reinterpret_cast<JSAMPARRAY>(&pScanline), 1 );
431  }
432 
433  if( status.is() )
434  {
435  status->setValue( nY * 100L / nHeight );
436  }
437  }
438 
439  jpeg_finish_compress(&cinfo);
440 
441  return true;
442 }
443 
444 void Transform(void* pInputStream, void* pOutputStream, long nAngle)
445 {
446  jpeg_transform_info aTransformOption;
447  JCOPY_OPTION aCopyOption = JCOPYOPT_ALL;
448 
449  jpeg_decompress_struct aSourceInfo;
450  jpeg_compress_struct aDestinationInfo;
451  ErrorManagerStruct aSourceError;
452  ErrorManagerStruct aDestinationError;
453 
454  jvirt_barray_ptr* aSourceCoefArrays = nullptr;
455  jvirt_barray_ptr* aDestinationCoefArrays = nullptr;
456 
457  aTransformOption.force_grayscale = FALSE;
458  aTransformOption.trim = FALSE;
459  aTransformOption.perfect = FALSE;
460  aTransformOption.crop = FALSE;
461 
462  // Angle to transform option
463  // 90 Clockwise = 270 Counterclockwise
464  switch (nAngle)
465  {
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;
477  }
478 
479  // Decompression
480  aSourceInfo.err = jpeg_std_error(&aSourceError.pub);
481  aSourceInfo.err->error_exit = errorExit;
482  aSourceInfo.err->output_message = outputMessage;
483 
484  // Compression
485  aDestinationInfo.err = jpeg_std_error(&aDestinationError.pub);
486  aDestinationInfo.err->error_exit = errorExit;
487  aDestinationInfo.err->output_message = outputMessage;
488 
489  aDestinationInfo.optimize_coding = TRUE;
490 
491  JpegDecompressOwner aDecompressOwner;
492  JpegCompressOwner aCompressOwner;
493 
494  if (setjmp(aSourceError.setjmp_buffer) || setjmp(aDestinationError.setjmp_buffer))
495  {
496  jpeg_destroy_decompress(&aSourceInfo);
497  jpeg_destroy_compress(&aDestinationInfo);
498  return;
499  }
500 
501  jpeg_create_decompress(&aSourceInfo);
502  aDecompressOwner.set(&aSourceInfo);
503  jpeg_create_compress(&aDestinationInfo);
504  aCompressOwner.set(&aDestinationInfo);
505 
506  jpeg_svstream_src (&aSourceInfo, pInputStream);
507 
508  jcopy_markers_setup(&aSourceInfo, aCopyOption);
509  jpeg_read_header(&aSourceInfo, TRUE);
510  jtransform_request_workspace(&aSourceInfo, &aTransformOption);
511 
512  aSourceCoefArrays = jpeg_read_coefficients(&aSourceInfo);
513  jpeg_copy_critical_parameters(&aSourceInfo, &aDestinationInfo);
514 
515  aDestinationCoefArrays = jtransform_adjust_parameters(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
516  jpeg_svstream_dest (&aDestinationInfo, pOutputStream);
517 
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);
523 
524  jpeg_finish_compress(&aDestinationInfo);
525 
526  jpeg_finish_decompress(&aSourceInfo);
527 }
528 
529 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
long Width() const
static void errorExit(j_common_ptr cinfo)
Definition: jpegc.cxx:58
Only create a bitmap, do not read pixel data.
Read pixel data into an existing bitmap.
bool CreateBitmap(JPEGCreateBitmapParam const &param)
Definition: JpegReader.cxx:191
static void ReadJPEG(JpegStuff &rContext, JPEGReader *pJPEGReader, void *pInputStream, long *pLines, Size const &previewSize, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess *ppAccess)
Definition: jpegc.cxx:148
long Height() const
void set(jpeg_compress_struct *cinfo)
Definition: jpegc.cxx:123
std::unique_ptr< BitmapScopedWriteAccess > pScopedAccess
Definition: jpegc.cxx:143
double getX() const
unsigned long X_density
Definition: JpegReader.hxx:41
vcl::ScopedBitmapAccess< BitmapWriteAccess, Bitmap,&Bitmap::AcquireWriteAccess > BitmapScopedWriteAccess
unsigned long density_unit
Definition: JpegReader.hxx:40
double getY() const
void * GetScanline(long nY)
Definition: JpegWriter.cxx:135
This template handles BitmapAccess the RAII way.
float x
void set(jpeg_decompress_struct *cinfo)
Definition: jpegc.cxx:105
ScanlineFormat
Definition: Scanline.hxx:28
unsigned long nWidth
Definition: JpegReader.hxx:38
void Transform(void *pInputStream, void *pOutputStream, long nAngle)
Definition: jpegc.cxx:444
static bool IsFuzzing()
jpeg_error_mgr pub
Definition: jpegc.cxx:48
jcopy_markers_execute(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, JCOPY_OPTION option)
Definition: transupp.c:1526
unsigned long Y_density
Definition: JpegReader.hxx:42
void jpeg_svstream_dest(j_compress_ptr cinfo, void *outfile)
Definition: JpegWriter.cxx:88
void jpeg_svstream_src(j_decompress_ptr cinfo, void *infile)
Definition: JpegReader.cxx:139
static void outputMessage(j_common_ptr cinfo)
Definition: jpegc.cxx:67
sal_uInt8 * Scanline
Definition: Scanline.hxx:25
boolean force_grayscale
Definition: transupp.h:130
JXFORM_CODE transform
Definition: transupp.h:127
#define TRUE
std::enable_if< std::is_signed< T >::value, bool >::type checked_multiply(T a, T b, T &result)
unsigned long nHeight
Definition: JpegReader.hxx:39
JpegDecompressOwner aOwner
Definition: jpegc.cxx:142
Bitmap & GetBitmap()
Definition: JpegReader.hxx:67
std::vector< sal_uInt8 > pCYMKBuffer
Definition: jpegc.cxx:145
std::vector< sal_uInt8 > pScanLineBuffer
Definition: jpegc.cxx:144
jpeg_compress_struct * m_cinfo
Definition: jpegc.cxx:135
jmp_buf setjmp_buffer
Definition: jpegc.cxx:49
ErrorManagerStruct jerr
Definition: jpegc.cxx:141
unsigned char sal_uInt8
jcopy_markers_setup(j_decompress_ptr srcinfo, JCOPY_OPTION option)
Definition: transupp.c:1499
static int GetWarningLimit()
Definition: jpegc.cxx:76
static void emitMessage(j_common_ptr cinfo, int msg_level)
Definition: jpegc.cxx:83
#define FALSE
jpeg_decompress_struct * m_cinfo
Definition: jpegc.cxx:117
#define SAL_WARN(area, stream)
boolean no_data_available
Definition: jpeg.h:62
GraphicFilterImportFlags
JCOPY_OPTION
Definition: transupp.h:199
bool WriteJPEG(JPEGWriter *pJPEGWriter, void *pOutputStream, long nWidth, long nHeight, basegfx::B2DSize const &rPPI, bool bGreys, long nQualityPercent, long aChromaSubsampling, css::uno::Reference< css::task::XStatusIndicator > const &status)
Definition: jpegc.cxx:358
jpeg_decompress_struct cinfo
Definition: jpegc.cxx:140