1// Aseprite
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/app.h"
13#include "app/console.h"
14#include "app/context.h"
15#include "app/doc.h"
16#include "app/file/file.h"
17#include "app/file/file_format.h"
18#include "app/file/format_options.h"
19#include "app/find_widget.h"
20#include "app/load_widget.h"
21#include "app/pref/preferences.h"
22#include "base/file_handle.h"
23#include "base/memory.h"
24#include "doc/doc.h"
25
26#include <algorithm>
27#include <csetjmp>
28#include <cstdio>
29#include <cstdlib>
30
31#include "jpeg_options.xml.h"
32
33#include "jpeglib.h"
34
35namespace app {
36
37using namespace base;
38
39class JpegFormat : public FileFormat {
40 // Data for JPEG files
41 class JpegOptions : public FormatOptions {
42 public:
43 JpegOptions() : quality(1.0f) { } // 1.0 maximum quality.
44 float quality;
45 };
46
47 const char* onGetName() const override {
48 return "jpeg";
49 }
50
51 void onGetExtensions(base::paths& exts) const override {
52 exts.push_back("jpeg");
53 exts.push_back("jpg");
54 }
55
56 dio::FileFormat onGetDioFormat() const override {
57 return dio::FileFormat::JPEG_IMAGE;
58 }
59
60 int onGetFlags() const override {
61 return
62 FILE_SUPPORT_LOAD |
63 FILE_SUPPORT_SAVE |
64 FILE_SUPPORT_RGB |
65 FILE_SUPPORT_GRAY |
66 FILE_SUPPORT_SEQUENCES |
67 FILE_SUPPORT_GET_FORMAT_OPTIONS |
68 FILE_ENCODE_ABSTRACT_IMAGE;
69 }
70
71 bool onLoad(FileOp* fop) override;
72 gfx::ColorSpaceRef loadColorSpace(FileOp* fop, jpeg_decompress_struct* dinfo);
73#ifdef ENABLE_SAVE
74 bool onSave(FileOp* fop) override;
75 void saveColorSpace(FileOp* fop, jpeg_compress_struct* cinfo,
76 const gfx::ColorSpace* colorSpace);
77#endif
78
79 FormatOptionsPtr onAskUserForFormatOptions(FileOp* fop) override;
80};
81
82FileFormat* CreateJpegFormat()
83{
84 return new JpegFormat;
85}
86
87struct error_mgr {
88 struct jpeg_error_mgr head;
89 jmp_buf setjmp_buffer;
90 FileOp* fop;
91};
92
93static void error_exit(j_common_ptr cinfo)
94{
95 // Display the message.
96 (*cinfo->err->output_message)(cinfo);
97
98 // Return control to the setjmp point.
99 longjmp(((struct error_mgr *)cinfo->err)->setjmp_buffer, 1);
100}
101
102static void output_message(j_common_ptr cinfo)
103{
104 char buffer[JMSG_LENGTH_MAX];
105
106 // Format the message.
107 (*cinfo->err->format_message)(cinfo, buffer);
108
109 // Put in the log file if.
110 LOG(ERROR, "JPEG: \"%s\"\n", buffer);
111
112 // Leave the message for the application.
113 ((struct error_mgr *)cinfo->err)->fop->setError("%s\n", buffer);
114}
115
116// Some code to read color spaces from jpeg files is from Skia
117// (SkJpegCodec.cpp) by Google Inc.
118static constexpr uint32_t kMarkerMaxSize = 65533;
119static constexpr uint32_t kICCMarker = JPEG_APP0 + 2;
120static constexpr uint32_t kICCMarkerHeaderSize = 14;
121static constexpr uint32_t kICCAvailDataPerMarker = (kMarkerMaxSize - kICCMarkerHeaderSize);
122static constexpr uint8_t kICCSig[] = { 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0' };
123
124static bool is_icc_marker(jpeg_marker_struct* marker)
125{
126 if (kICCMarker != marker->marker ||
127 marker->data_length < kICCMarkerHeaderSize) {
128 return false;
129 }
130 else
131 return !memcmp(marker->data, kICCSig, sizeof(kICCSig));
132}
133
134bool JpegFormat::onLoad(FileOp* fop)
135{
136 struct jpeg_decompress_struct dinfo;
137 struct error_mgr jerr;
138 JDIMENSION num_scanlines;
139 JSAMPARRAY buffer;
140 JDIMENSION buffer_height;
141 int c;
142
143 FileHandle handle(open_file_with_exception(fop->filename(), "rb"));
144 FILE* file = handle.get();
145
146 // Initialize the JPEG decompression object with error handling.
147 jerr.fop = fop;
148 dinfo.err = jpeg_std_error(&jerr.head);
149
150 jerr.head.error_exit = error_exit;
151 jerr.head.output_message = output_message;
152
153 // Establish the setjmp return context for error_exit to use.
154 if (setjmp(jerr.setjmp_buffer)) {
155 jpeg_destroy_decompress(&dinfo);
156 return false;
157 }
158
159 jpeg_create_decompress(&dinfo);
160
161 // Specify data source for decompression.
162 jpeg_stdio_src(&dinfo, file);
163
164 // Instruct jpeg library to save the markers that we care
165 // about. Since the color profile will not change, we can skip this
166 // step on rewinds.
167 jpeg_save_markers(&dinfo, kICCMarker, 0xFFFF);
168
169 // Read file header, set default decompression parameters.
170 jpeg_read_header(&dinfo, true);
171
172 if (dinfo.jpeg_color_space == JCS_GRAYSCALE)
173 dinfo.out_color_space = JCS_GRAYSCALE;
174 else
175 dinfo.out_color_space = JCS_RGB;
176
177 // Start decompressor.
178 jpeg_start_decompress(&dinfo);
179
180 // Create the image.
181 ImageRef image = fop->sequenceImage(
182 (dinfo.out_color_space == JCS_RGB ? IMAGE_RGB:
183 IMAGE_GRAYSCALE),
184 dinfo.output_width,
185 dinfo.output_height);
186 if (!image) {
187 jpeg_destroy_decompress(&dinfo);
188 return false;
189 }
190
191 // Create the buffer.
192 buffer_height = dinfo.rec_outbuf_height;
193 buffer = (JSAMPARRAY)base_malloc(sizeof(JSAMPROW) * buffer_height);
194 if (!buffer) {
195 jpeg_destroy_decompress(&dinfo);
196 return false;
197 }
198
199 for (c=0; c<(int)buffer_height; c++) {
200 buffer[c] = (JSAMPROW)base_malloc(sizeof(JSAMPLE) *
201 dinfo.output_width * dinfo.output_components);
202 if (!buffer[c]) {
203 for (c--; c>=0; c--)
204 base_free(buffer[c]);
205 base_free(buffer);
206 jpeg_destroy_decompress(&dinfo);
207 return false;
208 }
209 }
210
211 // Generate a grayscale palette if is necessary.
212 if (image->pixelFormat() == IMAGE_GRAYSCALE)
213 for (c=0; c<256; c++)
214 fop->sequenceSetColor(c, c, c, c);
215
216 // Read each scan line.
217 while (dinfo.output_scanline < dinfo.output_height) {
218 num_scanlines = jpeg_read_scanlines(&dinfo, buffer, buffer_height);
219
220 // RGB
221 if (image->pixelFormat() == IMAGE_RGB) {
222 uint8_t* src_address;
223 uint32_t* dst_address;
224 int x, y, r, g, b;
225
226 for (y=0; y<(int)num_scanlines; y++) {
227 src_address = ((uint8_t**)buffer)[y];
228 dst_address = (uint32_t*)image->getPixelAddress(0, dinfo.output_scanline-1+y);
229
230 for (x=0; x<image->width(); x++) {
231 r = *(src_address++);
232 g = *(src_address++);
233 b = *(src_address++);
234 *(dst_address++) = rgba(r, g, b, 255);
235 }
236 }
237 }
238 // Grayscale
239 else {
240 uint8_t* src_address;
241 uint16_t* dst_address;
242 int x, y;
243
244 for (y=0; y<(int)num_scanlines; y++) {
245 src_address = ((uint8_t**)buffer)[y];
246 dst_address = (uint16_t*)image->getPixelAddress(0, dinfo.output_scanline-1+y);
247
248 for (x=0; x<image->width(); x++)
249 *(dst_address++) = graya(*(src_address++), 255);
250 }
251 }
252
253 fop->setProgress((float)(dinfo.output_scanline+1) / (float)(dinfo.output_height));
254 if (fop->isStop())
255 break;
256 }
257
258 // Read color space
259 gfx::ColorSpaceRef colorSpace = loadColorSpace(fop, &dinfo);
260 if (colorSpace)
261 fop->setEmbeddedColorProfile();
262 else { // sRGB is the default JPG color space.
263 colorSpace = gfx::ColorSpace::MakeSRGB();
264 }
265 if (colorSpace &&
266 fop->document()->sprite()->colorSpace()->type() == gfx::ColorSpace::None) {
267 fop->document()->sprite()->setColorSpace(colorSpace);
268 fop->document()->notifyColorSpaceChanged();
269 }
270
271 for (c=0; c<(int)buffer_height; c++)
272 base_free(buffer[c]);
273 base_free(buffer);
274
275 jpeg_finish_decompress(&dinfo);
276 jpeg_destroy_decompress(&dinfo);
277
278 return true;
279}
280
281// ICC profiles may be stored using a sequence of multiple markers. We obtain the ICC profile
282// in two steps:
283// (1) Discover all ICC profile markers and verify that they are numbered properly.
284// (2) Copy the data from each marker into a contiguous ICC profile.
285gfx::ColorSpaceRef JpegFormat::loadColorSpace(FileOp* fop, jpeg_decompress_struct* dinfo)
286{
287 // Note that 256 will be enough storage space since each markerIndex is stored in 8-bits.
288 jpeg_marker_struct* markerSequence[256];
289 memset(markerSequence, 0, sizeof(markerSequence));
290 uint8_t numMarkers = 0;
291 size_t totalBytes = 0;
292
293 // Discover any ICC markers and verify that they are numbered properly.
294 for (jpeg_marker_struct* marker = dinfo->marker_list; marker; marker = marker->next) {
295 if (is_icc_marker(marker)) {
296 // Verify that numMarkers is valid and consistent.
297 if (0 == numMarkers) {
298 numMarkers = marker->data[13];
299 if (0 == numMarkers) {
300 fop->setError("ICC Profile Error: numMarkers must be greater than zero.\n");
301 return nullptr;
302 }
303 }
304 else if (numMarkers != marker->data[13]) {
305 fop->setError("ICC Profile Error: numMarkers must be consistent.\n");
306 return nullptr;
307 }
308
309 // Verify that the markerIndex is valid and unique. Note that zero is not
310 // a valid index.
311 uint8_t markerIndex = marker->data[12];
312 if (markerIndex == 0 || markerIndex > numMarkers) {
313 fop->setError("ICC Profile Error: markerIndex is invalid.\n");
314 return nullptr;
315 }
316 if (markerSequence[markerIndex]) {
317 fop->setError("ICC Profile Error: Duplicate value of markerIndex.\n");
318 return nullptr;
319 }
320 markerSequence[markerIndex] = marker;
321 ASSERT(marker->data_length >= kICCMarkerHeaderSize);
322 totalBytes += marker->data_length - kICCMarkerHeaderSize;
323 }
324 }
325
326 if (0 == totalBytes) {
327 // No non-empty ICC profile markers were found.
328 return nullptr;
329 }
330
331 // Combine the ICC marker data into a contiguous profile.
332 std::vector<uint8_t> iccData(totalBytes);
333 uint8_t* dst = &iccData[0];
334 for (uint32_t i = 1; i <= numMarkers; i++) {
335 jpeg_marker_struct* marker = markerSequence[i];
336 if (!marker) {
337 fop->setError("ICC Profile Error: Missing marker %d of %d.\n", i, numMarkers);
338 return nullptr;
339 }
340
341 uint8_t* src = ((uint8_t*)marker->data) + kICCMarkerHeaderSize;
342 size_t bytes = marker->data_length - kICCMarkerHeaderSize;
343 memcpy(dst, src, bytes);
344 dst = dst + bytes;
345 }
346
347 return gfx::ColorSpace::MakeICC(std::move(iccData));
348}
349
350#ifdef ENABLE_SAVE
351
352bool JpegFormat::onSave(FileOp* fop)
353{
354 struct jpeg_compress_struct cinfo;
355 struct error_mgr jerr;
356 const FileAbstractImage* img = fop->abstractImage();
357 const ImageSpec spec = img->spec();
358 JSAMPARRAY buffer;
359 JDIMENSION buffer_height;
360 const auto jpeg_options = std::static_pointer_cast<JpegOptions>(fop->formatOptions());
361 const int qualityValue =
362 (jpeg_options ? (int)std::clamp(100.0f * jpeg_options->quality, 0.f, 100.f):
363 100);
364
365 int c;
366
367 LOG("JPEG: Saving with options: quality=%d\n", qualityValue);
368
369 // Open the file for write in it.
370 FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
371 FILE* file = handle.get();
372
373 // Allocate and initialize JPEG compression object.
374 jerr.fop = fop;
375 cinfo.err = jpeg_std_error(&jerr.head);
376 jpeg_create_compress(&cinfo);
377
378 // SPECIFY data destination file.
379 jpeg_stdio_dest(&cinfo, file);
380
381 // SET parameters for compression.
382 cinfo.image_width = spec.width();
383 cinfo.image_height = spec.height();
384
385 if (spec.colorMode() == ColorMode::GRAYSCALE) {
386 cinfo.input_components = 1;
387 cinfo.in_color_space = JCS_GRAYSCALE;
388 }
389 else {
390 cinfo.input_components = 3;
391 cinfo.in_color_space = JCS_RGB;
392 }
393
394 jpeg_set_defaults(&cinfo);
395 jpeg_set_quality(&cinfo, qualityValue, true);
396 cinfo.dct_method = JDCT_ISLOW;
397 cinfo.smoothing_factor = 0;
398
399 // START compressor.
400 jpeg_start_compress(&cinfo, true);
401
402 // Save color space
403 if (fop->preserveColorProfile() &&
404 fop->document()->sprite()->colorSpace())
405 saveColorSpace(fop, &cinfo, fop->document()->sprite()->colorSpace().get());
406
407 // CREATE the buffer.
408 buffer_height = 1;
409 buffer = (JSAMPARRAY)base_malloc(sizeof(JSAMPROW) * buffer_height);
410 if (!buffer) {
411 fop->setError("Not enough memory for the buffer.\n");
412 jpeg_destroy_compress(&cinfo);
413 return false;
414 }
415
416 for (c=0; c<(int)buffer_height; c++) {
417 buffer[c] = (JSAMPROW)base_malloc(sizeof(JSAMPLE) *
418 cinfo.image_width * cinfo.num_components);
419 if (!buffer[c]) {
420 fop->setError("Not enough memory for buffer scanlines.\n");
421 for (c--; c>=0; c--)
422 base_free(buffer[c]);
423 base_free(buffer);
424 jpeg_destroy_compress(&cinfo);
425 return false;
426 }
427 }
428
429 // Write each scan line.
430 while (cinfo.next_scanline < cinfo.image_height) {
431 // RGB
432 if (spec.colorMode() == ColorMode::RGB) {
433 uint32_t* src_address;
434 uint8_t* dst_address;
435 int x, y;
436 for (y=0; y<(int)buffer_height; y++) {
437 src_address = (uint32_t*)img->getScanline(cinfo.next_scanline+y);
438 dst_address = ((uint8_t**)buffer)[y];
439
440 for (x=0; x<spec.width(); ++x) {
441 c = *(src_address++);
442 *(dst_address++) = rgba_getr(c);
443 *(dst_address++) = rgba_getg(c);
444 *(dst_address++) = rgba_getb(c);
445 }
446 }
447 }
448 // Grayscale.
449 else {
450 uint16_t* src_address;
451 uint8_t* dst_address;
452 int x, y;
453 for (y=0; y<(int)buffer_height; y++) {
454 src_address = (uint16_t*)img->getScanline(cinfo.next_scanline+y);
455 dst_address = ((uint8_t**)buffer)[y];
456 for (x=0; x<spec.width(); ++x)
457 *(dst_address++) = graya_getv(*(src_address++));
458 }
459 }
460 jpeg_write_scanlines(&cinfo, buffer, buffer_height);
461
462 fop->setProgress((float)(cinfo.next_scanline+1) / (float)(cinfo.image_height));
463 }
464
465 // Destroy all data.
466 for (c=0; c<(int)buffer_height; c++)
467 base_free(buffer[c]);
468 base_free(buffer);
469
470 // Finish compression.
471 jpeg_finish_compress(&cinfo);
472
473 // Release JPEG compression object.
474 jpeg_destroy_compress(&cinfo);
475
476 // All fine.
477 return true;
478}
479
480void JpegFormat::saveColorSpace(FileOp* fop, jpeg_compress_struct* cinfo,
481 const gfx::ColorSpace* colorSpace)
482{
483 if (!colorSpace || colorSpace->type() != gfx::ColorSpace::ICC)
484 return;
485
486 size_t iccSize = colorSpace->iccSize();
487 auto iccData = (const uint8_t*)colorSpace->iccData();
488 if (!iccSize || !iccData)
489 return;
490
491 std::vector<uint8_t> markerData(kMarkerMaxSize);
492 int markerIndex = 1;
493 int numMarkers =
494 (iccSize / kICCAvailDataPerMarker) +
495 (iccSize % kICCAvailDataPerMarker > 0 ? 1: 0);
496
497 // ICC profile too big to fit in JPEG markers (64kb*255 ~= 16mb)
498 if (numMarkers > 255) {
499 fop->setError("ICC profile is too big to enter in the JPEG file.\n");
500 return;
501 }
502
503 while (iccSize > 0) {
504 const size_t n = std::min<int>(iccSize, kICCAvailDataPerMarker);
505
506 ASSERT(n > 0);
507 ASSERT(n < kICCAvailDataPerMarker);
508
509 // Marker Header
510 std::copy(kICCSig, kICCSig+sizeof(kICCSig), &markerData[0]);
511 markerData[sizeof(kICCSig) ] = markerIndex;
512 markerData[sizeof(kICCSig)+1] = numMarkers;
513
514 // Marker Data
515 std::copy(iccData, iccData+n, &markerData[kICCMarkerHeaderSize]);
516
517 jpeg_write_marker(cinfo, kICCMarker, &markerData[0], kICCMarkerHeaderSize + n);
518
519 ++markerIndex;
520 iccSize -= n;
521 iccData += n;
522 }
523}
524
525#endif // ENABLE_SAVE
526
527// Shows the JPEG configuration dialog.
528FormatOptionsPtr JpegFormat::onAskUserForFormatOptions(FileOp* fop)
529{
530 auto opts = fop->formatOptionsOfDocument<JpegOptions>();
531#ifdef ENABLE_UI
532 if (fop->context() && fop->context()->isUIAvailable()) {
533 try {
534 auto& pref = Preferences::instance();
535
536 if (pref.isSet(pref.jpeg.quality))
537 opts->quality = pref.jpeg.quality();
538
539 if (pref.jpeg.showAlert()) {
540 app::gen::JpegOptions win;
541 win.quality()->setValue(int(opts->quality * 10.0f));
542 win.openWindowInForeground();
543
544 if (win.closer() == win.ok()) {
545 pref.jpeg.quality(float(win.quality()->getValue()) / 10.0f);
546 pref.jpeg.showAlert(!win.dontShow()->isSelected());
547
548 opts->quality = pref.jpeg.quality();
549 }
550 else {
551 opts.reset();
552 }
553 }
554 }
555 catch (std::exception& e) {
556 Console::showException(e);
557 return std::shared_ptr<JpegOptions>(0);
558 }
559 }
560#endif // ENABLE_UI
561 return opts;
562}
563
564} // namespace app
565