00001
00030 #include "libnebular-plugin-base.hpp"
00031
00032 #include <fstream>
00033 #include <set>
00034
00035
00036 #include <cstring>
00037
00038 #if __GNUC__
00039
00040 #include <stdint.h>
00041
00043
00044 typedef uint16_t WORD;
00045 typedef uint32_t DWORD;
00046 typedef int16_t SHORT;
00047 typedef int32_t LONG;
00049
00050 #else
00051 #error Please map exact-with integer types to corresponding aliases
00052 #endif // __GNUC__
00053
00054 #ifdef __GNUC__
00055
00056
00057 #define PACKED_VAR __attribute__ ((packed))
00058 #else
00059 #error Please provide a way to say compiler that structure fields should be packed
00060 #endif
00061
00062 namespace libnebular{
00063 using namespace std;
00064
00066 class BmpPictureHandler: public PictureHandler{
00067 private:
00069 struct BmpFileHeader
00070 {
00072 WORD FileType PACKED_VAR;
00074 DWORD FileSize PACKED_VAR;
00076 WORD Reserved1 PACKED_VAR;
00078 WORD Reserved2 PACKED_VAR;
00080 DWORD BitmapOffset PACKED_VAR;
00081 } bmpFileHeader;
00083 struct BmpV3BmpHeaderAddon
00084 {
00085
00087 DWORD Compression PACKED_VAR;
00089 DWORD SizeOfBitmap PACKED_VAR;
00091 LONG HorzResolution PACKED_VAR;
00093 LONG VertResolution PACKED_VAR;
00095 DWORD ColorsUsed PACKED_VAR;
00097 DWORD ColorsImportant PACKED_VAR;
00098 } bmpV3BmpHeaderAddon;
00100 struct BmpV3WinNtBmpHeaderAddon
00101 {
00103 DWORD RedMask PACKED_VAR;
00105 DWORD GreenMask PACKED_VAR;
00107 DWORD BlueMask PACKED_VAR;
00108 } bmpV3WinNtBmpHeaderAddon;
00110 struct BmpV4BmpHeaderAddon
00111 {
00112
00113
00115
00117
00119
00121 DWORD AlphaMask PACKED_VAR;
00123 DWORD CSType PACKED_VAR;
00125 LONG RedX PACKED_VAR;
00127 LONG RedY PACKED_VAR;
00129 LONG RedZ PACKED_VAR;
00131 LONG GreenX PACKED_VAR;
00133 LONG GreenY PACKED_VAR;
00135 LONG GreenZ PACKED_VAR;
00137 LONG BlueX PACKED_VAR;
00139 LONG BlueY PACKED_VAR;
00141 LONG BlueZ PACKED_VAR;
00143 DWORD GammaRed PACKED_VAR;
00145 DWORD GammaGreen PACKED_VAR;
00147 DWORD GammaBlue PACKED_VAR;
00148 } bmpV4BmpHeaderAddon;
00150 struct BmpV4BmpHeader
00151 {
00153 DWORD Size;
00155 LONG Width;
00157 LONG Height;
00159 WORD Planes;
00161 WORD BitsPerPixel;
00163 DWORD Compression;
00165 DWORD SizeOfBitmap;
00167 LONG HorzResolution;
00169 LONG VertResolution;
00171 DWORD ColorsUsed;
00173 DWORD ColorsImportant;
00174
00175
00177 DWORD RedMask;
00179 DWORD GreenMask;
00181 DWORD BlueMask;
00183 DWORD AlphaMask;
00185 DWORD CSType;
00187 LONG RedX;
00189 LONG RedY;
00191 LONG RedZ;
00193 LONG GreenX;
00195 LONG GreenY;
00197 LONG GreenZ;
00199 LONG BlueX;
00201 LONG BlueY;
00203 LONG BlueZ;
00205 DWORD GammaRed;
00207 DWORD GammaGreen;
00209 DWORD GammaBlue;
00210 } bmpV4BmpHeader;
00211
00212 boost::shared_ptr<istream> fIn;
00213
00214 public:
00215 BmpPictureHandler(){
00216 }
00217
00218 ~BmpPictureHandler(){
00219 }
00220
00221 void readFileInfo(){
00222 fIn = boost::shared_ptr<istream>(
00223 new ifstream(
00224 pic->get<String>("opt/file/url").c_str(),
00225 ios::in | ios::binary
00226 )
00227 );
00228 if(fIn->fail()){
00229 throw LIBNEBULAR_ERROR("Failed to open file \"" + pic->get<String>("opt/file/url") + "\"");
00230 }
00231 fIn->exceptions(ios::failbit | ios::badbit | ios::eofbit);
00232 {
00233 readBin(*fIn, bmpFileHeader);
00234
00235 const char magicDataCharArray[] = {'B', 'M'};
00236 if(!(
00237 bmpFileHeader.FileType == *(
00238 reinterpret_cast<const WORD*>(magicDataCharArray)
00239 )
00240 )){
00241 throw LIBNEBULAR_ERROR("Content magic not found");
00242 }
00243 }
00244
00245
00246 {
00247 DWORD Size;
00248 readBin(*fIn, Size);
00249
00250 switch(Size){
00251 case 12:
00252 pic->set<Integer>("file/bmp-major-version", 2);
00253 break;
00254
00255 case 40:
00256 pic->set<Integer>("file/bmp-major-version", 3);
00257 break;
00258
00259 case 108:
00260 pic->set<Integer>("file/bmp-major-version", 3);
00261 break;
00262
00263 default:
00264 Log::warning(
00265 Log::getWrongValueMsg("\"Size\"", Size, "in {12, 40, 108}", "bitmap header", "Unsupported")
00266 );
00267 pic->set<bool>("file/bmp-version-unknown", true);
00268
00269 }
00270
00271 LONG Width;
00272 LONG Height;
00273 if(pic->get<Integer>("file/bmp-major-version") >= 3){
00274 readBin(*fIn, Width);
00275 readBin(*fIn, Height);
00276 }else{
00277 SHORT WidthField;
00278 SHORT HeightField;
00279 readBin(*fIn, WidthField);
00280 readBin(*fIn, HeightField);
00281 Width = WidthField;
00282 Height = HeightField;
00283 }
00284
00285 if(Width < 0){
00286 throw LIBNEBULAR_ERROR(
00287 Log::getWrongValueMsg("\"Width\"", Width, "\"> 0\"", "bitmap header", "Invalid")
00288 );
00289 }
00290 pic->set<Integer>("bm/x-size", Width);
00291 if(Height < 0){
00292 pic->set<bool>("bm/orient-top", true);
00293 pic->set<Integer>("bm/y-size", -Height);
00294 }else{
00295 pic->set<bool>("bm/orient-top", false);
00296 pic->set<Integer>("bm/y-size", Height);
00297 }
00298
00299 WORD Planes;
00300 readBin(*fIn, Planes);
00301 if(Planes != 1){
00302 const string wrongnessMsg =
00303 Log::getWrongValueMsg("\"Planes\"", Planes, "", "bitmap header", "Unexpected");
00304 if(Planes == 0){
00305 Log::warning(wrongnessMsg);
00306 }else{
00307 throw LIBNEBULAR_ERROR(wrongnessMsg);
00308 }
00309 }
00310
00311 WORD BitsPerPixel;
00312 readBin(*fIn, BitsPerPixel);
00313 const WORD bitsPerPixelAllowedCArray[] =
00314 {1, 4, 8, 16, 24, 32};
00315 std::set<WORD> bitsPerPixelAllowed(
00316 bitsPerPixelAllowedCArray,
00317 bitsPerPixelAllowedCArray+ARRAY_SIZE(bitsPerPixelAllowedCArray)
00318 );
00319 if(
00320 bitsPerPixelAllowed.find(BitsPerPixel) ==
00321 bitsPerPixelAllowed.end()
00322 ){
00323 throw LIBNEBULAR_ERROR(
00324 Log::getWrongValueMsg("\"BitsPerPixel\"", Integer(BitsPerPixel), "in {1, 4, 8, 16, 24, 32}", "bitmap header", "Unexpected")
00325 );
00326 }
00327 pic->set<Integer>("bm/bpp", BitsPerPixel);
00328 }
00329
00330 if(pic->get<Integer>("file/bmp-major-version") >= 3){
00331 fIn->read(
00332 reinterpret_cast<char*>(&bmpV3BmpHeaderAddon),
00333 sizeof(bmpV3BmpHeaderAddon)
00334 );
00335
00336 if(!(
00337 (
00338 (
00339 pic->get<Integer>("bm/bpp") <= 8 ||
00340 pic->get<Integer>("bm/bpp") == 24 ||
00341 pic->get<Integer>("bm/bpp") == 32
00342 )
00343 &&
00344
00345 bmpV3BmpHeaderAddon.Compression == 0
00346 ) || (
00347 pic->get<Integer>("bm/bpp") > 8 &&
00348
00349 bmpV3BmpHeaderAddon.Compression == 3
00350 )
00351 )){
00352 throw LIBNEBULAR_ERROR(
00353 Log::getWrongValueMsg("\"Compression\"", bmpV3BmpHeaderAddon.Compression, "", "bitmap header", "Unexpected")
00354 );
00355
00356 }
00357
00358 if(bmpV3BmpHeaderAddon.HorzResolution != 0){
00359 pic->set<Integer>("bm/x-dots-per-m", bmpV3BmpHeaderAddon.HorzResolution);
00360 }
00361 if(bmpV3BmpHeaderAddon.VertResolution != 0){
00362 pic->set<Integer>("bm/y-dots-per-m", bmpV3BmpHeaderAddon.VertResolution);
00363 }
00364 pic->check<PictureProps::PropBmPixelsPerLength>();
00365
00366
00367 pic->set<Integer>(
00368 "bm/pixel-mask/a",
00369 0
00370 );
00371
00372 if(pic->get<Integer>("file/bmp-major-version") >= 4){
00373 fIn->read(
00374 reinterpret_cast<char*>(&bmpV4BmpHeaderAddon),
00375 sizeof(bmpV4BmpHeaderAddon)
00376 );
00377 pic->set< HexInteger<Integer> >(
00378 "bm/pixel-mask/a",
00379 bmpV4BmpHeaderAddon.AlphaMask
00380 );
00381
00382 if(
00383 bmpV4BmpHeaderAddon.CSType != 1
00384 ){
00385 throw LIBNEBULAR_ERROR(
00386 Log::getWrongValueMsg("\"CSType\"", bmpV4BmpHeaderAddon.CSType, "\"1\"", "bitmap header", "Unsupported")
00387 );
00388 }
00389
00390
00391 }
00392
00393 }
00394
00395
00396 if(pic->get<Integer>("bm/bpp") > 8){
00397 pic->set(
00398 "bm/pixel-format-type",
00399 "rgb"
00400 );
00401 fIn->read(
00402 reinterpret_cast<char*>(&bmpV3WinNtBmpHeaderAddon),
00403 sizeof(bmpV3WinNtBmpHeaderAddon)
00404 );
00405
00406 pic->set<Integer>(
00407 "bm/pixel-mask/a",
00408 0
00409 );
00410 if(bmpV3BmpHeaderAddon.Compression == 3){
00411 pic->set< HexInteger<Integer> >(
00412 "bm/pixel-mask/r",
00413 bmpV3WinNtBmpHeaderAddon.RedMask
00414 );
00415 pic->set< HexInteger<Integer> >(
00416 "bm/pixel-mask/g",
00417 bmpV3WinNtBmpHeaderAddon.GreenMask
00418 );
00419 pic->set< HexInteger<Integer> >(
00420 "bm/pixel-mask/b",
00421 bmpV3WinNtBmpHeaderAddon.BlueMask
00422 );
00423 }else{
00424
00425 pic->set(
00426 "bm/pixel-mask/r",
00427 "0xff0000"
00428 );
00429 pic->set(
00430 "bm/pixel-mask/g",
00431 "0xff00"
00432 );
00433 pic->set(
00434 "bm/pixel-mask/b",
00435 "0xff"
00436 );
00437 if(
00438 pic->get<Integer>("bm/bpp") == 24
00439 ){
00440 pic->set(
00441 "bm/pixel-mask/a",
00442 "0"
00443 );
00444 }else{
00445
00446 pic->set(
00447 "bm/pixel-mask/a",
00448 "0xff000000"
00449 );
00450 }
00451 }
00452
00453 if(bmpV3BmpHeaderAddon.ColorsImportant > 0){
00454 Log::warning(
00455 Log::getWrongValueMsg("\"ColorsImportant\"", bmpV3BmpHeaderAddon.ColorsImportant, "\"0\" when format is not paletted", "bitmap header", "Unexpected")
00456 );
00457 }
00458 }else{
00459
00460
00461 pic->set(
00462 "bm/pixel-format-type",
00463 "pal"
00464 );
00465 if(bmpV3BmpHeaderAddon.ColorsUsed > 0){
00466 pic->set<Integer>("bm/palette-colors", bmpV3BmpHeaderAddon.ColorsUsed);
00467 }else{
00468 pic->set<Integer>(
00469 "bm/palette-colors",
00470 1 << pic->get<Integer>("bm/bpp")
00471 );
00472 }
00473 if(bmpV3BmpHeaderAddon.ColorsImportant > 0){
00474 pic->set<Integer>("bm/bmp-colors-important", bmpV3BmpHeaderAddon.ColorsImportant);
00475 }else{
00476 pic->set<Integer>(
00477 "bm/bmp-colors-important",
00478 pic->get<Integer>("bm/palette-colors")
00479 );
00480 }
00481
00482 pic->set<Integer>(
00483 "bm/pixel-mask/pal",
00484 bits::generateContig<Integer>(
00485 pic->get<Integer>("bm/bpp")
00486 )
00487 );
00488
00489 {
00490 boost::shared_ptr<Picture> palette =
00491 createSubtreeProxy<Picture>(
00492 *pic,
00493 "bm/palette/"
00494 );
00495
00496 palette->setDefaults();
00497
00498 {
00499 palette->set<Integer>(
00500 "bm/x-size",
00501 pic->get<Integer>("bm/palette-colors")
00502 );
00503 palette->set<Integer>(
00504 "bm/y-size",
00505 1
00506 );
00507
00508 if(pic->get<Integer>("file/bmp-major-version") >= 3){
00509 palette->set<Integer>(
00510 "bm/bpp",
00511 4*8
00512 );
00513 }else{
00514 palette->set<Integer>(
00515 "bm/bpp",
00516 3*8
00517 );
00518 }
00519
00520 palette->set("bm/pixel-format-type", "rgb");
00521 palette->set("bm/pixel-mask/r", "0xff");
00522 palette->set("bm/pixel-mask/g", "0xff00");
00523 palette->set("bm/pixel-mask/b", "0xff0000");
00524 palette->set("bm/pixel-mask/a", "0");
00525 }
00526 palette->calculate<PictureProps::PropCalcBmDataSize>();
00527 {
00528 Blob bmData;
00529 if(palette->isSet("bm/data")){
00530 bmData = palette->get<Blob>("bm/data");
00531 palette->unSet("bm/data");
00532 bmData.forsakeData();
00533 }
00534 bmData.prepareForSize(palette->get<Integer>("calc/bm/data-size"));
00535 fIn->read(
00536 bmData.getDataReadWrite(),
00537 palette->get<Integer>("calc/bm/data-size")
00538 );
00539 palette->set("bm/data", bmData);
00540 }
00541
00542 }
00543 }
00544
00545 Integer bmScanlineDataPitchBits =
00546 pic->get<Integer>("bm/x-size")*pic->get<Integer>("bm/bpp");
00547
00548 const Integer bmScanlineAlignBits = 4*bitsPerByte;
00549 pic->set<Integer>(
00550 "bm/scanline-skip",
00551 ( bmScanlineDataPitchBits % bmScanlineAlignBits == 0 ?
00552 0 :
00553 bmScanlineAlignBits - bmScanlineDataPitchBits % bmScanlineAlignBits )
00554 );
00555
00556 }
00557
00558 void readBm(){
00559 pic->calculate<PictureProps::PropCalcBmDataSize>();
00560
00561 Blob bmData;
00562 if(pic->isSet("bm/data")){
00563 bmData = pic->get<Blob>("bm/data");
00564 pic->unSet("bm/data");
00565 bmData.forsakeData();
00566 }
00567 bmData.prepareForSize(pic->get<Integer>("calc/bm/data-size"));
00568 fIn->seekg(
00569 bmpFileHeader.BitmapOffset,
00570 ios_base::beg
00571 );
00572 fIn->read(
00573 bmData.getDataReadWrite(),
00574 pic->get<Integer>("calc/bm/data-size")
00575 );
00576 pic->set("bm/data", bmData);
00577 }
00578
00579 void write(){
00580
00581 boost::shared_ptr<Picture> picBm(new Picture);
00582
00583 boost::shared_ptr<ostream> fOut(
00584 new ofstream(
00585 pic->get<String>("opt/file/url").c_str()
00586 )
00587 );
00588
00589 if(fOut->fail()){
00590 throw LIBNEBULAR_ERROR("Failed to open file \"" + pic->get<String>("opt/file/url") + "\"");
00591 }
00592
00593 fOut->exceptions(ios::failbit | ios::badbit | ios::eofbit);
00594
00595 if(pic->isSet("opt/bm/bpp")){
00596 if(!(
00597 pic->get<Integer>("opt/bm/bpp") == 24 ||
00598 pic->get<Integer>("opt/bm/bpp") == 32
00599 )){
00600 throw LIBNEBULAR_ERROR(
00601 Log::getWrongValueMsg(
00602 "\"opt/bm/bpp\"",
00603 pic->get<Integer>("opt/bm/bpp"),
00604 "in {24, 32}",
00605 string(__func__) + " option key",
00606 "Unsupported"
00607 )
00608 );
00609 }
00610 }else{
00611 setDefault(*pic, "opt/bm/bpp", "32");
00612 }
00613
00614 const Integer bmScanlineAlignBits = 4*bitsPerByte;
00615 {
00616 picBm->set(
00617 "opt/bm/bpp",
00618 pic->get<Integer>("opt/bm/bpp")
00619 );
00620 pic->unSet("opt/bm/bpp");
00621
00622 picBm->set<String>("opt/bm/pixel-format-type", "rgb");
00623 picBm->set<String>("opt/bm/pixel-mask/r", "0xff0000");
00624 picBm->set<String>("opt/bm/pixel-mask/g", "0xff00");
00625 picBm->set<String>("opt/bm/pixel-mask/b", "0xff");
00626 if(picBm->get<Integer>("opt/bm/bpp") == 32){
00627 picBm->set<String>("opt/bm/pixel-mask/a", "0xff000000");
00628 }else{
00629 picBm->set<String>("opt/bm/pixel-mask/a", "0");
00630 }
00631
00632 Integer bmScanlineDataPitchBits =
00633 pic->get<Integer>("bm/x-size")*picBm->get<Integer>("opt/bm/bpp");
00634
00635 picBm->set<Integer>(
00636 "opt/bm/scanline-skip",
00637 ( bmScanlineDataPitchBits % bmScanlineAlignBits == 0 ?
00638 0 :
00639 bmScanlineAlignBits - bmScanlineDataPitchBits % bmScanlineAlignBits )
00640 );
00641
00642 picBm->set<String>("opt/bm/scanline-axis", "x");
00643
00644 picBm->set<String>("opt/bm/orient-left", "true");
00645
00646 if(pic->isSet("opt/bm/orient-top")){
00647 picBm->set(
00648 "opt/bm/orient-top",
00649 pic->get<bool>("opt/bm/orient-top")
00650 );
00651 pic->unSet("opt/bm/orient-top");
00652 }else{
00653 picBm->set(
00654 "opt/bm/orient-top",
00655 false
00656 );
00657 }
00658
00659 picBm->reproduce(*pic);
00660 }
00661
00662 {
00663 memset(
00664 &bmpFileHeader,
00665 0,
00666 sizeof(bmpFileHeader)
00667 );
00668
00669 Integer fileBmOffset =
00670 sizeof(bmpFileHeader) +
00671 sizeof(bmpV4BmpHeader);
00672
00673 fileBmOffset +=
00674 ( fileBmOffset % checkedBitsToIntBytes(bmScanlineAlignBits) == 0 ?
00675 0 :
00676 checkedBitsToIntBytes(bmScanlineAlignBits) - fileBmOffset % checkedBitsToIntBytes(bmScanlineAlignBits) )
00677 ;
00678
00679 const char magicDataCharArray[] = {'B', 'M'};
00680 bmpFileHeader.FileType = *reinterpret_cast<const WORD*>(magicDataCharArray);
00681
00682 picBm->calculate<PictureProps::PropCalcBmDataSize>();
00683 bmpFileHeader.FileSize = fileBmOffset + picBm->get<Integer>("calc/bm/data-size");
00684 bmpFileHeader.BitmapOffset = fileBmOffset;
00685
00686 writeBin(*fOut, bmpFileHeader);
00687 }
00688
00689 {
00690
00691 memset(
00692 &bmpV4BmpHeader,
00693 0,
00694 sizeof(bmpV4BmpHeader)
00695 );
00696
00697 bmpV4BmpHeader.Size = sizeof(bmpV4BmpHeader);
00698 bmpV4BmpHeader.Width = picBm->get<LONG>("bm/x-size");
00699 bmpV4BmpHeader.Height = (
00700 !picBm->get<bool>("bm/orient-top") ?
00701 picBm->get<LONG>("bm/y-size") :
00702 -picBm->get<LONG>("bm/y-size")
00703 );
00704 bmpV4BmpHeader.Planes = 1;
00705 bmpV4BmpHeader.BitsPerPixel = picBm->get<WORD>("bm/bpp");
00706
00707
00708 bmpV4BmpHeader.Compression = 3;
00709 bmpV4BmpHeader.RedMask = picBm->get<DWORD>("bm/pixel-mask/r");
00710 bmpV4BmpHeader.GreenMask = picBm->get<DWORD>("bm/pixel-mask/g");
00711 bmpV4BmpHeader.BlueMask = picBm->get<DWORD>("bm/pixel-mask/b");
00712 bmpV4BmpHeader.AlphaMask = picBm->get<DWORD>("bm/pixel-mask/a");
00713
00714 bmpV4BmpHeader.SizeOfBitmap = picBm->get<Integer>("calc/bm/data-size");
00715
00716 pic->check<PictureProps::PropBmPixelsPerLength>();
00717 if(pic->isSet("bm/x-dots-per-m")){
00718 bmpV4BmpHeader.HorzResolution = pic->get<DWORD>("bm/x-dots-per-m");
00719 }
00720 if(pic->isSet("bm/y-dots-per-m")){
00721 bmpV4BmpHeader.VertResolution = pic->get<DWORD>("bm/y-dots-per-m");
00722 }
00723
00724
00725
00726 bmpV4BmpHeader.CSType = 1;
00727
00728 writeBin(*fOut, bmpV4BmpHeader);
00729 }
00730
00731 {
00732 fOut->seekp(
00733 bmpFileHeader.BitmapOffset,
00734 ios_base::beg
00735 );
00736 fOut->write(
00737 picBm->get<Blob>("bm/data").getDataReadOnly(),
00738 picBm->get<Integer>("calc/bm/data-size")
00739 );
00740 }
00741 }
00742
00743 void virtual setPicture(Picture &picture){
00744 PictureHandler::setPicture(picture);
00745 picture.set<String>("handler", "bmp");
00746 }
00747 };
00748
00749 typedef MgrForPicHandler<BmpPictureHandler> BmpPicHandlerMgr;
00750
00751 template <> bool BmpPicHandlerMgr::isHandleableByFileExt(Picture &pictureFile) const{
00752 determineFileExt(pictureFile);
00753 const std::string fileExt = pictureFile.get<String>("opt/file/url-extension");
00754 if(
00755 fileExt == "bmp" || fileExt == "dib"
00756 ){
00757 return true;
00758 }else{
00759 return false;
00760 }
00761 }
00762
00763 template <> bool BmpPicHandlerMgr::isHandleableByContentMagic(Picture &pictureFile) const{
00764 const Integer magicDataSize = 2;
00765 const char magicDataCharArray[] = {'B', 'M'};
00766 const Blob magicData(magicDataSize, magicDataCharArray);
00767 const Integer magicFileOffset = 0;
00768
00769 if(haveContentMagic(pictureFile, magicData, magicFileOffset)){
00770 pictureFile.set("file/mimetype", "image/x-ms-bmp");
00771 return true;
00772 }else{
00773 return false;
00774 }
00775 }
00776
00777 template <> bool BmpPicHandlerMgr::isWriteable(Picture &picture) const{
00778 return (
00779 picture.get<String>("opt/file/mimetype") == "image/x-ms-bmp" ||
00780 picture.get<String>("opt/file/mimetype") == "image/x-bmp"
00781 );
00782 }
00783 }
00784
00785 LIBNEBULAR_SET_PLUGIN_PIC_HANDLER_MGR(libnebular::BmpPicHandlerMgr)