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