image_libjpeg.C

00001 /*
00002  * This file is part of the "Archon" framework.
00003  * (http://files3d.sourceforge.net)
00004  *
00005  * Copyright © 2002 by Kristian Spangsege and Brian Kristiansen.
00006  *
00007  * Permission to use, copy, modify, and distribute this software and
00008  * its documentation under the terms of the GNU General Public License is
00009  * hereby granted. No representations are made about the suitability of
00010  * this software for any purpose. It is provided "as is" without express
00011  * or implied warranty. See the GNU General Public License
00012  * (http://www.gnu.org/copyleft/gpl.html) for more details.
00013  *
00014  * The characters in this file are ISO8859-1 encoded.
00015  *
00016  * The documentation in this file is in "Doxygen" style
00017  * (http://www.doxygen.org).
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); // No logging since we are only probing.
00175 
00176         /*
00177          * ALERT: No exceptions may arise between this point and the
00178          * call to jpeg_create_decompress below.
00179          */
00180         LoadWrapper w;
00181 
00182         // Catch errors occuring in any of the following JPEG-functions:
00183         if(!setjmp(c.setjmpBuffer)) // try
00184         {
00185           w.cinfo.err = &c; // Downcast to jpeg_err_mgr
00186           jpeg_create_decompress(&w.cinfo);
00187           w.cinfo.src = &c; // Downcast to jpeg_source_mgr
00188           return jpeg_read_header(&w.cinfo, 1) == JPEG_HEADER_OK;
00189         }
00190         else // catch
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         // If possible use extra resources to improve the perceived
00207         // progressiveness of the loading process.
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          * ALERT: No exceptions may arise between this point and the
00218          * call to jpeg_create_decompress below.
00219          */
00220         LoadWrapper w;
00221 
00222         /*
00223          * Handle termination and catch errors occuring in any of the
00224          * following JPEG-functions.
00225          */
00226         if(!setjmp(c.setjmpBuffer)) // try
00227         {
00228           w.cinfo.err = &c; // Downcast to jpeg_err_mgr
00229           jpeg_create_decompress(&w.cinfo);
00230           w.cinfo.src = &c; // Downcast to jpeg_source_mgr
00231           if(tracker) w.cinfo.progress = &c; // Downcast to jpeg_progress_mgr
00232 
00233           // Load comments from stream
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           // Turn off progressive mode for files/streams with
00240           // single-scan images.
00241           progressive = progressive && jpeg_has_multiple_scans(&w.cinfo);
00242 
00243           if(progressive)
00244           {
00245             // select buffered-image mode
00246             w.cinfo.buffered_image = 1;
00247 
00248             // Run the initial passes with single-pass fixed color quantization
00249             w.cinfo.two_pass_quantize = 0;
00250             w.cinfo.colormap          = 0;
00251 
00252             // Prepare for shifting to two-pass optimum color quantization.
00253             w.cinfo.enable_2pass_quant = 1;
00254           }
00255 
00256           // SET UP PRESCALING AT THIS POINT.
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           // Fetch the image geometry
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           // Construct an image with an uninitialized pixel buffer
00273           image = Image(width, height, components, bitsPerComponent);
00274 
00275           // Make row array
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                * Absorb any waiting input by calling jpeg_consume_input().
00288                *
00289                * It seems to me there are two important aspectes of
00290                * this. First of all it can be used to advance the input
00291                * consumption to the point where a blocking read would
00292                * block. This is important in progressive visualization
00293                * mode, since it may allow us to skip display iterations
00294                * if the data arrives fast enough. The challenge here is
00295                * that it requires support for a non-blocking read on the
00296                * stream.
00297                *
00298                * The obvious procedure would then be to start by
00299                * switching the stream to non-blocking mode, then calling
00300                * jpeg_consume_input() repeatedly until it returns either
00301                * JPEG_SUSPENDED (stream reader would block) or
00302                * JPEG_REACHED_EOI.
00303                *
00304                * At this point we must....
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               // Run final pass with two-pass optimum color quantization.
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           // Transcode is done to destroy potentail illegal 8-bit data
00349           // from image.
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 // catch
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         // Transcode comment to enforce 7-bit ASCII
00377         string comment = CharEnc::transcode(image.getComment(), CharEnc::UTF_8, CharEnc::US_ASCII);
00378         Array<JSAMPROW> rowPointers;
00379         Array<JOCTET> row;
00380 
00381         /*
00382          * ALERT: No exceptions may arise between this point and the
00383          * call to jpeg_create_compress below.
00384          */
00385         SaveWrapper w;
00386 
00387         /*
00388          * Handle termination and catch errors occuring in any of the
00389          * following JPEG-functions.
00390          */
00391         if(!setjmp(c.setjmpBuffer)) // try
00392         {
00393           w.cinfo.err = &c; // Downcast to jpeg_err_mgr
00394           jpeg_create_compress(&w.cinfo);
00395           w.cinfo.dest = &c; // Downcast to jpeg_destination_mgr
00396           if(tracker) w.cinfo.progress = &c; // Downcast to jpeg_progress_mgr
00397 
00398           // Set header info
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           // Save comment
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             // Make row array
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) // L or LA (luminance)
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 // RGB or RGBA
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 // catch
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;       // For warnings
00488         jmp_buf setjmpBuffer; // For aborting from the LIBJPEG.
00489         int errorType;        // 0 for none, 1 for ThreadTerminatedException,
00490                               // 2 for IOException, 3 for UnexpectedException
00491                               // from the stream, 4 for libjpeg initiated
00492                               // error or invalid format detected.
00493         string errorMessage;  // For fatal errors
00494         string errorLocation; // Source file location from exception
00495         Context(Image::ProgressTracker *tracker, Logger *logger):
00496           tracker(tracker), logger(logger), errorType(0)
00497         {
00498           jpeg_std_error(this); // Start by defaulting everything, then
00499                                 // override
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;        // have we gotten any data yet?
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; // use default method
00557           term_source       = termCallback;
00558           bytes_in_buffer   = 0; // forces fill_input_buffer on first read
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             // Insert a fake EOI marker
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           /* Just a dumb implementation for now.  Could use fseek() except
00681            * it doesn't work on pipes.  Not clear that being smart is worth
00682            * any trouble anyway --- large skips are infrequent.
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               /* note we assume that fill_input_buffer will never return FALSE,
00691                * so suspension need not be handled.
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          * From LIBJPEG/example.c:
00701          *
00702          * An additional method that can be provided by data source modules is the
00703          * resync_to_restart method for error recovery in the presence of RST markers.
00704          * For the moment, this source module just uses the default resync method
00705          * provided by the JPEG library.  That method assumes that no backtracking
00706          * is possible.
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 }

Generated on Sun Jul 30 22:55:44 2006 for Archon by  doxygen 1.4.4