00001
00022 #include "libnebular-plugin-base.hpp"
00023
00024 #include <sstream>
00025 #include <vector>
00026
00027 #include <boost/static_assert.hpp>
00028 #include <boost/bind.hpp>
00029
00030
00031 #include "jpeglib.h"
00032
00033 namespace libnebular{
00034 using namespace std;
00035
00036 class JpegPictureHandler;
00037
00038 class JpegPictureHandler: public PictureHandler{
00039 private:
00040 static const String dctTypeNameTable[];
00041 static const String jpegColorSpaceTable[];
00042
00044 NumberedStrings dctTypeName;
00046 NumberedStrings jpegColorSpaceString;
00047
00048 J_DCT_METHOD curIdctMethod;
00049
00051 struct MyErrorMgr {
00052 public:
00053 jpeg_error_mgr pub;
00054
00055 private:
00056
00057 int msgLevel;
00058 JMETHOD(void, prevEmitMessage, (j_common_ptr cinfo, int msgLevel));
00059 RawCExceptionLongJmp rawCException;
00060
00061 static MyErrorMgr *get(j_common_ptr cInfo){
00062 return reinterpret_cast<MyErrorMgr*>(cInfo->err);
00063 }
00064
00065 template <typename CurrException> void doThrow(const CurrException &newException){
00066 rawCException.doThrow(newException);
00067 }
00068
00069 static void myErrorExit (j_common_ptr cInfo)
00070 {
00071 char buffer[JMSG_LENGTH_MAX];
00072 cInfo->err->format_message(cInfo, buffer);
00073
00074 ostringstream tmpOut; tmpOut << "From <libjpeg>: \"" << buffer << "\"";
00075
00076 MyErrorMgr::get(cInfo)->doThrow( LIBNEBULAR_ERROR(tmpOut.str()) );
00077 }
00078
00079 static void myEmitMessage (j_common_ptr cInfo, int msgLevel)
00080 {
00081 MyErrorMgr::get(cInfo)->msgLevel = msgLevel;
00082 MyErrorMgr::get(cInfo)->prevEmitMessage(cInfo, msgLevel);
00083 }
00084
00085 static void myOutputMessage (j_common_ptr cInfo)
00086 {
00087 char buffer[JMSG_LENGTH_MAX];
00088 cInfo->err->format_message(cInfo, buffer);
00089
00090 const int &msgLevel = MyErrorMgr::get(cInfo)->msgLevel;
00091
00092 if(
00093 msgLevel == -1
00094 ){
00095 ostringstream tmpOut; tmpOut << "From <libjpeg>: \"" << buffer << "\"";
00096 Log::warning(tmpOut.str());
00097 }else{
00098 ostringstream tmpOut; tmpOut << "From <libjpeg> (trace level " << msgLevel << "): \"" << buffer << "\"";
00099 Log::info(tmpOut.str());
00100 }
00101 }
00102 public:
00103 template <typename Return> Return runRawC(Loki::Functor<Return> procedure){
00104 return rawCException.run<Return>(procedure);
00105 }
00106 MyErrorMgr(){
00107 jpeg_std_error(&pub);
00108
00109 pub.error_exit = myErrorExit;
00110 prevEmitMessage = pub.emit_message;
00111 pub.emit_message = myEmitMessage;
00112 pub.output_message = myOutputMessage;
00113 }
00114
00115 };
00116
00117 struct MyErrorMgr jerr;
00118
00119 struct jpeg_decompress_struct decInfo;
00120 struct jpeg_compress_struct cInfo;
00121 CFileWrapper file;
00122
00123 public:
00124 template <typename Return> Return runRawC(Loki::Functor<Return> procedure){
00125 return jerr.runRawC<Return>(procedure);
00126 }
00127
00128 JpegPictureHandler();
00129 ~JpegPictureHandler(){
00130 jpeg_destroy_compress(&cInfo);
00131 jpeg_destroy_decompress(&decInfo);
00132 }
00133
00139 void readFileInfo(){
00140 file = CFileWrapper(pic->get<String>("opt/file/url").c_str(), "rb");
00141
00142 runRawC<void>(boost::bind(
00143 jpeg_stdio_src, &decInfo, file.get()
00144 ));
00145 runRawC<int>(boost::bind(
00146 jpeg_read_header, &decInfo, TRUE
00147 ));
00148
00149 pic->set<Integer>("bm/x-size", decInfo.image_width);
00150 pic->set<Integer>("bm/y-size", decInfo.image_height);
00151 pic->set<Integer>("bm/jpeg-color-components", decInfo.num_components);
00152 pic->set<String>("bm/jpeg-color-space", jpegColorSpaceString.indexToString(decInfo.jpeg_color_space));
00153
00154 pic->set<bool>("file/jpeg-jfif-header", decInfo.saw_JFIF_marker);
00155 if(decInfo.saw_JFIF_marker){
00156
00157
00158 pic->set<Integer>("file/jpeg-jfif-major-version", decInfo.JFIF_major_version);
00159 pic->set<Integer>("file/jpeg-jfif-minor-version", decInfo.JFIF_minor_version);
00160 if(decInfo.density_unit == 1){
00161
00162 pic->set<Integer>("bm/x-dpi", decInfo.X_density);
00163 pic->set<Integer>("bm/y-dpi", decInfo.Y_density);
00164 }else if(decInfo.density_unit == 2){
00165
00166 const Integer dpmPerDpcm = 100;
00167 pic->set<Integer>("bm/x-dots-per-m", decInfo.X_density*dpmPerDpcm);
00168 pic->set<Integer>("bm/y-dots-per-m", decInfo.Y_density*dpmPerDpcm);
00169 }else if(decInfo.density_unit != 0){
00170 throw LIBNEBULAR_ERROR(
00171 Log::getWrongValueMsg(
00172 "\"density_unit\"",
00173 Integer(decInfo.density_unit),
00174 "in {0, 1, 2}",
00175 "JPEG header",
00176 "Invalid"
00177 )
00178 );
00179 }
00180 }
00181
00182 pic->set<bool>("file/jpeg-saw-adobe-marker", decInfo.saw_Adobe_marker);
00183 if(decInfo.saw_Adobe_marker){
00184
00185 pic->set<Integer>("file/jpeg-adobe-transform", decInfo.Adobe_transform);
00186 }
00187
00188 {
00189 pic->set<Integer>("bm/bpp", 24);
00190
00191 pic->set<String>("bm/pixel-mask/r", "0xff");
00192 pic->set<String>("bm/pixel-mask/g", "0xff00");
00193 pic->set<String>("bm/pixel-mask/b", "0xff0000");
00194 pic->set<String>("bm/pixel-mask/a", "0");
00195
00196 pic->set<String>("bm/scanline-skip", "0");
00197 pic->set<String>("bm/scanline-axis", "x");
00198
00199 pic->set<String>("bm/orient-left", "true");
00200 pic->set<String>("bm/orient-top", "true");
00201
00202 decInfo.out_color_space = JCS_RGB;
00203 }
00204 }
00205
00206 void readBm(){
00207
00208 {
00209 pic->setDefaultIfNotSet("opt/file/jpeg-dct-method", "islow");
00210
00211 decInfo.dct_method = static_cast<J_DCT_METHOD>(
00212 dctTypeName.stringToIndex(pic->get<String>("opt/file/jpeg-dct-method"))
00213 );
00214
00215 pic->acceptOpt("file/jpeg-dct-method");
00216 }
00217
00218 Blob bmData;
00219 if(pic->isSet("bm/data")){
00220 bmData = pic->get<Blob>("bm/data");
00221 pic->unSet("bm/data");
00222 bmData.forsakeData();
00223 }
00224 pic->calculate<PictureProps::PropCalcBmDataSize>();
00225
00226 Integer bmScanlinePitch = checkedBitsToIntBytes(
00227 pic->get<Integer>("calc/bm/scanline-pitch-bits")
00228 );
00229 BOOST_STATIC_ASSERT(sizeof(JSAMPLE) == 1);
00230
00231 bmData.prepareForSize(
00232 pic->get<Integer>("calc/bm/data-size")
00233 );
00234
00235 const unsigned int scanlinesPerPass = 1;
00236
00237
00238 runRawC<boolean>(boost::bind(
00239 jpeg_start_decompress, &decInfo
00240 ));
00241
00242
00243 std::vector<JSAMPLE*> buffer(scanlinesPerPass);
00244 Loki::Functor<JDIMENSION> readScanlines =
00245 boost::bind(
00246 jpeg_read_scanlines, &decInfo, &(buffer[0]), scanlinesPerPass
00247 );
00248 while (decInfo.output_scanline < decInfo.output_height) {
00249 JSAMPLE *curDestScanLine = reinterpret_cast<JSAMPLE*>(bmData.getDataReadWrite())+
00250 bmScanlinePitch*1*decInfo.output_scanline;
00251 for(Integer i = 0; i < scanlinesPerPass; i++){
00252 buffer[i] = reinterpret_cast<JSAMPLE*>(
00253 curDestScanLine
00254 );
00255 curDestScanLine += bmScanlinePitch;
00256 }
00257 runRawC<JDIMENSION>(readScanlines);
00258 }
00259
00260
00261 runRawC<boolean>(boost::bind(
00262 jpeg_finish_decompress, &decInfo
00263 ));
00264
00265 pic->set("bm/data", bmData);
00266 }
00267
00268 void write(){
00269
00270 boost::shared_ptr<Picture> picBm(new Picture);
00271
00272 {
00273 picBm->set<Integer>("opt/bm/bpp", 24);
00274
00275 picBm->set<String>("opt/bm/pixel-format-type", "rgb");
00276 picBm->set<String>("opt/bm/pixel-mask/r", "0xff");
00277 picBm->set<String>("opt/bm/pixel-mask/g", "0xff00");
00278 picBm->set<String>("opt/bm/pixel-mask/b", "0xff0000");
00279 picBm->set<String>("opt/bm/pixel-mask/a", "0");
00280
00281 picBm->set<String>("opt/bm/scanline-skip", "0");
00282 picBm->set<String>("opt/bm/scanline-axis", "x");
00283
00284 picBm->set<String>("opt/bm/orient-left", "true");
00285 picBm->set<String>("opt/bm/orient-top", "true");
00286
00287 picBm->reproduce(*pic.get());
00288 }
00289
00290 file = CFileWrapper(pic->get<String>("opt/file/url").c_str(), "wb");
00291 runRawC<void>(boost::bind(
00292 jpeg_stdio_dest, &cInfo, file.get()
00293 ));
00294
00295 cInfo.image_width = pic->get<Integer>("bm/x-size");
00296 cInfo.image_height = pic->get<Integer>("bm/y-size");
00297
00298
00299 cInfo.in_color_space = JCS_RGB;
00300 pic->set<String>("bm/jpeg-color-space", jpegColorSpaceString.indexToString(cInfo.jpeg_color_space));
00301
00302 cInfo.input_components = 3;
00303 pic->set<Integer>("bm/jpeg-color-components", cInfo.num_components);
00304
00305 runRawC<void>(boost::bind(
00306 jpeg_set_defaults, &cInfo
00307 ));
00308
00309 pic->setDefaultIfNotSet("opt/file/jpeg-force-baseline", "true");
00310 if(!pic->isSet("opt/file/jpeg-quality")){
00311 pic->setDefaultIfNotSet("opt/file/jpeg-quality", "75");
00312 }else{
00313 if(pic->isSet("opt/file/jpeg-linear-quality")){
00314 if(
00315 runRawC<int>(boost::bind(
00316 jpeg_quality_scaling, pic->get<Integer>("opt/file/jpeg-quality")
00317 )) !=
00318 pic->get<Integer>("opt/file/jpeg-linear-quality")
00319 ){
00320 throw LIBNEBULAR_ERROR(Log::getWrongValueMsg(
00321 "\"opt/file/jpeg-linear-quality\"", pic->get<Integer>("opt/file/jpeg-linear-quality"), "not conflicting with \"opt/file/jpeg-quality\"",
00322 String(__func__)+" option key", "Conflicting"
00323 ));
00324 }
00325 }
00326 }
00327 if(pic->isSet("opt/file/jpeg-quality")){
00328 runRawC<void>(boost::bind(
00329 jpeg_set_quality, &cInfo,
00330 pic->get<Integer>("opt/file/jpeg-quality"),
00331 pic->get<bool>("opt/file/jpeg-force-baseline")
00332 ));
00333
00334 }else{
00335 runRawC<void>(boost::bind(
00336 jpeg_set_linear_quality, &cInfo,
00337 pic->get<Integer>("opt/file/jpeg-linear-quality"),
00338 pic->get<bool>("opt/file/jpeg-force-baseline")
00339 ));
00340 }
00341
00342 pic->setDefaultIfNotSet("opt/file/jpeg-simple-progression", "false");
00343 if(pic->get<bool>("opt/file/jpeg-simple-progression")){
00344 runRawC<void>(boost::bind(
00345 jpeg_simple_progression, &cInfo
00346 ));
00347 }
00348 pic->acceptOpt("file/jpeg-simple-progression");
00349
00350 pic->setDefaultIfNotSet("opt/file/jpeg-optimize-coding", "true");
00351 cInfo.optimize_coding = pic->get<bool>("opt/file/jpeg-optimize-coding");
00352 pic->acceptOpt("file/jpeg-optimize-coding");
00353
00354 pic->setDefaultIfNotSet("opt/file/jpeg-jfif-header", "true");
00355 cInfo.write_JFIF_header = pic->get<bool>("opt/file/jpeg-jfif-header");
00356 pic->acceptOpt("file/jpeg-jfif-header");
00357 if(cInfo.write_JFIF_header){
00358
00359
00360 if(pic->isSet("bm/x-dpi") && pic->isSet("bm/y-dpi")){
00361 cInfo.density_unit = 1;
00362 cInfo.X_density = pic->get<Integer>("bm/x-dpi");
00363 cInfo.Y_density = pic->get<Integer>("bm/y-dpi");
00364 }if(pic->isSet("bm/x-dots-per-m") && pic->isSet("bm/y-dots-per-m")){
00365 cInfo.density_unit = 2;
00366 const Integer cmPerM = 100;
00367 if(
00368 pic->get<Integer>("bm/x-dots-per-m") % cmPerM != 0
00369 ){
00370 Log::warning(String() + "Property \"" + "bm/x-dots-per-m" + "\" value in file loses precision of that in \"Picture\"");
00371 }
00372 if(
00373 pic->get<Integer>("bm/y-dots-per-m") % cmPerM != 0
00374 ){
00375 Log::warning(String() + "Property \"" + "bm/y-dots-per-m" + "\" value in file loses precision of that in \"Picture\"");
00376 }
00377 cInfo.X_density = roundDiv(
00378 pic->get<Integer>("bm/x-dots-per-m"),
00379 cmPerM
00380 );
00381 cInfo.Y_density = roundDiv(
00382 pic->get<Integer>("bm/y-dots-per-m"),
00383 cmPerM
00384 );
00385 }else{
00386 cInfo.density_unit = 0;
00387 }
00388 }
00389
00390 pic->setDefaultIfNotSet("opt/file/jpeg-dct-method", "islow");
00391 cInfo.dct_method = static_cast<J_DCT_METHOD>(
00392 dctTypeName.stringToIndex(pic->get<String>("opt/file/jpeg-dct-method"))
00393 );
00394 pic->acceptOpt("file/jpeg-dct-method");
00395
00396 Blob bmData = picBm->get<Blob>("bm/data");
00397
00398
00399 pic->calculate<PictureProps::PropCalcBmDataSize>();
00400 Integer bmScanlinePitch = checkedBitsToIntBytes(
00401 pic->get<Integer>("calc/bm/scanline-pitch-bits")
00402 );
00403 BOOST_STATIC_ASSERT(sizeof(JSAMPLE) == 1);
00404
00405 const Integer scanlinesPerPass = 1;
00406
00407 runRawC<void>(boost::bind(
00408 jpeg_start_compress, &cInfo, TRUE
00409 ));
00410
00411 pic->acceptOpt("file/jpeg-quality");
00412 pic->acceptOpt("file/jpeg-linear-quality");
00413 pic->acceptOpt("file/jpeg-force-baseline");
00414
00415 std::vector<const JSAMPLE*> buffer(scanlinesPerPass);
00416 Loki::Functor<JDIMENSION> writeScanlines = boost::bind(
00417 jpeg_write_scanlines,
00418 &cInfo,
00419 const_cast<JSAMPLE**>( &(buffer[0]) ),
00420 scanlinesPerPass
00421 );
00422 while (cInfo.next_scanline < cInfo.image_height) {
00423 const JSAMPLE *curDestScanLine = reinterpret_cast<const JSAMPLE*>(bmData.getDataReadOnly())+
00424 bmScanlinePitch*1*cInfo.next_scanline;
00425 for(Integer i = 0; i < scanlinesPerPass; i++){
00426 buffer[i] = reinterpret_cast<const JSAMPLE*>(
00427 curDestScanLine
00428 );
00429 curDestScanLine += bmScanlinePitch;
00430
00431 }
00432 runRawC<JDIMENSION>(writeScanlines);
00433 }
00434
00435 runRawC<void>(boost::bind(
00436 jpeg_finish_compress, &cInfo
00437 ));
00438 }
00439
00440 void virtual setPicture(Picture &picture){
00441 PictureHandler::setPicture(picture);
00442 picture.set<String>("handler", "jpeg");
00443 }
00444 };
00445
00446 const String JpegPictureHandler::dctTypeNameTable[] = {
00447 "islow",
00448 "ifast",
00449 "float"
00450 };
00451
00452 const String JpegPictureHandler::jpegColorSpaceTable[] = {
00453 "unknown",
00454 "grayscale",
00455 "rgb",
00456 "ycbcr",
00457 "cmyk",
00458 "ycck"
00459 };
00460
00461 JpegPictureHandler::JpegPictureHandler():
00462 dctTypeName(ARRAY_SIZE(dctTypeNameTable), dctTypeNameTable),
00463 jpegColorSpaceString(ARRAY_SIZE(jpegColorSpaceTable), jpegColorSpaceTable)
00464 {
00465 decInfo.err = &jerr.pub;
00466 cInfo.err = &jerr.pub;
00467
00468
00469 runRawC<void>(boost::bind(
00470 jpeg_CreateDecompress, &decInfo, JPEG_LIB_VERSION,
00471 sizeof(jpeg_decompress_struct)
00472 ));
00473 runRawC<void>(boost::bind(
00474 jpeg_CreateCompress, &cInfo, JPEG_LIB_VERSION,
00475 sizeof(jpeg_compress_struct)
00476 ));
00477 }
00478
00479 typedef MgrForPicHandler<JpegPictureHandler> CurrPicHandlerMgr;
00480
00481 template <> bool CurrPicHandlerMgr::isHandleableByFileExt(Picture &pictureFile) const{
00482 determineFileExt(pictureFile);
00483 const String fileExt = pictureFile.get<String>("opt/file/url-extension");
00484 if(
00485 fileExt == "jpg" || fileExt == "jpeg" || fileExt == "jpe"
00486 ){
00487 return true;
00488 }else{
00489 return false;
00490 }
00491 }
00492
00493 template <> bool CurrPicHandlerMgr::isHandleableByContentMagic(Picture &pictureFile) const{
00494 const Integer magicDataSize = 2;
00495 const char magicDataCharArray[] ="\xff\xd8";
00496 const Blob magicData(magicDataSize, magicDataCharArray);
00497 const Integer magicFileOffset = 0;
00498
00499 if(haveContentMagic(pictureFile, magicData, magicFileOffset)){
00500 pictureFile.set<String>("file/mimetype", "image/jpeg");
00501 return true;
00502 }else{
00503 return false;
00504 }
00505 }
00506
00507 template <> bool CurrPicHandlerMgr::isWriteable(Picture &picture) const{
00508 return (
00509 picture.get<String>("opt/file/mimetype") == "image/jpeg"
00510 );
00511 }
00512 }
00513
00514 LIBNEBULAR_SET_PLUGIN_PIC_HANDLER_MGR(libnebular::CurrPicHandlerMgr)