00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include <stdio.h>
00021 #include <errno.h>
00022 #include <setjmp.h>
00023 #include <iostream>
00024 #include <string>
00025
00026 #include <jpeglib.h>
00027 #include <jerror.h>
00028
00029 #include <archon/util/mutex.H>
00030 #include <archon/util/text.H>
00031 #include <archon/util/image.H>
00032 #include <archon/util/charenc.H>
00033
00034 #if BITS_IN_JSAMPLE != 8
00035 #error ERROR: Unsupported number of bits per color component in LIBJPEG.
00036 #endif
00037
00038 using namespace std;
00039
00040 namespace Archon
00041 {
00042 namespace Utilities
00043 {
00165 struct FormatLibjpeg: Image::Format
00166 {
00167 string getSpecifier() const
00168 {
00169 return "jpeg";
00170 }
00171
00172 bool checkSignature(Ref<Stream::Reader> r) const
00173 {
00174 LoadContext c(r, 0, 0);
00175
00176
00177
00178
00179
00180 LoadWrapper w;
00181
00182
00183 if(!setjmp(c.setjmpBuffer))
00184 {
00185 w.cinfo.err = &c;
00186 jpeg_create_decompress(&w.cinfo);
00187 w.cinfo.src = &c;
00188 return jpeg_read_header(&w.cinfo, 1) == JPEG_HEADER_OK;
00189 }
00190 else
00191 {
00192 return false;
00193 }
00194 }
00195
00196 bool checkSuffix(string s) const
00197 {
00198 return s == "jpg" || s == "jpeg";
00199 }
00200
00201 Image load(Ref<Stream::Reader> reader, Image::ProgressTracker *tracker,
00202 Logger *logger) const
00203 throw(Image::InvalidFormatException,
00204 IOException, UnexpectedException)
00205 {
00206
00207
00208 bool progressive = false;
00209
00210 LoadContext c(reader, tracker, logger);
00211
00212 string comment;
00213 Image image;
00214 Array<JSAMPROW> rowPointers;
00215
00216
00217
00218
00219
00220 LoadWrapper w;
00221
00222
00223
00224
00225
00226 if(!setjmp(c.setjmpBuffer))
00227 {
00228 w.cinfo.err = &c;
00229 jpeg_create_decompress(&w.cinfo);
00230 w.cinfo.src = &c;
00231 if(tracker) w.cinfo.progress = &c;
00232
00233
00234 jpeg_save_markers(&w.cinfo, JPEG_COM, 0xFFFF);
00235
00236 if(jpeg_read_header(&w.cinfo, 1) != JPEG_HEADER_OK)
00237 ARCHON_THROW1(Image::InvalidFormatException, "Not a JPEG header");
00238
00239
00240
00241 progressive = progressive && jpeg_has_multiple_scans(&w.cinfo);
00242
00243 if(progressive)
00244 {
00245
00246 w.cinfo.buffered_image = 1;
00247
00248
00249 w.cinfo.two_pass_quantize = 0;
00250 w.cinfo.colormap = 0;
00251
00252
00253 w.cinfo.enable_2pass_quant = 1;
00254 }
00255
00256
00257
00258 jpeg_start_decompress(&w.cinfo);
00259
00260 if(w.cinfo.output_components > 4)
00261 ARCHON_THROW1(Image::InvalidFormatException, "Image contains more than 4 color components per pixel");
00262
00263
00264 unsigned bitsPerComponent = 8;
00265 unsigned height = w.cinfo.output_height;
00266 unsigned width = w.cinfo.output_width;
00267 Image::ComponentSpecifier components =
00268 static_cast<Image::ComponentSpecifier>(w.cinfo.output_components);
00269 unsigned charsPerPixel = (bitsPerComponent*components+7)/8;
00270 unsigned charsPerRow = charsPerPixel*width;
00271
00272
00273 image = Image(width, height, components, bitsPerComponent);
00274
00275
00276 JSAMPROW pixelBuffer =
00277 reinterpret_cast<JSAMPROW>(getPixelBufferNoLeak(image));
00278 rowPointers.reset(height);
00279 for(unsigned i=0; i<height; ++i)
00280 rowPointers[i] = pixelBuffer + (height-i-1)*charsPerRow;
00281
00282 if(progressive)
00283 {
00284 for(;;)
00285 {
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306 switch(jpeg_consume_input(&w.cinfo))
00307 {
00308 case JPEG_REACHED_SOS: cerr << "JPEG_REACHED_SOS\n";
00309 case JPEG_REACHED_EOI: cerr << "JPEG_REACHED_EOI\n";
00310 case JPEG_ROW_COMPLETED: cerr << "JPEG_ROW_COMPLETED\n";
00311 case JPEG_SCAN_COMPLETED: cerr << "JPEG_SCAN_COMPLETED\n";
00312 case JPEG_SUSPENDED: cerr << "JPEG_SUSPENDED\n";
00313 }
00314
00315 bool final_pass = jpeg_input_complete(&w.cinfo);
00316
00317
00318 if(final_pass)
00319 {
00320 w.cinfo.two_pass_quantize = 1;
00321 w.cinfo.colormap = 0;
00322 }
00323
00324 jpeg_start_output(&w.cinfo, w.cinfo.input_scan_number);
00325 while(w.cinfo.output_scanline < height)
00326 jpeg_read_scanlines(&w.cinfo, rowPointers.get()+w.cinfo.output_scanline, height-w.cinfo.output_scanline);
00327 jpeg_finish_output(&w.cinfo);
00328
00329 if(final_pass) break;
00330 }
00331 }
00332 else
00333 {
00334 while(w.cinfo.output_scanline < height)
00335 jpeg_read_scanlines(&w.cinfo, rowPointers.get()+w.cinfo.output_scanline, height-w.cinfo.output_scanline);
00336 }
00337
00338 jpeg_saved_marker_ptr text = w.cinfo.marker_list;
00339 while(text)
00340 {
00341 string c = Text::lineTrim(string(reinterpret_cast<char *>(text->data), text->data_length));
00342 if(c.empty())
00343 if(comment.empty()) comment += "\n\n";
00344 comment += c;
00345 text = text->next;
00346 }
00347
00348
00349
00350 if(!comment.empty())
00351 image.setComment(CharEnc::transcode(comment, CharEnc::US_ASCII, CharEnc::UTF_8));
00352
00353 jpeg_finish_decompress(&w.cinfo);
00354 }
00355 else
00356 {
00357 switch(c.errorType)
00358 {
00359 case 1: throw ThreadTerminatedException(c.errorLocation);
00360 case 2: throw Stream::ReadException(c.errorLocation, c.errorMessage);
00361 case 3: throw UnexpectedException(c.errorLocation, c.errorMessage);
00362 }
00363 ARCHON_THROW1(Image::InvalidFormatException, c.errorMessage);
00364 }
00365
00366 return image;
00367 }
00368
00369
00370 void save(Image image, Ref<Stream::Writer> writer,
00371 Image::ProgressTracker *tracker, Logger *logger) const
00372 throw(IOException, UnexpectedException)
00373 {
00374 SaveContext c(writer, tracker, logger);
00375
00376
00377 string comment = CharEnc::transcode(image.getComment(), CharEnc::UTF_8, CharEnc::US_ASCII);
00378 Array<JSAMPROW> rowPointers;
00379 Array<JOCTET> row;
00380
00381
00382
00383
00384
00385 SaveWrapper w;
00386
00387
00388
00389
00390
00391 if(!setjmp(c.setjmpBuffer))
00392 {
00393 w.cinfo.err = &c;
00394 jpeg_create_compress(&w.cinfo);
00395 w.cinfo.dest = &c;
00396 if(tracker) w.cinfo.progress = &c;
00397
00398
00399 unsigned width = image.getWidth();
00400 unsigned height = image.getHeight();
00401 Image::ComponentSpecifier components = image.getComponentSpecifier();
00402 bool gray =
00403 components == Image::components_l ||
00404 components == Image::components_la;
00405 w.cinfo.image_width = image.getWidth();
00406 w.cinfo.image_height = height;
00407 w.cinfo.input_components = gray ? 1 : 3;
00408 w.cinfo.in_color_space = gray ? JCS_GRAYSCALE : JCS_RGB;
00409 jpeg_set_defaults(&w.cinfo);
00410
00411 jpeg_start_compress(&w.cinfo, 1);
00412
00413
00414 jpeg_write_marker(&w.cinfo, JPEG_COM, reinterpret_cast<const JOCTET *>(comment.data()), comment.size());
00415
00416 if((components == Image::components_l ||
00417 components == Image::components_rgb) &&
00418 image.getBitsPerComponent() == 8)
00419 {
00420
00421 JSAMPROW pixelBuffer =
00422 reinterpret_cast<JSAMPROW>(getPixelBufferNoLeak(image));
00423 rowPointers.reset(height);
00424 for(unsigned i=0; i<height; ++i)
00425 rowPointers[i] = pixelBuffer + (height-i-1)*image.getCharsPerRow();
00426
00427 while(w.cinfo.next_scanline < height)
00428 jpeg_write_scanlines(&w.cinfo, rowPointers.get()+w.cinfo.next_scanline, height-w.cinfo.next_scanline);
00429 }
00430 else if(gray)
00431 {
00432 row.reset(width);
00433 JSAMPROW rows[1];
00434 rows[0] = row.get();
00435 while(w.cinfo.next_scanline < height)
00436 {
00437 int y = height-w.cinfo.next_scanline-1;
00438 for(unsigned x=0; x<width; ++x)
00439 {
00440 unsigned l;
00441 image.getPixel(x, y, l);
00442 row[x] = static_cast<JOCTET>(l);
00443 }
00444 jpeg_write_scanlines(&w.cinfo, rows, 1);
00445 }
00446 }
00447 else
00448 {
00449 row.reset(width*3);
00450 JSAMPROW rows[1];
00451 rowPointers[0] = row.get();
00452 while(w.cinfo.next_scanline < height)
00453 {
00454 int y = height-w.cinfo.next_scanline-1;
00455 for(unsigned x=0; x<width; ++x)
00456 {
00457 unsigned r, g, b;
00458 image.getPixel(x, y, r, g, b);
00459 int i = x*3;
00460 row[i] = static_cast<JOCTET>(r);
00461 row[i+1] = static_cast<JOCTET>(g);
00462 row[i+2] = static_cast<JOCTET>(b);
00463 }
00464 jpeg_write_scanlines(&w.cinfo, rows, 1);
00465 }
00466 }
00467
00468 jpeg_finish_compress(&w.cinfo);
00469 }
00470 else
00471 {
00472 switch(c.errorType)
00473 {
00474 case 1: throw ThreadTerminatedException(c.errorLocation);
00475 case 2: throw Stream::ReadException(c.errorLocation, c.errorMessage);
00476 case 3: throw UnexpectedException(c.errorLocation, c.errorMessage);
00477 }
00478 ARCHON_THROW1(Image::InvalidFormatException, c.errorMessage);
00479 }
00480 }
00481
00482 private:
00483
00484 struct Context: jpeg_progress_mgr, jpeg_error_mgr
00485 {
00486 Image::ProgressTracker *tracker;
00487 Logger *logger;
00488 jmp_buf setjmpBuffer;
00489 int errorType;
00490
00491
00492
00493 string errorMessage;
00494 string errorLocation;
00495 Context(Image::ProgressTracker *tracker, Logger *logger):
00496 tracker(tracker), logger(logger), errorType(0)
00497 {
00498 jpeg_std_error(this);
00499
00500 progress_monitor = progressCallback;
00501 error_exit = errorCallback;
00502 output_message = warningCallback;
00503 }
00504
00505 void warning(string message)
00506 {
00507 if(logger) logger->log(message);
00508 }
00509
00510 static void progressCallback(j_common_ptr cinfo) throw()
00511 {
00512 Context *c = static_cast<Context *>(cinfo->progress);
00513 double p = c->completed_passes +
00514 static_cast<double>(c->pass_counter)/c->pass_limit;
00515 c->tracker->progress(p/c->total_passes);
00516 }
00517
00518 static void errorCallback(j_common_ptr cinfo) throw()
00519 {
00520 Context *c = static_cast<Context *>(cinfo->err);
00521 {
00522 char buffer[JMSG_LENGTH_MAX];
00523 (*cinfo->err->format_message)(cinfo, buffer);
00524 c->errorType = 4;
00525 c->errorMessage = buffer;
00526 }
00527 longjmp(c->setjmpBuffer, 1);
00528 }
00529
00530 static void warningCallback(j_common_ptr cinfo) throw()
00531 {
00532 Context *c = static_cast<Context *>(cinfo->err);
00533 char buffer[JMSG_LENGTH_MAX];
00534 (*cinfo->err->format_message)(cinfo, buffer);
00535 c->warning(buffer);
00536 }
00537 };
00538
00542 static const int bufferSize = 4096;
00543
00544 struct LoadContext: jpeg_source_mgr, Context
00545 {
00546 Array<JOCTET> buffer;
00547 bool startOfFile;
00548 Ref<Stream::Reader> reader;
00549 LoadContext(Ref<Stream::Reader> reader, Image::ProgressTracker *tracker,
00550 Logger *logger): Context(tracker, logger), buffer(bufferSize),
00551 startOfFile(true), reader(reader)
00552 {
00553 init_source = initCallback;
00554 fill_input_buffer = readCallback;
00555 skip_input_data = skipCallback;
00556 resync_to_restart = jpeg_resync_to_restart;
00557 term_source = termCallback;
00558 bytes_in_buffer = 0;
00559 next_input_byte = 0;
00560 }
00561
00568 static void initCallback(j_decompress_ptr cinfo) throw() {}
00569
00610 static int readCallback(j_decompress_ptr cinfo) throw()
00611 {
00612 LoadContext *c = static_cast<LoadContext *>(cinfo->src);
00613 int n = 0;
00614 try
00615 {
00616 n = c->reader->read(reinterpret_cast<char *>(c->buffer.get()),
00617 bufferSize);
00618 }
00619 catch(ThreadTerminatedException &e)
00620 {
00621 c->errorType = 1;
00622 c->errorLocation = e.getLocation();
00623 }
00624 catch(IOException &e)
00625 {
00626 c->errorType = 2;
00627 c->errorLocation = e.getLocation();
00628 c->errorMessage = e.getMessage();
00629 }
00630 catch(UnexpectedException &e)
00631 {
00632 c->errorType = 3;
00633 c->errorLocation = e.getLocation();
00634 c->errorMessage = e.getMessage();
00635 }
00636
00637 if(c->errorType == 1 || c->errorType == 2 && c->startOfFile ||
00638 c->errorType == 3) longjmp(c->setjmpBuffer, 1);
00639
00640 if(n < 1)
00641 {
00642 if(c->startOfFile)
00643 {
00644 ERREXIT(cinfo, JERR_INPUT_EMPTY);
00645 }
00646
00647 if(c->errorType == 2) c->warning(c->errorMessage);
00648 else WARNMS(cinfo, JWRN_JPEG_EOF);
00649
00650
00651 c->buffer[0] = static_cast<JOCTET>(0xFF);
00652 c->buffer[1] = static_cast<JOCTET>(JPEG_EOI);
00653 n = 2;
00654 }
00655
00656 c->next_input_byte = c->buffer.get();
00657 c->bytes_in_buffer = n;
00658 c->startOfFile = false;
00659
00660 return 1;
00661 }
00662
00676 static void skipCallback(j_decompress_ptr cinfo, long num_bytes) throw()
00677 {
00678 LoadContext *c = static_cast<LoadContext *>(cinfo->src);
00679
00680
00681
00682
00683
00684 if(num_bytes > 0)
00685 {
00686 while(num_bytes > static_cast<long>(c->bytes_in_buffer))
00687 {
00688 num_bytes -= static_cast<long>(c->bytes_in_buffer);
00689 readCallback(cinfo);
00690
00691
00692
00693 }
00694 c->next_input_byte += static_cast<size_t>(num_bytes);
00695 c->bytes_in_buffer -= static_cast<size_t>(num_bytes);
00696 }
00697 }
00698
00699
00700
00701
00702
00703
00704
00705
00706
00707
00708
00719 static void termCallback(j_decompress_ptr cinfo) throw() {}
00720 };
00721
00722 struct SaveContext: jpeg_destination_mgr, Context
00723 {
00724 Array<JOCTET> buffer;
00725 Ref<Stream::Writer> writer;
00726 SaveContext(Ref<Stream::Writer> writer, Image::ProgressTracker *tracker,
00727 Logger *logger):
00728 Context(tracker, logger), buffer(bufferSize), writer(writer)
00729 {
00730 init_destination = initCallback;
00731 empty_output_buffer = writeCallback;
00732 term_destination = termCallback;
00733 next_output_byte = buffer.get();
00734 free_in_buffer = bufferSize;
00735 }
00736
00743 static void initCallback(j_compress_ptr cinfo) throw() {}
00744
00769 static int writeCallback(j_compress_ptr cinfo) throw()
00770 {
00771 SaveContext *c = static_cast<SaveContext *>(cinfo->dest);
00772
00773 try
00774 {
00775 c->writer->writeAll(reinterpret_cast<const char *>(c->buffer.get()),
00776 bufferSize);
00777 c->next_output_byte = c->buffer.get();
00778 c->free_in_buffer = bufferSize;
00779 return 1;
00780 }
00781 catch(ThreadTerminatedException &e)
00782 {
00783 c->errorType = 1;
00784 c->errorLocation = e.getLocation();
00785 }
00786 catch(IOException &e)
00787 {
00788 c->errorType = 2;
00789 c->errorLocation = e.getLocation();
00790 c->errorMessage = e.getMessage();
00791 }
00792 catch(UnexpectedException &e)
00793 {
00794 c->errorType = 3;
00795 c->errorLocation = e.getLocation();
00796 c->errorMessage = e.getMessage();
00797 }
00798
00799 longjmp(c->setjmpBuffer, 1);
00800 }
00801
00812 static void termCallback(j_compress_ptr cinfo) throw()
00813 {
00814 SaveContext *c = static_cast<SaveContext *>(cinfo->dest);
00815
00816 try
00817 {
00818 c->writer->writeAll(reinterpret_cast<const char *>(c->buffer.get()),
00819 bufferSize - c->free_in_buffer);
00820 return;
00821 }
00822 catch(ThreadTerminatedException &e)
00823 {
00824 c->errorType = 1;
00825 c->errorLocation = e.getLocation();
00826 }
00827 catch(IOException &e)
00828 {
00829 c->errorType = 2;
00830 c->errorLocation = e.getLocation();
00831 c->errorMessage = e.getMessage();
00832 }
00833 catch(UnexpectedException &e)
00834 {
00835 c->errorType = 3;
00836 c->errorLocation = e.getLocation();
00837 c->errorMessage = e.getMessage();
00838 }
00839
00840 longjmp(c->setjmpBuffer, 1);
00841 }
00842 };
00843
00844
00862 struct LoadWrapper
00863 {
00864 jpeg_decompress_struct cinfo;
00865 ~LoadWrapper()
00866 {
00867 cinfo.err = 0;
00868 cinfo.src = 0;
00869 jpeg_destroy_decompress(&cinfo);
00870 }
00871 };
00872
00873
00877 struct SaveWrapper
00878 {
00879 jpeg_compress_struct cinfo;
00880 ~SaveWrapper()
00881 {
00882 cinfo.err = 0;
00883 cinfo.dest = 0;
00884 jpeg_destroy_compress(&cinfo);
00885 }
00886 };
00887 };
00888
00889 Image::Format *getDefaultJpegFormat()
00890 {
00891 static FormatLibjpeg f;
00892 return &f;
00893 }
00894 }
00895 }