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 
31 extern "C" {
32 #include "transupp.h"
33 }
34 
35 #include "jpeg.h"
36 #include "JpegReader.hxx"
37 #include "JpegWriter.hxx"
38 #include <memory>
39 #include <unotools/configmgr.hxx>
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 
47 namespace {
48 
49 struct 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 
61 extern "C" {
62 
63 static 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 
72 static 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 
81 static int GetWarningLimit()
82 {
83  return utl::ConfigManager::IsFuzzing() ? 5 : 1000;
84 }
85 
86 extern "C" {
87 
88 static 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 
107 namespace {
108 
109 class JpegDecompressOwner
110 {
111 public:
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  }
123 private:
124  jpeg_decompress_struct *m_cinfo = nullptr;
125 };
126 
127 class JpegCompressOwner
128 {
129 public:
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  }
141 private:
142  jpeg_compress_struct *m_cinfo = nullptr;
143 };
144 
145 struct 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 
157 static void ReadJPEG(JpegStuff& rContext, JPEGReader* pJPEGReader, void* pInputStream, 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  long nWidth = rContext.cinfo.output_width;
186  long nHeight = rContext.cinfo.output_height;
187 
188  long nResult = 0;
189  if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000))
190  return;
191 
192  bool bGray = (rContext.cinfo.output_components == 1);
193 
194  JPEGCreateBitmapParam aCreateBitmapParam;
195 
196  aCreateBitmapParam.nWidth = nWidth;
197  aCreateBitmapParam.nHeight = nHeight;
198 
199  aCreateBitmapParam.density_unit = rContext.cinfo.density_unit;
200  aCreateBitmapParam.X_density = rContext.cinfo.X_density;
201  aCreateBitmapParam.Y_density = rContext.cinfo.Y_density;
202  aCreateBitmapParam.bGray = bGray;
203 
204  const auto bOnlyCreateBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap);
205  const auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
206  bool bBitmapCreated = bUseExistingBitmap;
207  if (!bBitmapCreated)
208  bBitmapCreated = pJPEGReader->CreateBitmap(aCreateBitmapParam);
209 
210  if (bBitmapCreated && !bOnlyCreateBitmap)
211  {
213  // ppAccess must be set if this flag is used.
214  assert(ppAccess);
215  else
216  rContext.pScopedAccess.reset(new BitmapScopedWriteAccess(pJPEGReader->GetBitmap()));
217 
218  BitmapScopedWriteAccess& pAccess = bUseExistingBitmap ? *ppAccess : *rContext.pScopedAccess;
219 
220  if (pAccess)
221  {
222  int nPixelSize = 3;
223  J_COLOR_SPACE best_out_color_space = JCS_RGB;
225  ScanlineFormat eFinalFormat = pAccess->GetScanlineFormat();
226 
227  if (bGray)
228  {
229  best_out_color_space = JCS_GRAYSCALE;
230  eScanlineFormat = ScanlineFormat::N8BitPal;
231  nPixelSize = 1;
232  }
233 #if defined(JCS_EXTENSIONS)
234  else if (eFinalFormat == ScanlineFormat::N32BitTcBgra)
235  {
236  best_out_color_space = JCS_EXT_BGRA;
237  eScanlineFormat = eFinalFormat;
238  nPixelSize = 4;
239  }
240  else if (eFinalFormat == ScanlineFormat::N32BitTcRgba)
241  {
242  best_out_color_space = JCS_EXT_RGBA;
243  eScanlineFormat = eFinalFormat;
244  nPixelSize = 4;
245  }
246  else if (eFinalFormat == ScanlineFormat::N32BitTcArgb)
247  {
248  best_out_color_space = JCS_EXT_ARGB;
249  eScanlineFormat = eFinalFormat;
250  nPixelSize = 4;
251  }
252 #endif
253  if (rContext.cinfo.jpeg_color_space == JCS_YCCK)
254  rContext.cinfo.out_color_space = JCS_CMYK;
255 
256  if (rContext.cinfo.out_color_space != JCS_CMYK)
257  rContext.cinfo.out_color_space = best_out_color_space;
258 
259  jpeg_start_decompress(&rContext.cinfo);
260 
261  JSAMPLE* aRangeLimit = rContext.cinfo.sample_range_limit;
262 
263  rContext.pScanLineBuffer.resize(nWidth * nPixelSize);
264 
265  if (rContext.cinfo.out_color_space == JCS_CMYK)
266  {
267  rContext.pCYMKBuffer.resize(nWidth * 4);
268  }
269 
270  for (*pLines = 0; *pLines < nHeight && !source->no_data_available; (*pLines)++)
271  {
272  size_t yIndex = *pLines;
273 
274  sal_uInt8* p = (rContext.cinfo.out_color_space == JCS_CMYK) ? rContext.pCYMKBuffer.data() : rContext.pScanLineBuffer.data();
275  jpeg_read_scanlines(&rContext.cinfo, reinterpret_cast<JSAMPARRAY>(&p), 1);
276 
277  if (rContext.cinfo.out_color_space == JCS_CMYK)
278  {
279  // convert CMYK to RGB
280  Scanline pScanline = pAccess->GetScanline(yIndex);
281  for (long cmyk = 0, x = 0; cmyk < nWidth * 4; cmyk += 4, ++x)
282  {
283  int color_C = 255 - rContext.pCYMKBuffer[cmyk + 0];
284  int color_M = 255 - rContext.pCYMKBuffer[cmyk + 1];
285  int color_Y = 255 - rContext.pCYMKBuffer[cmyk + 2];
286  int color_K = 255 - rContext.pCYMKBuffer[cmyk + 3];
287 
288  sal_uInt8 cRed = aRangeLimit[255L - (color_C + color_K)];
289  sal_uInt8 cGreen = aRangeLimit[255L - (color_M + color_K)];
290  sal_uInt8 cBlue = aRangeLimit[255L - (color_Y + color_K)];
291 
292  pAccess->SetPixelOnData(pScanline, x, BitmapColor(cRed, cGreen, cBlue));
293  }
294  }
295  else
296  {
297  pAccess->CopyScanline(yIndex, rContext.pScanLineBuffer.data(), eScanlineFormat, rContext.pScanLineBuffer.size());
298  }
299 
300  /* PENDING ??? */
301  if (rContext.cinfo.err->msg_code == 113)
302  break;
303  }
304 
305  rContext.pScanLineBuffer.clear();
306  rContext.pCYMKBuffer.clear();
307  }
308  rContext.pScopedAccess.reset();
309  }
310 
311  if (bBitmapCreated && !bOnlyCreateBitmap)
312  {
313  jpeg_finish_decompress(&rContext.cinfo);
314  }
315  else
316  {
317  jpeg_abort_decompress(&rContext.cinfo);
318  }
319 }
320 
321 void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, long* pLines,
322  GraphicFilterImportFlags nImportFlags,
323  BitmapScopedWriteAccess* ppAccess )
324 {
325  JpegStuff aContext;
326  ReadJPEG(aContext, pJPEGReader, pInputStream, pLines, nImportFlags, ppAccess);
327 }
328 
329 bool WriteJPEG( JPEGWriter* pJPEGWriter, void* pOutputStream,
330  long nWidth, long nHeight, basegfx::B2DSize const & rPPI, bool bGreys,
331  long nQualityPercent, long aChromaSubsampling,
332  css::uno::Reference<css::task::XStatusIndicator> const & status )
333 {
334  jpeg_compress_struct cinfo;
335  ErrorManagerStruct jerr;
336  void* pScanline;
337  long nY;
338 
339  JpegCompressOwner aOwner;
340 
341  if ( setjmp( jerr.setjmp_buffer ) )
342  {
343  return false;
344  }
345 
346  cinfo.err = jpeg_std_error( &jerr.pub );
347  jerr.pub.error_exit = errorExit;
348  jerr.pub.output_message = outputMessage;
349 
350  jpeg_create_compress( &cinfo );
351  aOwner.set(&cinfo);
352  jpeg_svstream_dest( &cinfo, pOutputStream );
353 
354  cinfo.image_width = static_cast<JDIMENSION>(nWidth);
355  cinfo.image_height = static_cast<JDIMENSION>(nHeight);
356  if ( bGreys )
357  {
358  cinfo.input_components = 1;
359  cinfo.in_color_space = JCS_GRAYSCALE;
360  }
361  else
362  {
363  cinfo.input_components = 3;
364  cinfo.in_color_space = JCS_RGB;
365  }
366 
367  jpeg_set_defaults( &cinfo );
368  jpeg_set_quality( &cinfo, static_cast<int>(nQualityPercent), FALSE );
369 
370  if (o3tl::convertsToAtMost(rPPI.getX(), 65535) && o3tl::convertsToAtMost(rPPI.getY(), 65535))
371  {
372  cinfo.density_unit = 1;
373  cinfo.X_density = rPPI.getX();
374  cinfo.Y_density = rPPI.getY();
375  }
376  else
377  {
378  SAL_WARN("vcl.filter", "ignoring too large PPI " << rPPI);
379  }
380 
381  if ( ( nWidth > 128 ) || ( nHeight > 128 ) )
382  jpeg_simple_progression( &cinfo );
383 
384  if (aChromaSubsampling == 1) // YUV 4:4:4
385  {
386  cinfo.comp_info[0].h_samp_factor = 1;
387  cinfo.comp_info[0].v_samp_factor = 1;
388  }
389  else if (aChromaSubsampling == 2) // YUV 4:2:2
390  {
391  cinfo.comp_info[0].h_samp_factor = 2;
392  cinfo.comp_info[0].v_samp_factor = 1;
393  }
394  else if (aChromaSubsampling == 3) // YUV 4:2:0
395  {
396  cinfo.comp_info[0].h_samp_factor = 2;
397  cinfo.comp_info[0].v_samp_factor = 2;
398  }
399 
400  jpeg_start_compress( &cinfo, TRUE );
401 
402  for( nY = 0; nY < nHeight; nY++ )
403  {
404  pScanline = pJPEGWriter->GetScanline( nY );
405 
406  if( pScanline )
407  {
408  jpeg_write_scanlines( &cinfo, reinterpret_cast<JSAMPARRAY>(&pScanline), 1 );
409  }
410 
411  if( status.is() )
412  {
413  status->setValue( nY * 100L / nHeight );
414  }
415  }
416 
417  jpeg_finish_compress(&cinfo);
418 
419  return true;
420 }
421 
422 void Transform(void* pInputStream, void* pOutputStream, long nAngle)
423 {
424  jpeg_transform_info aTransformOption;
425  JCOPY_OPTION aCopyOption = JCOPYOPT_ALL;
426 
427  jpeg_decompress_struct aSourceInfo;
428  jpeg_compress_struct aDestinationInfo;
429  ErrorManagerStruct aSourceError;
430  ErrorManagerStruct aDestinationError;
431 
432  jvirt_barray_ptr* aSourceCoefArrays = nullptr;
433  jvirt_barray_ptr* aDestinationCoefArrays = nullptr;
434 
435  aTransformOption.force_grayscale = FALSE;
436  aTransformOption.trim = FALSE;
437  aTransformOption.perfect = FALSE;
438  aTransformOption.crop = FALSE;
439 
440  // Angle to transform option
441  // 90 Clockwise = 270 Counterclockwise
442  switch (nAngle)
443  {
444  case 2700:
445  aTransformOption.transform = JXFORM_ROT_90;
446  break;
447  case 1800:
448  aTransformOption.transform = JXFORM_ROT_180;
449  break;
450  case 900:
451  aTransformOption.transform = JXFORM_ROT_270;
452  break;
453  default:
454  aTransformOption.transform = JXFORM_NONE;
455  }
456 
457  // Decompression
458  aSourceInfo.err = jpeg_std_error(&aSourceError.pub);
459  aSourceInfo.err->error_exit = errorExit;
460  aSourceInfo.err->output_message = outputMessage;
461 
462  // Compression
463  aDestinationInfo.err = jpeg_std_error(&aDestinationError.pub);
464  aDestinationInfo.err->error_exit = errorExit;
465  aDestinationInfo.err->output_message = outputMessage;
466 
467  aDestinationInfo.optimize_coding = TRUE;
468 
469  JpegDecompressOwner aDecompressOwner;
470  JpegCompressOwner aCompressOwner;
471 
472  if (setjmp(aSourceError.setjmp_buffer) || setjmp(aDestinationError.setjmp_buffer))
473  {
474  jpeg_destroy_decompress(&aSourceInfo);
475  jpeg_destroy_compress(&aDestinationInfo);
476  return;
477  }
478 
479  jpeg_create_decompress(&aSourceInfo);
480  aDecompressOwner.set(&aSourceInfo);
481  jpeg_create_compress(&aDestinationInfo);
482  aCompressOwner.set(&aDestinationInfo);
483 
484  jpeg_svstream_src (&aSourceInfo, pInputStream);
485 
486  jcopy_markers_setup(&aSourceInfo, aCopyOption);
487  jpeg_read_header(&aSourceInfo, TRUE);
488  jtransform_request_workspace(&aSourceInfo, &aTransformOption);
489 
490  aSourceCoefArrays = jpeg_read_coefficients(&aSourceInfo);
491  jpeg_copy_critical_parameters(&aSourceInfo, &aDestinationInfo);
492 
493  aDestinationCoefArrays = jtransform_adjust_parameters(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
494  jpeg_svstream_dest (&aDestinationInfo, pOutputStream);
495 
496  // Compute optimal Huffman coding tables instead of precomputed tables
497  aDestinationInfo.optimize_coding = TRUE;
498  jpeg_write_coefficients(&aDestinationInfo, aDestinationCoefArrays);
499  jcopy_markers_execute(&aSourceInfo, &aDestinationInfo, aCopyOption);
500  jtransform_execute_transformation(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
501 
502  jpeg_finish_compress(&aDestinationInfo);
503 
504  jpeg_finish_decompress(&aSourceInfo);
505 }
506 
507 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static void errorExit(j_common_ptr cinfo)
Definition: jpegc.cxx:63
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
double getX() const
unsigned long X_density
Definition: JpegReader.hxx:42
vcl::ScopedBitmapAccess< BitmapWriteAccess, Bitmap,&Bitmap::AcquireWriteAccess > BitmapScopedWriteAccess
unsigned long density_unit
Definition: JpegReader.hxx:41
double getY() const
void * GetScanline(long nY)
Definition: JpegWriter.cxx:139
This template handles BitmapAccess the RAII way.
float x
ScanlineFormat
Definition: Scanline.hxx:28
unsigned long nWidth
Definition: JpegReader.hxx:39
void Transform(void *pInputStream, void *pOutputStream, long nAngle)
Definition: jpegc.cxx:422
static bool IsFuzzing()
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:43
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 outputMessage(j_common_ptr cinfo)
Definition: jpegc.cxx:72
sal_uInt8 * Scanline
Definition: Scanline.hxx:25
boolean force_grayscale
Definition: transupp.h:130
JXFORM_CODE transform
Definition: transupp.h:127
#define TRUE
static void ReadJPEG(JpegStuff &rContext, JPEGReader *pJPEGReader, void *pInputStream, long *pLines, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess *ppAccess)
Definition: jpegc.cxx:157
std::enable_if< std::is_signed< T >::value, bool >::type checked_multiply(T a, T b, T &result)
std::enable_if_t< std::is_floating_point_v< F > &&std::is_integral_v< I >, bool > convertsToAtMost(F value, I max)
unsigned long nHeight
Definition: JpegReader.hxx:40
Bitmap & GetBitmap()
Definition: JpegReader.hxx:68
unsigned char sal_uInt8
jcopy_markers_setup(j_decompress_ptr srcinfo, JCOPY_OPTION option)
Definition: transupp.c:1499
static int GetWarningLimit()
Definition: jpegc.cxx:81
static void emitMessage(j_common_ptr cinfo, int msg_level)
Definition: jpegc.cxx:88
void * p
#define FALSE
#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:329
void set(css::uno::UnoInterfaceReference const &value)