00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include <errno.h>
00021
00022 #include <cstdio>
00023
00024 #include <iostream>
00025 #include <string>
00026
00027 #include <png.h>
00028
00029
00030 #include <setjmp.h>
00031
00032 #include <archon/util/mutex.H>
00033 #include <archon/util/text.H>
00034 #include <archon/util/charenc.H>
00035
00036 #include <archon/util/image_png.H>
00037
00038 using namespace std;
00039
00040 namespace Archon
00041 {
00042 namespace Utilities
00043 {
00044
00104 struct FormatLibpng: Image::Format
00105 {
00106 string getSpecifier() const
00107 {
00108 return "png";
00109 }
00110
00111 bool checkSignature(Ref<Stream::Reader> r) const
00112 {
00113 char header[8];
00114 return r->readAll(header, 8)==8 &&
00115 !png_sig_cmp(reinterpret_cast<png_bytep>(header), 0, 8);
00116 }
00117
00118 bool checkSuffix(string s) const
00119 {
00120 return s == "png";
00121 }
00122
00130 Image load(Ref<Stream::Reader> reader, Image::ProgressTracker *tracker,
00131 Logger *logger) const
00132 throw(Image::InvalidFormatException,
00133 IOException, UnexpectedException)
00134 {
00135 char header[8];
00136 if(reader->readAll(header, 8) != 8 ||
00137 png_sig_cmp(reinterpret_cast<png_bytep>(header), 0, 8))
00138 ARCHON_THROW1(Image::InvalidFormatException, "Not a PNG header");
00139
00140 LoadContext c(reader, tracker, logger);
00141 LoadWrapper w;
00142
00143 w.png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, &c,
00144 errorCallback, warningCallback);
00145 if(!w.png_ptr)
00146 ARCHON_THROW1(ResourceException,
00147 "PNG library was unable to allocate "
00148 "a 'png_struct' structure");
00149
00150 w.info_ptr = png_create_info_struct(w.png_ptr);
00151 if(!w.info_ptr)
00152 ARCHON_THROW1(ResourceException,
00153 "PNG library was unable to allocate "
00154 "first 'png_info' structure");
00155
00156 w.end_info_ptr = png_create_info_struct(w.png_ptr);
00157 if(!w.end_info_ptr)
00158 ARCHON_THROW1(ResourceException,
00159 "PNG library was unable to allocate "
00160 "second 'png_info' structure");
00161
00162 png_set_read_fn(w.png_ptr, 0, readCallback);
00163 png_set_read_status_fn(w.png_ptr, progressCallback);
00164
00165 string comment;
00166 Image image;
00167 Array<png_byte *> rowPointers;
00168
00169
00170
00171
00172
00173 if(!setjmp(png_jmpbuf(w.png_ptr)))
00174 {
00175 png_set_sig_bytes(w.png_ptr, 8);
00176 png_read_info(w.png_ptr, w.info_ptr);
00177
00178
00179 unsigned bitsPerComponent = png_get_bit_depth(w.png_ptr, w.info_ptr);
00180 const png_byte colorType = png_get_color_type(w.png_ptr, w.info_ptr);
00181 if(bitsPerComponent > 8) png_set_swap(w.png_ptr);
00182 if(bitsPerComponent < 8) png_set_packing(w.png_ptr);
00183 if(colorType == PNG_COLOR_TYPE_PALETTE)
00184 png_set_palette_to_rgb(w.png_ptr);
00185
00186
00187
00188
00189
00190
00191
00192 png_read_update_info(w.png_ptr, w.info_ptr);
00193 unsigned height = png_get_image_height(w.png_ptr, w.info_ptr);
00194 unsigned width = png_get_image_width (w.png_ptr, w.info_ptr);
00195 Image::ComponentSpecifier components =
00196 static_cast<Image::ComponentSpecifier>(png_get_channels(w.png_ptr,
00197 w.info_ptr));
00198 unsigned charsPerPixel = (bitsPerComponent*components+7)/8;
00199 unsigned charsPerRow = charsPerPixel*width;
00200
00201 if(png_get_rowbytes(w.png_ptr, w.info_ptr) != charsPerRow)
00202 ARCHON_THROW1(InternalException,
00203 "Unexpected number of bytes per row "
00204 "reported by the PNG library");
00205
00206 comment = "N/Y/A";
00207
00208
00209 image = Image(width, height, components, bitsPerComponent, comment);
00210
00211
00212 png_byte *pixelBuffer =
00213 reinterpret_cast<png_byte *>(getPixelBufferNoLeak(image));
00214 rowPointers.reset(height);
00215 for(unsigned i=0; i<height; ++i)
00216 rowPointers[i] = pixelBuffer + (height-i-1)*charsPerRow;
00217
00218 c.totalRows = height;
00219
00220 png_read_image(w.png_ptr, rowPointers.get());
00221 png_read_end(w.png_ptr, w.end_info_ptr);
00222 }
00223 else
00224 {
00225 switch(c.errorType)
00226 {
00227 case 1: throw ThreadTerminatedException(c.errorLocation);
00228 case 2: throw Stream::ReadException(c.errorLocation, c.errorMessage);
00229 case 3: throw UnexpectedException(c.errorLocation, c.errorMessage);
00230 }
00231 ARCHON_THROW1(Image::InvalidFormatException, c.errorMessage);
00232 }
00233
00234 return image;
00235 }
00236
00237
00244 void save(Image image, Ref<Stream::Writer> writer,
00245 Image::ProgressTracker *tracker, Logger *logger) const
00246 throw(IOException, UnexpectedException)
00247 {
00248 SaveContext c(writer, tracker, logger);
00249 SaveWrapper w;
00250
00251 w.png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, &c,
00252 errorCallback, warningCallback);
00253 if(!w.png_ptr)
00254 ARCHON_THROW1(ResourceException,
00255 "PNG library was unable to allocate "
00256 "a 'png_struct' structure");
00257
00258 w.info_ptr = png_create_info_struct(w.png_ptr);
00259 if(!w.info_ptr)
00260 ARCHON_THROW1(ResourceException,
00261 "PNG library was unable to allocate "
00262 "first 'png_info' structure");
00263
00264 png_set_write_fn(w.png_ptr, 0, writeCallback, flushCallback);
00265 png_set_write_status_fn(w.png_ptr, progressCallback);
00266
00267 string comment = image.getComment();
00268 Array<png_byte *> rowPointers;
00269
00270
00271
00272
00273
00274 if(!setjmp(png_jmpbuf(w.png_ptr)))
00275 {
00276 int colorType;
00277 switch(image.getComponentSpecifier())
00278 {
00279 case Image::components_l: colorType = PNG_COLOR_TYPE_GRAY; break;
00280 case Image::components_la: colorType = PNG_COLOR_TYPE_GRAY_ALPHA; break;
00281 case Image::components_rgb: colorType = PNG_COLOR_TYPE_RGB; break;
00282 case Image::components_rgba: colorType = PNG_COLOR_TYPE_RGB_ALPHA; break;
00283 default:
00284 ARCHON_THROW1(InternalException,
00285 "Unexpected channel specifier " +
00286 Text::toString(image.getComponentSpecifier()));
00287 };
00288
00289 unsigned height = image.getHeight();
00290 unsigned bitsPerComponent = image.getBitsPerComponent();
00291 png_set_IHDR(w.png_ptr, w.info_ptr, image.getWidth(), height,
00292 bitsPerComponent, colorType, PNG_INTERLACE_NONE,
00293 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
00294
00295 png_write_info(w.png_ptr, w.info_ptr);
00296
00297
00298 if(bitsPerComponent < 8) png_set_packing(w.png_ptr);
00299 if(bitsPerComponent > 8) png_set_swap(w.png_ptr);
00300
00301 png_byte *pixelBuffer =
00302 reinterpret_cast<png_byte *>(getPixelBufferNoLeak(image));
00303 rowPointers.reset(height);
00304 for(unsigned i=0; i<height; i++)
00305 rowPointers[i] = pixelBuffer + (height-i-1)*image.getCharsPerRow();
00306
00307 c.totalRows = height;
00308
00309 png_write_image(w.png_ptr, rowPointers.get());
00310
00311
00312 if(!comment.empty())
00313 {
00314
00315
00316 string::size_type i = 0;
00317 for(;;)
00318 {
00319 string::size_type p = comment.find('\0');
00320 if(p == string::npos) break;
00321 comment.replace(i, 1, "\xEF\xBF\xBD");
00322 }
00323
00324 png_text text;
00325
00326 #ifdef PNG_iTXt_SUPPORTED
00327
00328 text.text_length = 0;
00329 text.itxt_length = comment.size();
00330 text.lang = 0;
00331 text.lang_key = 0;
00332 text.compression = comment.size() < 1000 ?
00333 PNG_ITXT_COMPRESSION_NONE : PNG_ITXT_COMPRESSION_zTXt;
00334
00335 #else
00336
00337 comment = CharEnc::transcode(comment, CharEnc::UTF_8,
00338 CharEnc::ISO_8859_1);
00339 text.text_length = comment.size();
00340 text.compression = comment.size() < 1000 ?
00341 PNG_TEXT_COMPRESSION_NONE : PNG_TEXT_COMPRESSION_zTXt;
00342
00343 #endif
00344
00345 text.key = "Comment";
00346 text.text = const_cast<char *>(comment.data());
00347
00348 png_set_text(w.png_ptr, w.info_ptr, &text, 1);
00349 }
00350
00351 png_write_end(w.png_ptr, 0);
00352 }
00353 else
00354 {
00355 switch(c.errorType)
00356 {
00357 case 1: throw ThreadTerminatedException(c.errorLocation);
00358 case 2: throw Stream::ReadException(c.errorLocation, c.errorMessage);
00359 case 3: throw UnexpectedException(c.errorLocation, c.errorMessage);
00360 }
00361 ARCHON_THROW1(Image::InvalidFormatException, c.errorMessage);
00362 }
00363 }
00364
00365 private:
00366
00367 struct Context
00368 {
00369 double totalRows;
00370 Image::ProgressTracker *tracker;
00371 Logger *logger;
00372 int errorType;
00373
00374
00375
00376 string errorMessage;
00377 string errorLocation;
00378 Context(Image::ProgressTracker *tracker, Logger *logger):
00379 totalRows(0), tracker(tracker), logger(logger), errorType(0) {}
00380 };
00381
00382 struct LoadContext: Context
00383 {
00384 Ref<Stream::Reader> reader;
00385 LoadContext(Ref<Stream::Reader> reader, Image::ProgressTracker *tracker,
00386 Logger *logger): Context(tracker, logger), reader(reader) {}
00387 };
00388
00389 struct SaveContext: Context
00390 {
00391 Ref<Stream::Writer> writer;
00392 SaveContext(Ref<Stream::Writer> writer, Image::ProgressTracker *tracker,
00393 Logger *logger): Context(tracker, logger), writer(writer) {}
00394 };
00395
00396 struct LoadWrapper
00397 {
00398 png_structp png_ptr;
00399 png_infop info_ptr;
00400 png_infop end_info_ptr;
00401 LoadWrapper():png_ptr(0), info_ptr(0), end_info_ptr(0) {}
00402 ~LoadWrapper()
00403 {
00404 if(png_ptr) png_destroy_read_struct(&png_ptr, info_ptr ? &info_ptr : 0,
00405 end_info_ptr ? &end_info_ptr : 0);
00406 }
00407 };
00408
00409 struct SaveWrapper
00410 {
00411 png_structp png_ptr;
00412 png_infop info_ptr;
00413 SaveWrapper():png_ptr(0), info_ptr(0) {}
00414 ~SaveWrapper()
00415 {
00416 if(png_ptr) png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : 0);
00417 }
00418 };
00419
00420 static void errorCallback(png_structp png_ptr, png_const_charp message) throw()
00421 {
00422 {
00423 Context *c = static_cast<Context *>(png_get_error_ptr(png_ptr));
00424 c->errorType = 4;
00425 c->errorMessage = message;
00426 }
00427 longjmp(png_jmpbuf(png_ptr), 1);
00428 }
00429
00430 static void warningCallback(png_structp png_ptr, png_const_charp message) throw()
00431 {
00432 Context *c = static_cast<Context *>(png_get_error_ptr(png_ptr));
00433 if(c->logger) c->logger->log(message);
00434 }
00435
00436 static void progressCallback(png_structp png_ptr, png_uint_32 row, int pass) throw()
00437 {
00438 Context *c = static_cast<Context *>(png_get_error_ptr(png_ptr));
00439
00440 if(c->tracker && c->totalRows) c->tracker->progress(row/c->totalRows);
00441 }
00442
00446 static void readCallback(png_structp png_ptr, png_bytep data,
00447 png_size_t length) throw()
00448 {
00449 LoadContext *c = static_cast<LoadContext *>(png_get_error_ptr(png_ptr));
00450 try
00451 {
00452 const int n = c->reader->readAll(reinterpret_cast<char *>(data),
00453 static_cast<int>(length));
00454 if(n == static_cast<int>(length)) return;
00455
00456 c->errorType = 4;
00457 c->errorMessage = "Premature end of PNG data";
00458 }
00459 catch(ThreadTerminatedException &e)
00460 {
00461 c->errorType = 1;
00462 c->errorLocation = e.getLocation();
00463 }
00464 catch(IOException &e)
00465 {
00466 c->errorType = 2;
00467 c->errorLocation = e.getLocation();
00468 c->errorMessage = e.getMessage();
00469 }
00470 catch(UnexpectedException &e)
00471 {
00472 c->errorType = 3;
00473 c->errorLocation = e.getLocation();
00474 c->errorMessage = e.getMessage();
00475 }
00476
00477 longjmp(png_jmpbuf(png_ptr), 1);
00478 }
00479
00480 static void writeCallback(png_structp png_ptr, png_bytep data,
00481 png_size_t length) throw()
00482 {
00483 SaveContext *c = static_cast<SaveContext *>(png_get_error_ptr(png_ptr));
00484 try
00485 {
00486 c->writer->writeAll(reinterpret_cast<const char *>(data),
00487 static_cast<int>(length));
00488 return;
00489 }
00490 catch(ThreadTerminatedException &e)
00491 {
00492 c->errorType = 1;
00493 c->errorLocation = e.getLocation();
00494 }
00495 catch(IOException &e)
00496 {
00497 c->errorType = 2;
00498 c->errorLocation = e.getLocation();
00499 c->errorMessage = e.getMessage();
00500 }
00501 catch(UnexpectedException &e)
00502 {
00503 c->errorType = 3;
00504 c->errorLocation = e.getLocation();
00505 c->errorMessage = e.getMessage();
00506 }
00507
00508 longjmp(png_jmpbuf(png_ptr), 1);
00509 }
00510
00514 static void flushCallback(png_structp png_ptr)
00515 {
00516 }
00517 };
00518
00519 Image::Format *getDefaultPngFormat()
00520 {
00521 static FormatLibpng f;
00522 return &f;
00523 }
00524 }
00525 }