LibreOffice Module vcl (master) 1
JpegReader.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
22#include "jpeg.h"
23#include <jpeglib.h>
24#include <jerror.h>
25
26#include "JpegReader.hxx"
27#include <vcl/graphicfilter.hxx>
28#include <vcl/outdev.hxx>
29#include <tools/fract.hxx>
30#include <tools/stream.hxx>
31#include <memory>
32
33#define BUFFER_SIZE 4096
34
35extern "C" {
36
37/*
38 * Initialize source --- called by jpeg_read_header
39 * before any data is actually read.
40 */
41static void init_source (j_decompress_ptr cinfo)
42{
43 SourceManagerStruct * source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
44
45 /* We reset the empty-input-file flag for each image,
46 * but we don't clear the input buffer.
47 * This is correct behavior for reading a series of images from one source.
48 */
49 source->start_of_file = TRUE;
51}
52
53}
54
55static tools::Long StreamRead( SvStream* pStream, void* pBuffer, tools::Long nBufferSize )
56{
57 tools::Long nRead = 0;
58
59 if( pStream->GetError() != ERRCODE_IO_PENDING )
60 {
61 sal_uInt64 nInitialPosition = pStream->Tell();
62
63 nRead = static_cast<tools::Long>(pStream->ReadBytes(pBuffer, nBufferSize));
64
65 if( pStream->GetError() == ERRCODE_IO_PENDING )
66 {
67 // in order to search from the old position
68 // we temporarily reset the error
69 pStream->ResetError();
70 pStream->Seek( nInitialPosition );
71 pStream->SetError( ERRCODE_IO_PENDING );
72 }
73 }
74
75 return nRead;
76}
77
78extern "C" {
79
80static boolean fill_input_buffer (j_decompress_ptr cinfo)
81{
82 SourceManagerStruct * source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
83 size_t nbytes;
84
85 nbytes = StreamRead(source->stream, source->buffer, BUFFER_SIZE);
86
87 if (!nbytes)
88 {
90 if (source->start_of_file) /* Treat empty input file as fatal error */
91 {
92 ERREXIT(cinfo, JERR_INPUT_EMPTY);
93 }
94 WARNMS(cinfo, JWRN_JPEG_EOF);
95 /* Insert a fake EOI marker */
96 source->buffer[0] = JOCTET(0xFF);
97 source->buffer[1] = JOCTET(JPEG_EOI);
98 nbytes = 2;
99 }
100
101 source->pub.next_input_byte = source->buffer;
102 source->pub.bytes_in_buffer = nbytes;
103 source->start_of_file = FALSE;
104
105 return TRUE;
106}
107
108static void skip_input_data (j_decompress_ptr cinfo, long numberOfBytes)
109{
110 SourceManagerStruct * source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
111
112 /* Just a dumb implementation for now. Could use fseek() except
113 * it doesn't work on pipes. Not clear that being smart is worth
114 * any trouble anyway --- large skips are infrequent.
115 */
116 if (numberOfBytes <= 0)
117 return;
118
119 while (numberOfBytes > static_cast<tools::Long>(source->pub.bytes_in_buffer))
120 {
121 numberOfBytes -= static_cast<tools::Long>(source->pub.bytes_in_buffer);
122 (void) fill_input_buffer(cinfo);
123
124 /* note we assume that fill_input_buffer will never return false,
125 * so suspension need not be handled.
126 */
127 }
128 source->pub.next_input_byte += static_cast<size_t>(numberOfBytes);
129 source->pub.bytes_in_buffer -= static_cast<size_t>(numberOfBytes);
130}
131
132static void term_source (j_decompress_ptr)
133{
134 /* no work necessary here */
135}
136
137}
138
139void jpeg_svstream_src (j_decompress_ptr cinfo, void* input)
140{
141 SourceManagerStruct * source;
142 SvStream* stream = static_cast<SvStream*>(input);
143
144 /* The source object and input buffer are made permanent so that a series
145 * of JPEG images can be read from the same file by calling jpeg_stdio_src
146 * only before the first one. (If we discarded the buffer at the end of
147 * one image, we'd likely lose the start of the next one.)
148 * This makes it unsafe to use this manager and a different source
149 * manager serially with the same JPEG object. Caveat programmer.
150 */
151
152 if (cinfo->src == nullptr)
153 { /* first time for this JPEG object? */
154 cinfo->src = static_cast<jpeg_source_mgr *>(
155 (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(SourceManagerStruct)));
156 source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
157 source->buffer = static_cast<JOCTET *>(
158 (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, BUFFER_SIZE * sizeof(JOCTET)));
159 }
160
161 source = reinterpret_cast<SourceManagerStruct*>(cinfo->src);
162 source->pub.init_source = init_source;
163 source->pub.fill_input_buffer = fill_input_buffer;
164 source->pub.skip_input_data = skip_input_data;
165 source->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
166 source->pub.term_source = term_source;
167 source->stream = stream;
168 source->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
169 source->pub.next_input_byte = nullptr; /* until buffer loaded */
170}
171
173 mrStream ( rStream ),
174 mnLastPos ( rStream.Tell() ),
175 mnLastLines ( 0 ),
176 mbSetLogSize ( nImportFlags & GraphicFilterImportFlags::SetLogsizeForJpeg )
177{
178 maUpperName = "SVIJPEG";
179
181 {
182 mpBitmap.emplace();
183 mpIncompleteAlpha.emplace();
184 }
185}
186
188{
189}
190
192{
193 if (rParam.nWidth > SAL_MAX_INT32 / 8 || rParam.nHeight > SAL_MAX_INT32 / 8)
194 return false; // avoid overflows later
195
196 if (rParam.nWidth == 0 || rParam.nHeight == 0)
197 return false;
198
199 Size aSize(rParam.nWidth, rParam.nHeight);
200 bool bGray = rParam.bGray;
201
202 mpBitmap.emplace();
203
204 sal_uInt64 nSize = aSize.Width() * aSize.Height();
205
206 if (nSize > SAL_MAX_INT32 / (bGray?1:3))
207 return false;
208
209 if( bGray )
210 {
211 BitmapPalette aGrayPal( 256 );
212
213 for( sal_uInt16 n = 0; n < 256; n++ )
214 {
215 const sal_uInt8 cGray = static_cast<sal_uInt8>(n);
216 aGrayPal[ n ] = BitmapColor( cGray, cGray, cGray );
217 }
218
219 mpBitmap.emplace(aSize, vcl::PixelFormat::N8_BPP, &aGrayPal);
220 }
221 else
222 {
223 mpBitmap.emplace(aSize, vcl::PixelFormat::N24_BPP);
224 }
225
226 if (mbSetLogSize)
227 {
228 unsigned long nUnit = rParam.density_unit;
229
230 if (((1 == nUnit) || (2 == nUnit)) && rParam.X_density && rParam.Y_density )
231 {
232 Fraction aFractX( 1, rParam.X_density );
233 Fraction aFractY( 1, rParam.Y_density );
234 MapMode aMapMode( nUnit == 1 ? MapUnit::MapInch : MapUnit::MapCM, Point(), aFractX, aFractY );
235 Size aPrefSize = OutputDevice::LogicToLogic(aSize, aMapMode, MapMode(MapUnit::Map100thMM));
236
237 mpBitmap->SetPrefSize(aPrefSize);
238 mpBitmap->SetPrefMapMode(MapMode(MapUnit::Map100thMM));
239 }
240 }
241
242 return true;
243}
244
246{
247 Graphic aGraphic;
248 const Size aSizePixel(mpBitmap->GetSizePixel());
249
250 if (!mnLastLines)
251 {
252 mpIncompleteAlpha.emplace(aSizePixel);
253 mpIncompleteAlpha->Erase(255);
254 }
255
256 if (nLines && (nLines < aSizePixel.Height()))
257 {
258 const tools::Long nNewLines = nLines - mnLastLines;
259
260 if (nNewLines > 0)
261 {
262 {
264 pAccess->SetFillColor(COL_ALPHA_OPAQUE);
265 pAccess->FillRect(tools::Rectangle(Point(0, mnLastLines), Size(pAccess->Width(), nNewLines)));
266 }
267
268 aGraphic = BitmapEx(*mpBitmap, *mpIncompleteAlpha);
269 }
270 else
271 {
272 aGraphic = BitmapEx(*mpBitmap);
273 }
274 }
275 else
276 {
277 aGraphic = BitmapEx(*mpBitmap);
278 }
279
280 mnLastLines = nLines;
281
282 return aGraphic;
283}
284
286{
287 ReadState eReadState;
288 bool bRet = false;
289
290 // seek back to the original position
292
293 // read the (partial) image
294 tools::Long nLines;
295 ReadJPEG( this, &mrStream, &nLines, nImportFlags, ppAccess );
296
297 auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
298 if (bUseExistingBitmap || !mpBitmap->IsEmpty())
299 {
301 {
302 rGraphic = CreateIntermediateGraphic(nLines);
303 }
304 else
305 {
306 if (!bUseExistingBitmap)
307 rGraphic = BitmapEx(*mpBitmap);
308 }
309
310 bRet = true;
311 }
312 else if( mrStream.GetError() == ERRCODE_IO_PENDING )
313 {
314 bRet = true;
315 }
316
317 // Set status ( Pending has priority )
319 {
320 eReadState = JPEGREAD_NEED_MORE;
322 }
323 else
324 {
325 eReadState = bRet ? JPEGREAD_OK : JPEGREAD_ERROR;
326 }
327
328 return eReadState;
329}
330
331/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void jpeg_svstream_src(j_decompress_ptr cinfo, void *input)
Definition: JpegReader.cxx:139
#define BUFFER_SIZE
Definition: JpegReader.cxx:33
static void term_source(j_decompress_ptr)
Definition: JpegReader.cxx:132
static tools::Long StreamRead(SvStream *pStream, void *pBuffer, tools::Long nBufferSize)
Definition: JpegReader.cxx:55
static void skip_input_data(j_decompress_ptr cinfo, long numberOfBytes)
Definition: JpegReader.cxx:108
static void init_source(j_decompress_ptr cinfo)
Definition: JpegReader.cxx:41
static boolean fill_input_buffer(j_decompress_ptr cinfo)
Definition: JpegReader.cxx:80
ReadState
Definition: JpegReader.hxx:31
@ JPEGREAD_NEED_MORE
Definition: JpegReader.hxx:34
@ JPEGREAD_ERROR
Definition: JpegReader.hxx:33
@ JPEGREAD_OK
Definition: JpegReader.hxx:32
OUString maUpperName
virtual ~JPEGReader() override
Definition: JpegReader.cxx:187
bool mbSetLogSize
Definition: JpegReader.hxx:56
ReadState Read(Graphic &rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess *ppAccess)
Definition: JpegReader.cxx:285
tools::Long mnLastPos
Definition: JpegReader.hxx:54
tools::Long mnLastLines
Definition: JpegReader.hxx:55
bool CreateBitmap(JPEGCreateBitmapParam const &param)
Definition: JpegReader.cxx:191
std::optional< AlphaMask > mpIncompleteAlpha
Definition: JpegReader.hxx:52
std::optional< Bitmap > mpBitmap
Definition: JpegReader.hxx:51
SvStream & mrStream
Definition: JpegReader.hxx:50
Graphic CreateIntermediateGraphic(tools::Long nLines)
Definition: JpegReader.cxx:245
JPEGReader(SvStream &rStream, GraphicFilterImportFlags nImportFlags)
Definition: JpegReader.cxx:172
SAL_WARN_UNUSED_RESULT Point LogicToLogic(const Point &rPtSource, const MapMode *pMapModeSource, const MapMode *pMapModeDest) const
Definition: map.cxx:1580
constexpr tools::Long Height() const
constexpr tools::Long Width() const
virtual void ResetError()
sal_uInt64 Tell() const
void SetError(ErrCode nErrorCode)
sal_uInt64 Seek(sal_uInt64 nPos)
std::size_t ReadBytes(void *pData, std::size_t nSize)
ErrCode GetError() const
This template handles BitmapAccess the RAII way.
constexpr ::Color COL_ALPHA_OPAQUE(0xff, 0xff, 0xff)
Reference< XOutputStream > stream
#define ERRCODE_IO_PENDING
GraphicFilterImportFlags
@ UseExistingBitmap
Read pixel data into an existing bitmap.
#define TRUE
#define FALSE
sal_Int64 n
void ReadJPEG(JPEGReader *pJPEGReader, void *pInputStream, tools::Long *pLines, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess *ppAccess)
Definition: jpegc.cxx:359
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
SvStream * stream
Definition: jpeg.h:59
JOCTET * buffer
Definition: jpeg.h:60
int no_data_available_failures
Definition: jpeg.h:62
boolean start_of_file
Definition: jpeg.h:61
jpeg_source_mgr pub
Definition: jpeg.h:58
unsigned char sal_uInt8
#define SAL_MAX_INT32