Line data Source code
1 : /**
2 : Copyright (c) 2022 Roman Katuntsev <sbkarr@stappler.org>
3 : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
4 :
5 : Permission is hereby granted, free of charge, to any person obtaining a copy
6 : of this software and associated documentation files (the "Software"), to deal
7 : in the Software without restriction, including without limitation the rights
8 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 : copies of the Software, and to permit persons to whom the Software is
10 : furnished to do so, subject to the following conditions:
11 :
12 : The above copyright notice and this permission notice shall be included in
13 : all copies or substantial portions of the Software.
14 :
15 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 : THE SOFTWARE.
22 : **/
23 :
24 : #include "SPBitmapFormat.h"
25 : #include "SPLog.h"
26 : #include "SPFilesystem.h"
27 : #include "jpeglib.h"
28 : #include <setjmp.h>
29 :
30 : namespace STAPPLER_VERSIONIZED stappler::bitmap::jpeg {
31 :
32 : struct JpegError {
33 : struct jpeg_error_mgr pub; /* "public" fields */
34 : jmp_buf setjmp_buffer; /* for return to caller */
35 :
36 0 : static void ErrorExit(j_common_ptr cinfo) {
37 0 : JpegError * myerr = (JpegError *) cinfo->err;
38 : char buffer[JMSG_LENGTH_MAX];
39 0 : (*cinfo->err->format_message) (cinfo, buffer);
40 0 : log::error("JPEG", "jpeg error: %s", buffer);
41 0 : longjmp(myerr->setjmp_buffer, 1);
42 : }
43 : };
44 :
45 5250 : static bool isJpg(const uint8_t * data, size_t dataLen) {
46 5250 : if (dataLen <= 4) {
47 0 : return false;
48 : }
49 :
50 : static const unsigned char JPG_SOI[] = {0xFF, 0xD8};
51 5250 : return memcmp(data, JPG_SOI, 2) == 0;
52 : }
53 :
54 1175 : static bool getJpegImageSize(const io::Producer &file, StackBuffer<512> &data, uint32_t &width, uint32_t &height) {
55 1175 : if (isJpg(data.data(), data.size())) {
56 225 : size_t offset = 2;
57 225 : uint16_t len = 0;
58 225 : uint8_t marker = 0;
59 :
60 225 : auto reader = BytesViewNetwork(data.data() + 2, data.size() - 2);
61 450 : while (reader.is((uint8_t) 0xFF)) {
62 225 : ++reader;
63 225 : ++offset;
64 : }
65 :
66 225 : marker = reader.readUnsigned();
67 225 : len = reader.readUnsigned16();
68 :
69 2325 : while (marker < 0xC0 || marker > 0xCF || marker == 0xC4) {
70 2100 : offset += 1 + len;
71 2100 : data.clear();
72 :
73 2100 : if (file.seekAndRead(offset, data, 12) != 12) {
74 0 : return false;
75 : }
76 :
77 2100 : if (data.size() >= 12) {
78 2100 : reader = data.get<BytesViewNetwork>();
79 :
80 4200 : while (reader.is((uint8_t) 0xFF)) {
81 2100 : ++reader;
82 2100 : ++offset;
83 : }
84 :
85 2100 : marker = reader.readUnsigned();
86 2100 : len = reader.readUnsigned16();
87 : } else {
88 0 : reader.clear();
89 0 : break;
90 : }
91 : }
92 :
93 225 : if (reader >= 5 && marker >= 0xC0 && marker <= 0xCF && marker != 0xC4) {
94 225 : ++reader;
95 225 : height = reader.readUnsigned16();
96 225 : width = reader.readUnsigned16();
97 225 : return true;
98 : }
99 :
100 0 : return false;
101 : }
102 950 : return false;
103 : }
104 :
105 : struct JpegReadStruct {
106 175 : ~JpegReadStruct() {
107 175 : if (initialized) {
108 175 : jpeg_destroy_decompress(&cinfo);
109 175 : initialized = false;
110 : }
111 :
112 175 : }
113 :
114 175 : JpegReadStruct() {
115 175 : cinfo.err = jpeg_std_error(&jerr.pub);
116 175 : jerr.pub.error_exit = &JpegError::ErrorExit;
117 175 : }
118 :
119 175 : bool init(const uint8_t *inputData, size_t size) {
120 175 : if (setjmp(jerr.setjmp_buffer)) {
121 0 : return false;
122 : }
123 :
124 175 : jpeg_create_decompress( &cinfo );
125 175 : initialized = true;
126 175 : jpeg_mem_src(&cinfo, const_cast<unsigned char*>(inputData), size);
127 :
128 : /* reading the image header which contains image information */
129 175 : jpeg_read_header(&cinfo, boolean(TRUE));
130 :
131 175 : return true;
132 : }
133 :
134 175 : bool info(ImageInfo &info) {
135 175 : if (setjmp(jerr.setjmp_buffer)) {
136 0 : return false;
137 : }
138 :
139 : // we only support RGB or grayscale
140 175 : if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
141 50 : info.color = (info.color == PixelFormat::A8 ? PixelFormat::A8 : PixelFormat::I8);
142 125 : } else if (cinfo.jpeg_color_space == JCS_YCCK || cinfo.jpeg_color_space == JCS_CMYK) {
143 50 : cinfo.out_color_space = JCS_CMYK;
144 50 : info.color = PixelFormat::RGB888;
145 : } else {
146 75 : cinfo.out_color_space = JCS_RGB;
147 75 : info.color = PixelFormat::RGB888;
148 : }
149 :
150 175 : if (info.color == PixelFormat::I8 || info.color == PixelFormat::RGB888) {
151 175 : info.alpha = AlphaFormat::Opaque;
152 : } else {
153 0 : info.alpha = AlphaFormat::Unpremultiplied;
154 : }
155 :
156 175 : jpeg_calc_output_dimensions( &cinfo );
157 :
158 175 : info.width = cinfo.output_width;
159 175 : info.height = cinfo.output_height;
160 175 : info.stride = cinfo.output_width * getBytesPerPixel(info.color);
161 :
162 175 : return true;
163 : }
164 :
165 100 : bool load(BitmapWriter &outputData) {
166 100 : if (!info(outputData)) {
167 0 : return false;
168 : }
169 :
170 100 : if (setjmp(jerr.setjmp_buffer)) {
171 0 : return false;
172 : }
173 :
174 100 : if (outputData.getStride) {
175 75 : outputData.stride = max(outputData.getStride(outputData.target, outputData.color, outputData.width),
176 150 : uint32_t(cinfo.output_width * getBytesPerPixel(outputData.color)));
177 : }
178 :
179 : /* Start decompression jpeg here */
180 100 : jpeg_start_decompress( &cinfo );
181 :
182 :
183 100 : auto dataLen = outputData.height * outputData.stride;
184 100 : outputData.resize(outputData.target, dataLen);
185 :
186 100 : JSAMPROW row_pointer[1] = {0};
187 100 : unsigned long location = 0;
188 :
189 100 : if (cinfo.out_color_space == JCS_CMYK || cinfo.out_color_space == JCS_YCCK) {
190 25 : memory::PoolInterface::BytesType buf; buf.resize(cinfo.output_width * cinfo.output_components);
191 8350 : while (cinfo.output_scanline < cinfo.output_height) {
192 8325 : row_pointer[0] = buf.data();
193 8325 : jpeg_read_scanlines(&cinfo, row_pointer, 1);
194 :
195 8325 : auto loc = outputData.getData(outputData.target, location);
196 4170825 : for (size_t i = 0; i < cinfo.output_width; ++ i) {
197 4162500 : *loc++ = (buf[i * 4]) * (buf[i * 4 + 3]) / 255;
198 4162500 : *loc++ = (buf[i * 4 + 1]) * (buf[i * 4 + 3]) / 255;
199 4162500 : *loc++ = (buf[i * 4 + 2]) * (buf[i * 4 + 3]) / 255;
200 : }
201 8325 : location += outputData.stride;
202 : }
203 25 : } else {
204 : /* now actually read the jpeg into the raw buffer */
205 : /* read one scan line at a time */
206 17525 : while (cinfo.output_scanline < cinfo.output_height) {
207 17450 : row_pointer[0] = outputData.getData(outputData.target, location);
208 17450 : location += outputData.stride;
209 17450 : jpeg_read_scanlines(&cinfo, row_pointer, 1);
210 : }
211 : }
212 :
213 100 : return true;
214 : }
215 :
216 : bool initialized = false;
217 : struct jpeg_decompress_struct cinfo;
218 : struct JpegError jerr;
219 : };
220 :
221 : struct JpegWriteStruct {
222 : struct jpeg_compress_struct cinfo;
223 : struct jpeg_error_mgr jerr;
224 : bool valid = false;
225 :
226 : FILE *fp = nullptr;
227 : BitmapWriter *vec = nullptr;
228 :
229 : unsigned char *mem = nullptr;
230 : size_t memSize = 0;
231 :
232 250 : ~JpegWriteStruct() {
233 250 : jpeg_destroy_compress( &cinfo );
234 :
235 250 : if (fp) {
236 175 : fclose(fp);
237 : }
238 250 : if (mem) {
239 75 : free(mem);
240 : }
241 250 : }
242 :
243 250 : JpegWriteStruct() {
244 250 : cinfo.err = jpeg_std_error( &jerr );
245 250 : jpeg_create_compress(&cinfo);
246 250 : }
247 :
248 75 : JpegWriteStruct(BitmapWriter *v) : JpegWriteStruct() {
249 75 : vec = v;
250 75 : jpeg_mem_dest(&cinfo, &mem, &memSize);
251 75 : valid = true;
252 75 : }
253 :
254 175 : JpegWriteStruct(const StringView &filename) : JpegWriteStruct() {
255 175 : fp = filesystem::native::fopen_fn(filename, "wb");
256 175 : if (!fp) {
257 0 : log::format(log::Error, "Bitmap", "fail to open file '%s' to write jpeg data", filename.data());
258 0 : valid = false;
259 0 : return;
260 : }
261 :
262 175 : jpeg_stdio_dest(&cinfo, fp);
263 175 : valid = true;
264 0 : }
265 :
266 250 : bool write(const uint8_t *data, BitmapWriter &state, bool invert = false) {
267 250 : if (!valid) {
268 0 : return false;
269 : }
270 :
271 : /* this is a pointer to one row of image data */
272 : JSAMPROW row_pointer[1];
273 :
274 : /* Setting the parameters of the output file here */
275 250 : cinfo.image_width = state.width;
276 250 : cinfo.image_height = state.height;
277 250 : cinfo.input_components = getBytesPerPixel(state.color);
278 :
279 250 : switch (state.color) {
280 25 : case PixelFormat::A8:
281 : case PixelFormat::I8:
282 25 : cinfo.input_components = 1;
283 25 : cinfo.in_color_space = JCS_GRAYSCALE;
284 25 : break;
285 175 : case PixelFormat::RGB888:
286 175 : cinfo.input_components = 3;
287 175 : cinfo.in_color_space = JCS_RGB;
288 175 : break;
289 50 : default:
290 50 : log::error("JPEG", "Color format is not supported by JPEG!");
291 50 : return false;
292 : break;
293 : }
294 :
295 : /* default compression parameters, we shouldn't be worried about these */
296 200 : jpeg_set_defaults( &cinfo );
297 200 : jpeg_set_quality( &cinfo, 90, boolean(TRUE) );
298 : /* Now do the compression .. */
299 200 : jpeg_start_compress( &cinfo, boolean(TRUE) );
300 : /* like reading a file, this time write one row at a time */
301 26450 : while( cinfo.next_scanline < cinfo.image_height ) {
302 26250 : row_pointer[0] = (JSAMPROW) &data[ (invert ? (state.height - 1 - cinfo.next_scanline) : cinfo.next_scanline) * state.stride ];
303 26250 : jpeg_write_scanlines( &cinfo, row_pointer, 1 );
304 : }
305 : /* similar to read file, clean up after we're done compressing */
306 200 : jpeg_finish_compress( &cinfo );
307 :
308 200 : if (vec) {
309 25 : if (memSize) {
310 25 : vec->assign(vec->target, mem, memSize);
311 : } else {
312 0 : return false;
313 : }
314 : }
315 :
316 200 : return true;
317 : }
318 : };
319 :
320 75 : static bool infoJpg(const uint8_t *inputData, size_t size, ImageInfo &outputData) {
321 75 : JpegReadStruct jpegStruct;
322 150 : return jpegStruct.init(inputData, size) && jpegStruct.info(outputData);
323 75 : }
324 :
325 100 : static bool loadJpg(const uint8_t *inputData, size_t size, BitmapWriter &outputData) {
326 100 : JpegReadStruct jpegStruct;
327 200 : return jpegStruct.init(inputData, size) && jpegStruct.load(outputData);
328 100 : }
329 :
330 175 : static bool saveJpeg(StringView filename, const uint8_t *data, BitmapWriter &state, bool invert) {
331 175 : JpegWriteStruct s(filename);
332 350 : return s.write(data, state, invert);
333 175 : }
334 :
335 75 : static bool writeJpeg(const uint8_t *data, BitmapWriter &state, bool invert) {
336 75 : JpegWriteStruct s(&state);
337 150 : return s.write(data, state, invert);
338 75 : }
339 :
340 : }
|