xref: /core/vcl/source/filter/jpeg/JpegReader.cxx (revision 6354829686a11f0bbcd86fba5eced9d15b76d2c4)
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 
35 extern "C" {
36 
37 /*
38  * Initialize source --- called by jpeg_read_header
39  * before any data is actually read.
40  */
init_source(j_decompress_ptr cinfo)41 static 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;
50     source->no_data_available_failures = 0;
51 }
52 
53 }
54 
StreamRead(SvStream * pStream,void * pBuffer,tools::Long nBufferSize)55 static 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 
78 extern "C" {
79 
fill_input_buffer(j_decompress_ptr cinfo)80 static 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     {
89         source->no_data_available_failures++;
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 
skip_input_data(j_decompress_ptr cinfo,long numberOfBytes)108 static 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 
term_source(j_decompress_ptr)132 static void term_source (j_decompress_ptr)
133 {
134     /* no work necessary here */
135 }
136 
137 }
138 
jpeg_svstream_src(j_decompress_ptr cinfo,void * input)139 void 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 
JPEGReader(SvStream & rStream,GraphicFilterImportFlags nImportFlags)172 JPEGReader::JPEGReader( SvStream& rStream, GraphicFilterImportFlags nImportFlags ) :
173     mrStream         ( rStream ),
174     mnLastPos        ( rStream.Tell() ),
175     mbSetLogSize     ( nImportFlags & GraphicFilterImportFlags::SetLogsizeForJpeg )
176 {
177     if (!(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap))
178     {
179         mpBitmap.emplace();
180     }
181 }
182 
CreateBitmap(JPEGCreateBitmapParam const & rParam)183 bool JPEGReader::CreateBitmap(JPEGCreateBitmapParam const & rParam)
184 {
185     if (rParam.nWidth > SAL_MAX_INT32 / 8 || rParam.nHeight > SAL_MAX_INT32 / 8)
186         return false; // avoid overflows later
187 
188     if (rParam.nWidth == 0 || rParam.nHeight == 0)
189         return false;
190 
191     Size aSize(rParam.nWidth, rParam.nHeight);
192     bool bGray = rParam.bGray;
193 
194     mpBitmap.emplace();
195 
196     sal_uInt64 nSize = aSize.Width() * aSize.Height();
197 
198     if (nSize > SAL_MAX_INT32 / (bGray?1:3))
199         return false;
200 
201     if( bGray )
202     {
203         BitmapPalette aGrayPal( 256 );
204 
205         for( sal_uInt16 n = 0; n < 256; n++ )
206         {
207             const sal_uInt8 cGray = static_cast<sal_uInt8>(n);
208             aGrayPal[ n ] = BitmapColor( cGray, cGray, cGray );
209         }
210 
211         mpBitmap.emplace(aSize, vcl::PixelFormat::N8_BPP, &aGrayPal);
212     }
213     else
214     {
215         mpBitmap.emplace(aSize, vcl::PixelFormat::N24_BPP);
216     }
217 
218     if (mbSetLogSize)
219     {
220         unsigned long nUnit = rParam.density_unit;
221 
222         if (((1 == nUnit) || (2 == nUnit)) && rParam.X_density && rParam.Y_density )
223         {
224             Fraction    aFractX( 1, rParam.X_density );
225             Fraction    aFractY( 1, rParam.Y_density );
226             MapMode     aMapMode( nUnit == 1 ? MapUnit::MapInch : MapUnit::MapCM, Point(), aFractX, aFractY );
227             Size        aPrefSize = OutputDevice::LogicToLogic(aSize, aMapMode, MapMode(MapUnit::Map100thMM));
228 
229             mpBitmap->SetPrefSize(aPrefSize);
230             mpBitmap->SetPrefMapMode(MapMode(MapUnit::Map100thMM));
231         }
232     }
233 
234     return true;
235 }
236 
Read(ImportOutput & rImportOutput,GraphicFilterImportFlags nImportFlags,BitmapScopedWriteAccess * ppAccess)237 ReadState JPEGReader::Read(ImportOutput& rImportOutput, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess )
238 {
239     bool bRet = false;
240 
241     // seek back to the original position
242     mrStream.Seek( mnLastPos );
243 
244     // read the (partial) image
245     ReadJPEG(this, &mrStream, nImportFlags, ppAccess);
246 
247     auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
248     if (bUseExistingBitmap || !mpBitmap->IsEmpty())
249     {
250         if (!bUseExistingBitmap)
251             rImportOutput.moBitmap = *mpBitmap;
252 
253         bRet = true;
254     }
255 
256     return bRet ? JPEGREAD_OK : JPEGREAD_ERROR;
257 }
258 
259 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
260