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, 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 
188  tools::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::N24BitTcBgr)
235  {
236  best_out_color_space = JCS_EXT_BGR;
237  eScanlineFormat = eFinalFormat;
238  nPixelSize = 3;
239  }
240  else if (eFinalFormat == ScanlineFormat::N32BitTcBgra)
241  {
242  best_out_color_space = JCS_EXT_BGRA;
243  eScanlineFormat = eFinalFormat;
244  nPixelSize = 4;
245  }
246  else if (eFinalFormat == ScanlineFormat::N32BitTcRgba)
247  {
248  best_out_color_space = JCS_EXT_RGBA;
249  eScanlineFormat = eFinalFormat;
250  nPixelSize = 4;
251  }
252  else if (eFinalFormat == ScanlineFormat::N32BitTcArgb)
253  {
254  best_out_color_space = JCS_EXT_ARGB;
255  eScanlineFormat = eFinalFormat;
256  nPixelSize = 4;
257  }
258 #endif
259  if (rContext.cinfo.jpeg_color_space == JCS_YCCK)
260  rContext.cinfo.out_color_space = JCS_CMYK;
261 
262  if (rContext.cinfo.out_color_space != JCS_CMYK)
263  rContext.cinfo.out_color_space = best_out_color_space;
264 
265  jpeg_start_decompress(&rContext.cinfo);
266 
267  JSAMPLE* aRangeLimit = rContext.cinfo.sample_range_limit;
268 
269  rContext.pScanLineBuffer.resize(nWidth * nPixelSize);
270 
271  if (rContext.cinfo.out_color_space == JCS_CMYK)
272  {
273  rContext.pCYMKBuffer.resize(nWidth * 4);
274  }
275 
276  for (*pLines = 0; *pLines < nHeight && !source->no_data_available; (*pLines)++)
277  {
278  size_t yIndex = *pLines;
279 
280  sal_uInt8* p = (rContext.cinfo.out_color_space == JCS_CMYK) ? rContext.pCYMKBuffer.data() : rContext.pScanLineBuffer.data();
281  jpeg_read_scanlines(&rContext.cinfo, reinterpret_cast<JSAMPARRAY>(&p), 1);
282 
283  if (rContext.cinfo.out_color_space == JCS_CMYK)
284  {
285  // convert CMYK to RGB
286  Scanline pScanline = pAccess->GetScanline(yIndex);
287  for (tools::Long cmyk = 0, x = 0; cmyk < nWidth * 4; cmyk += 4, ++x)
288  {
289  int color_C = 255 - rContext.pCYMKBuffer[cmyk + 0];
290  int color_M = 255 - rContext.pCYMKBuffer[cmyk + 1];
291  int color_Y = 255 - rContext.pCYMKBuffer[cmyk + 2];
292  int color_K = 255 - rContext.pCYMKBuffer[cmyk + 3];
293 
294  sal_uInt8 cRed = aRangeLimit[255L - (color_C + color_K)];
295  sal_uInt8 cGreen = aRangeLimit[255L - (color_M + color_K)];
296  sal_uInt8 cBlue = aRangeLimit[255L - (color_Y + color_K)];
297 
298  pAccess->SetPixelOnData(pScanline, x, BitmapColor(cRed, cGreen, cBlue));
299  }
300  }
301  else
302  {
303  pAccess->CopyScanline(yIndex, rContext.pScanLineBuffer.data(), eScanlineFormat, rContext.pScanLineBuffer.size());
304  }
305 
306  /* PENDING ??? */
307  if (rContext.cinfo.err->msg_code == 113)
308  break;
309  }
310 
311  rContext.pScanLineBuffer.clear();
312  rContext.pCYMKBuffer.clear();
313  }
314  rContext.pScopedAccess.reset();
315  }
316 
317  if (bBitmapCreated && !bOnlyCreateBitmap)
318  {
319  jpeg_finish_decompress(&rContext.cinfo);
320  }
321  else
322  {
323  jpeg_abort_decompress(&rContext.cinfo);
324  }
325 }
326 
327 void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines,
328  GraphicFilterImportFlags nImportFlags,
329  BitmapScopedWriteAccess* ppAccess )
330 {
331  JpegStuff aContext;
332  ReadJPEG(aContext, pJPEGReader, pInputStream, pLines, nImportFlags, ppAccess);
333 }
334 
335 bool WriteJPEG( JPEGWriter* pJPEGWriter, void* pOutputStream,
336  tools::Long nWidth, tools::Long nHeight, basegfx::B2DSize const & rPPI, bool bGreys,
337  tools::Long nQualityPercent, tools::Long aChromaSubsampling,
338  css::uno::Reference<css::task::XStatusIndicator> const & status )
339 {
340  jpeg_compress_struct cinfo;
341  ErrorManagerStruct jerr;
342  void* pScanline;
343  tools::Long nY;
344 
345  JpegCompressOwner aOwner;
346 
347  if ( setjmp( jerr.setjmp_buffer ) )
348  {
349  return false;
350  }
351 
352  cinfo.err = jpeg_std_error( &jerr.pub );
353  jerr.pub.error_exit = errorExit;
354  jerr.pub.output_message = outputMessage;
355 
356  jpeg_create_compress( &cinfo );
357  aOwner.set(&cinfo);
358  jpeg_svstream_dest( &cinfo, pOutputStream );
359 
360  cinfo.image_width = static_cast<JDIMENSION>(nWidth);
361  cinfo.image_height = static_cast<JDIMENSION>(nHeight);
362  if ( bGreys )
363  {
364  cinfo.input_components = 1;
365  cinfo.in_color_space = JCS_GRAYSCALE;
366  }
367  else
368  {
369  cinfo.input_components = 3;
370  cinfo.in_color_space = JCS_RGB;
371  }
372 
373  jpeg_set_defaults( &cinfo );
374  jpeg_set_quality( &cinfo, static_cast<int>(nQualityPercent), FALSE );
375 
376  if (o3tl::convertsToAtMost(rPPI.getX(), 65535) && o3tl::convertsToAtMost(rPPI.getY(), 65535))
377  {
378  cinfo.density_unit = 1;
379  cinfo.X_density = rPPI.getX();
380  cinfo.Y_density = rPPI.getY();
381  }
382  else
383  {
384  SAL_WARN("vcl.filter", "ignoring too large PPI " << rPPI);
385  }
386 
387  if ( ( nWidth > 128 ) || ( nHeight > 128 ) )
388  jpeg_simple_progression( &cinfo );
389 
390  if (aChromaSubsampling == 1) // YUV 4:4:4
391  {
392  cinfo.comp_info[0].h_samp_factor = 1;
393  cinfo.comp_info[0].v_samp_factor = 1;
394  }
395  else if (aChromaSubsampling == 2) // YUV 4:2:2
396  {
397  cinfo.comp_info[0].h_samp_factor = 2;
398  cinfo.comp_info[0].v_samp_factor = 1;
399  }
400  else if (aChromaSubsampling == 3) // YUV 4:2:0
401  {
402  cinfo.comp_info[0].h_samp_factor = 2;
403  cinfo.comp_info[0].v_samp_factor = 2;
404  }
405 
406  jpeg_start_compress( &cinfo, TRUE );
407 
408  for( nY = 0; nY < nHeight; nY++ )
409  {
410  pScanline = pJPEGWriter->GetScanline( nY );
411 
412  if( pScanline )
413  {
414  jpeg_write_scanlines( &cinfo, reinterpret_cast<JSAMPARRAY>(&pScanline), 1 );
415  }
416 
417  if( status.is() )
418  {
419  status->setValue( nY * 100L / nHeight );
420  }
421  }
422 
423  jpeg_finish_compress(&cinfo);
424 
425  return true;
426 }
427 
428 void Transform(void* pInputStream, void* pOutputStream, Degree10 nAngle)
429 {
430  jpeg_transform_info aTransformOption;
431  JCOPY_OPTION aCopyOption = JCOPYOPT_ALL;
432 
433  jpeg_decompress_struct aSourceInfo;
434  jpeg_compress_struct aDestinationInfo;
435  ErrorManagerStruct aSourceError;
436  ErrorManagerStruct aDestinationError;
437 
438  jvirt_barray_ptr* aSourceCoefArrays = nullptr;
439  jvirt_barray_ptr* aDestinationCoefArrays = nullptr;
440 
441  aTransformOption.force_grayscale = FALSE;
442  aTransformOption.trim = FALSE;
443  aTransformOption.perfect = FALSE;
444  aTransformOption.crop = FALSE;
445 
446  // Angle to transform option
447  // 90 Clockwise = 270 Counterclockwise
448  switch (nAngle.get())
449  {
450  case 2700:
451  aTransformOption.transform = JXFORM_ROT_90;
452  break;
453  case 1800:
454  aTransformOption.transform = JXFORM_ROT_180;
455  break;
456  case 900:
457  aTransformOption.transform = JXFORM_ROT_270;
458  break;
459  default:
460  aTransformOption.transform = JXFORM_NONE;
461  }
462 
463  // Decompression
464  aSourceInfo.err = jpeg_std_error(&aSourceError.pub);
465  aSourceInfo.err->error_exit = errorExit;
466  aSourceInfo.err->output_message = outputMessage;
467 
468  // Compression
469  aDestinationInfo.err = jpeg_std_error(&aDestinationError.pub);
470  aDestinationInfo.err->error_exit = errorExit;
471  aDestinationInfo.err->output_message = outputMessage;
472 
473  aDestinationInfo.optimize_coding = TRUE;
474 
475  JpegDecompressOwner aDecompressOwner;
476  JpegCompressOwner aCompressOwner;
477 
478  if (setjmp(aSourceError.setjmp_buffer))
479  {
480  jpeg_destroy_decompress(&aSourceInfo);
481  jpeg_destroy_compress(&aDestinationInfo);
482  return;
483  }
484  if (setjmp(aDestinationError.setjmp_buffer))
485  {
486  jpeg_destroy_decompress(&aSourceInfo);
487  jpeg_destroy_compress(&aDestinationInfo);
488  return;
489  }
490 
491  jpeg_create_decompress(&aSourceInfo);
492  aDecompressOwner.set(&aSourceInfo);
493  jpeg_create_compress(&aDestinationInfo);
494  aCompressOwner.set(&aDestinationInfo);
495 
496  jpeg_svstream_src (&aSourceInfo, pInputStream);
497 
498  jcopy_markers_setup(&aSourceInfo, aCopyOption);
499  jpeg_read_header(&aSourceInfo, TRUE);
500  jtransform_request_workspace(&aSourceInfo, &aTransformOption);
501 
502  aSourceCoefArrays = jpeg_read_coefficients(&aSourceInfo);
503  jpeg_copy_critical_parameters(&aSourceInfo, &aDestinationInfo);
504 
505  aDestinationCoefArrays = jtransform_adjust_parameters(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
506  jpeg_svstream_dest (&aDestinationInfo, pOutputStream);
507 
508  // Compute optimal Huffman coding tables instead of precomputed tables
509  aDestinationInfo.optimize_coding = TRUE;
510  jpeg_write_coefficients(&aDestinationInfo, aDestinationCoefArrays);
511  jcopy_markers_execute(&aSourceInfo, &aDestinationInfo, aCopyOption);
512  jtransform_execute_transformation(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
513 
514  jpeg_finish_compress(&aDestinationInfo);
515 
516  jpeg_finish_decompress(&aSourceInfo);
517 }
518 
519 /* 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
tools::ULong Y_density
Definition: JpegReader.hxx:43
long Long
double getX() const
vcl::ScopedBitmapAccess< BitmapWriteAccess, Bitmap,&Bitmap::AcquireWriteAccess > BitmapScopedWriteAccess
double getY() const
float x
void SetPixelOnData(sal_uInt8 *pData, tools::Long nX, const BitmapColor &rBitmapColor)
ScanlineFormat
Definition: Scanline.hxx:28
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:335
const BorderLinePrimitive2D *pCandidateB assert(pCandidateA)
Scanline GetScanline(tools::Long nY) const
static bool IsFuzzing()
jcopy_markers_execute(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, JCOPY_OPTION option)
Definition: transupp.c:1526
static void ReadJPEG(JpegStuff &rContext, JPEGReader *pJPEGReader, void *pInputStream, tools::Long *pLines, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess *ppAccess)
Definition: jpegc.cxx:157
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
tools::ULong X_density
Definition: JpegReader.hxx:42
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
UNDERLYING_TYPE get() const
JXFORM_CODE transform
Definition: transupp.h:127
#define TRUE
ScanlineFormat GetScanlineFormat() const
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)
void * GetScanline(tools::Long nY)
Definition: JpegWriter.cxx:139
Bitmap & GetBitmap()
Definition: JpegReader.hxx:68
tools::ULong nHeight
Definition: JpegReader.hxx:40
unsigned char sal_uInt8
jcopy_markers_setup(j_decompress_ptr srcinfo, JCOPY_OPTION option)
Definition: transupp.c:1499
void CopyScanline(tools::Long nY, const BitmapReadAccess &rReadAcc)
Definition: bmpacc.cxx:331
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)
void Transform(void *pInputStream, void *pOutputStream, Degree10 nAngle)
Definition: jpegc.cxx:428
boolean no_data_available
Definition: jpeg.h:62
GraphicFilterImportFlags
tools::ULong density_unit
Definition: JpegReader.hxx:41
JCOPY_OPTION
Definition: transupp.h:199
tools::ULong nWidth
Definition: JpegReader.hxx:39
void set(css::uno::UnoInterfaceReference const &value)