From 5bed6e4a4c916a97f8fe4d1b07f7eecf4d733b90 Mon Sep 17 00:00:00 2001 From: Michele Calgaro Date: Fri, 22 Nov 2024 18:41:30 +0900 Subject: Rename 'digikam' folder to 'src' Signed-off-by: Michele Calgaro (cherry picked from commit ee0d99607c14cb63d3ebdb3a970b508949fa8219) --- src/libs/Makefile.am | 9 + src/libs/curves/Makefile.am | 16 + src/libs/curves/imagecurves.cpp | 768 + src/libs/curves/imagecurves.h | 111 + src/libs/dialogs/Makefile.am | 33 + src/libs/dialogs/ctrlpaneldlg.cpp | 445 + src/libs/dialogs/ctrlpaneldlg.h | 110 + src/libs/dialogs/deletedialog.cpp | 309 + src/libs/dialogs/deletedialog.h | 140 + src/libs/dialogs/deletedialogbase.ui | 188 + src/libs/dialogs/dprogressdlg.cpp | 224 + src/libs/dialogs/dprogressdlg.h | 79 + src/libs/dialogs/iccprofileinfodlg.cpp | 60 + src/libs/dialogs/iccprofileinfodlg.h | 58 + src/libs/dialogs/imagedialog.cpp | 366 + src/libs/dialogs/imagedialog.h | 100 + src/libs/dialogs/imagedlgbase.cpp | 261 + src/libs/dialogs/imagedlgbase.h | 98 + src/libs/dialogs/imageguidedlg.cpp | 597 + src/libs/dialogs/imageguidedlg.h | 123 + src/libs/dialogs/rawcameradlg.cpp | 178 + src/libs/dialogs/rawcameradlg.h | 61 + src/libs/dimg/Makefile.am | 27 + src/libs/dimg/README | 95 + src/libs/dimg/dcolor.cpp | 281 + src/libs/dimg/dcolor.h | 152 + src/libs/dimg/dcolorblend.h | 179 + src/libs/dimg/dcolorcomposer.cpp | 436 + src/libs/dimg/dcolorcomposer.h | 133 + src/libs/dimg/dcolorpixelaccess.h | 78 + src/libs/dimg/ddebug.cpp | 92 + src/libs/dimg/ddebug.h | 73 + src/libs/dimg/dimg.cpp | 1700 + src/libs/dimg/dimg.h | 353 + src/libs/dimg/dimgprivate.h | 81 + src/libs/dimg/dimgscale.cpp | 2127 + src/libs/dimg/drawdecoding.h | 128 + src/libs/dimg/exposurecontainer.h | 65 + src/libs/dimg/filters/Makefile.am | 21 + src/libs/dimg/filters/bcgmodifier.cpp | 208 + src/libs/dimg/filters/bcgmodifier.h | 73 + src/libs/dimg/filters/colormodifier.cpp | 287 + src/libs/dimg/filters/colormodifier.h | 63 + src/libs/dimg/filters/dimggaussianblur.cpp | 327 + src/libs/dimg/filters/dimggaussianblur.h | 97 + src/libs/dimg/filters/dimgimagefilters.cpp | 977 + src/libs/dimg/filters/dimgimagefilters.h | 133 + src/libs/dimg/filters/dimgsharpen.cpp | 243 + src/libs/dimg/filters/dimgsharpen.h | 69 + src/libs/dimg/filters/dimgthreadedfilter.cpp | 170 + src/libs/dimg/filters/dimgthreadedfilter.h | 153 + src/libs/dimg/filters/hslmodifier.cpp | 240 + src/libs/dimg/filters/hslmodifier.h | 58 + src/libs/dimg/filters/icctransform.cpp | 831 + src/libs/dimg/filters/icctransform.h | 94 + src/libs/dimg/loaders/Makefile.am | 21 + src/libs/dimg/loaders/README | 42 + src/libs/dimg/loaders/dimgloader.cpp | 200 + src/libs/dimg/loaders/dimgloader.h | 97 + src/libs/dimg/loaders/dimgloaderobserver.h | 67 + src/libs/dimg/loaders/iccjpeg.c | 270 + src/libs/dimg/loaders/iccjpeg.h | 101 + src/libs/dimg/loaders/jp2kloader.cpp | 715 + src/libs/dimg/loaders/jp2kloader.h | 69 + src/libs/dimg/loaders/jp2ksettings.cpp | 139 + src/libs/dimg/loaders/jp2ksettings.h | 67 + src/libs/dimg/loaders/jpegloader.cpp | 676 + src/libs/dimg/loaders/jpegloader.h | 80 + src/libs/dimg/loaders/jpegsettings.cpp | 154 + src/libs/dimg/loaders/jpegsettings.h | 63 + src/libs/dimg/loaders/pngloader.cpp | 993 + src/libs/dimg/loaders/pngloader.h | 73 + src/libs/dimg/loaders/pngsettings.cpp | 102 + src/libs/dimg/loaders/pngsettings.h | 60 + src/libs/dimg/loaders/ppmloader.cpp | 178 + src/libs/dimg/loaders/ppmloader.h | 59 + src/libs/dimg/loaders/qimageloader.cpp | 126 + src/libs/dimg/loaders/qimageloader.h | 57 + src/libs/dimg/loaders/rawloader.cpp | 371 + src/libs/dimg/loaders/rawloader.h | 86 + src/libs/dimg/loaders/tiffloader.cpp | 806 + src/libs/dimg/loaders/tiffloader.h | 76 + src/libs/dimg/loaders/tiffsettings.cpp | 94 + src/libs/dimg/loaders/tiffsettings.h | 60 + src/libs/dmetadata/Makefile.am | 18 + src/libs/dmetadata/dmetadata.cpp | 544 + src/libs/dmetadata/dmetadata.h | 86 + src/libs/dmetadata/photoinfocontainer.h | 82 + src/libs/greycstoration/CImg.h | 36837 ++++++++ src/libs/greycstoration/LICENSE.txt | 505 + src/libs/greycstoration/Makefile.am | 20 + src/libs/greycstoration/greycstoration.h | 481 + src/libs/greycstoration/greycstorationiface.cpp | 473 + src/libs/greycstoration/greycstorationiface.h | 89 + src/libs/greycstoration/greycstorationsettings.h | 144 + src/libs/greycstoration/greycstorationwidget.cpp | 377 + src/libs/greycstoration/greycstorationwidget.h | 70 + src/libs/histogram/Makefile.am | 16 + src/libs/histogram/imagehistogram.cpp | 548 + src/libs/histogram/imagehistogram.h | 113 + src/libs/imageproperties/Makefile.am | 51 + .../imageproperties/cameraitempropertiestab.cpp | 555 + src/libs/imageproperties/cameraitempropertiestab.h | 69 + src/libs/imageproperties/imagedescedittab.cpp | 1763 + src/libs/imageproperties/imagedescedittab.h | 145 + .../imageproperties/imagepropertiescolorstab.cpp | 799 + .../imageproperties/imagepropertiescolorstab.h | 114 + .../imageproperties/imagepropertiesmetadatatab.cpp | 201 + .../imageproperties/imagepropertiesmetadatatab.h | 68 + .../imageproperties/imagepropertiessidebar.cpp | 150 + src/libs/imageproperties/imagepropertiessidebar.h | 93 + .../imagepropertiessidebarcamgui.cpp | 209 + .../imageproperties/imagepropertiessidebarcamgui.h | 87 + .../imageproperties/imagepropertiessidebardb.cpp | 368 + .../imageproperties/imagepropertiessidebardb.h | 117 + src/libs/imageproperties/imagepropertiestab.cpp | 601 + src/libs/imageproperties/imagepropertiestab.h | 66 + src/libs/imageproperties/navigatebartab.cpp | 146 + src/libs/imageproperties/navigatebartab.h | 85 + src/libs/imageproperties/navigatebarwidget.cpp | 112 + src/libs/imageproperties/navigatebarwidget.h | 70 + src/libs/imageproperties/talbumlistview.cpp | 528 + src/libs/imageproperties/talbumlistview.h | 109 + src/libs/jpegutils/Makefile.am | 22 + src/libs/jpegutils/jinclude.h | 90 + src/libs/jpegutils/jpegint.h | 811 + src/libs/jpegutils/jpegutils.cpp | 532 + src/libs/jpegutils/jpegutils.h | 44 + src/libs/jpegutils/libjpeg62.README | 385 + src/libs/jpegutils/transupp.cpp | 2527 + src/libs/jpegutils/transupp.h | 373 + src/libs/levels/Makefile.am | 17 + src/libs/levels/imagelevels.cpp | 715 + src/libs/levels/imagelevels.h | 105 + src/libs/lprof/Makefile.am | 15 + src/libs/lprof/cmshull.cpp | 1480 + src/libs/lprof/cmslm.cpp | 288 + src/libs/lprof/cmslnr.cpp | 560 + src/libs/lprof/cmsmatn.cpp | 323 + src/libs/lprof/cmsmkmsh.cpp | 346 + src/libs/lprof/cmsmntr.cpp | 371 + src/libs/lprof/cmsoutl.cpp | 284 + src/libs/lprof/cmspcoll.cpp | 1045 + src/libs/lprof/cmsprf.cpp | 439 + src/libs/lprof/cmsreg.cpp | 558 + src/libs/lprof/cmsscn.cpp | 422 + src/libs/lprof/cmssheet.cpp | 1746 + src/libs/lprof/lcmsprf.h | 485 + src/libs/sqlite2/Makefile.am | 43 + src/libs/sqlite2/README | 2 + src/libs/sqlite2/attach.c | 311 + src/libs/sqlite2/auth.c | 219 + src/libs/sqlite2/btree.c | 3584 + src/libs/sqlite2/btree.h | 156 + src/libs/sqlite2/btree_rb.c | 1488 + src/libs/sqlite2/build.c | 2156 + src/libs/sqlite2/copy.c | 110 + src/libs/sqlite2/date.c | 875 + src/libs/sqlite2/delete.c | 393 + src/libs/sqlite2/encode.c | 257 + src/libs/sqlite2/expr.c | 1662 + src/libs/sqlite2/func.c | 658 + src/libs/sqlite2/hash.c | 356 + src/libs/sqlite2/hash.h | 109 + src/libs/sqlite2/insert.c | 919 + src/libs/sqlite2/main.c | 1143 + src/libs/sqlite2/opcodes.c | 140 + src/libs/sqlite2/opcodes.h | 138 + src/libs/sqlite2/os.c | 1850 + src/libs/sqlite2/os.h | 191 + src/libs/sqlite2/pager.c | 2220 + src/libs/sqlite2/pager.h | 107 + src/libs/sqlite2/parse.c | 4035 + src/libs/sqlite2/parse.h | 130 + src/libs/sqlite2/pragma.c | 712 + src/libs/sqlite2/printf.c | 858 + src/libs/sqlite2/random.c | 97 + src/libs/sqlite2/select.c | 2434 + src/libs/sqlite2/shell.c | 1354 + src/libs/sqlite2/sqlite.h | 872 + src/libs/sqlite2/sqliteInt.h | 1274 + src/libs/sqlite2/table.c | 203 + src/libs/sqlite2/tokenize.c | 679 + src/libs/sqlite2/trigger.c | 764 + src/libs/sqlite2/update.c | 459 + src/libs/sqlite2/util.c | 1134 + src/libs/sqlite2/vacuum.c | 305 + src/libs/sqlite2/vdbe.c | 4921 ++ src/libs/sqlite2/vdbe.h | 112 + src/libs/sqlite2/vdbeInt.h | 303 + src/libs/sqlite2/vdbeaux.c | 1061 + src/libs/sqlite2/where.c | 1235 + src/libs/sqlite3/Makefile.am | 9 + src/libs/sqlite3/sqlite3.c | 86994 +++++++++++++++++++ src/libs/sqlite3/sqlite3.h | 5638 ++ src/libs/themeengine/Makefile.am | 12 + src/libs/themeengine/texture.cpp | 495 + src/libs/themeengine/texture.h | 83 + src/libs/themeengine/theme.cpp | 181 + src/libs/themeengine/theme.h | 112 + src/libs/themeengine/themeengine.cpp | 1132 + src/libs/themeengine/themeengine.h | 107 + src/libs/threadimageio/Makefile.am | 23 + src/libs/threadimageio/loadingcache.cpp | 256 + src/libs/threadimageio/loadingcache.h | 135 + src/libs/threadimageio/loadingcacheinterface.cpp | 73 + src/libs/threadimageio/loadingcacheinterface.h | 56 + src/libs/threadimageio/loadingdescription.cpp | 152 + src/libs/threadimageio/loadingdescription.h | 135 + src/libs/threadimageio/loadsavetask.cpp | 424 + src/libs/threadimageio/loadsavetask.h | 360 + src/libs/threadimageio/loadsavethread.cpp | 331 + src/libs/threadimageio/loadsavethread.h | 184 + src/libs/threadimageio/managedloadsavethread.cpp | 362 + src/libs/threadimageio/managedloadsavethread.h | 134 + src/libs/threadimageio/previewloadthread.cpp | 52 + src/libs/threadimageio/previewloadthread.h | 57 + src/libs/threadimageio/previewtask.cpp | 258 + src/libs/threadimageio/previewtask.h | 57 + src/libs/threadimageio/sharedloadsavethread.cpp | 65 + src/libs/threadimageio/sharedloadsavethread.h | 43 + src/libs/thumbbar/Makefile.am | 18 + src/libs/thumbbar/thumbbar.cpp | 1138 + src/libs/thumbbar/thumbbar.h | 239 + src/libs/thumbbar/thumbnailjob.cpp | 318 + src/libs/thumbbar/thumbnailjob.h | 88 + src/libs/whitebalance/Makefile.am | 16 + src/libs/whitebalance/blackbody.h | 539 + src/libs/whitebalance/whitebalance.cpp | 382 + src/libs/whitebalance/whitebalance.h | 72 + src/libs/widgets/Makefile.am | 12 + src/libs/widgets/common/Makefile.am | 25 + src/libs/widgets/common/colorgradientwidget.cpp | 161 + src/libs/widgets/common/colorgradientwidget.h | 73 + src/libs/widgets/common/curveswidget.cpp | 838 + src/libs/widgets/common/curveswidget.h | 132 + src/libs/widgets/common/dcursortracker.cpp | 109 + src/libs/widgets/common/dcursortracker.h | 76 + src/libs/widgets/common/dlogoaction.cpp | 96 + src/libs/widgets/common/dlogoaction.h | 56 + src/libs/widgets/common/dpopupmenu.cpp | 197 + src/libs/widgets/common/dpopupmenu.h | 83 + src/libs/widgets/common/filesaveoptionsbox.cpp | 182 + src/libs/widgets/common/filesaveoptionsbox.h | 72 + src/libs/widgets/common/histogramwidget.cpp | 1089 + src/libs/widgets/common/histogramwidget.h | 177 + src/libs/widgets/common/paniconwidget.cpp | 324 + src/libs/widgets/common/paniconwidget.h | 120 + src/libs/widgets/common/previewwidget.cpp | 640 + src/libs/widgets/common/previewwidget.h | 131 + src/libs/widgets/common/searchtextbar.cpp | 260 + src/libs/widgets/common/searchtextbar.h | 111 + src/libs/widgets/common/sidebar.cpp | 363 + src/libs/widgets/common/sidebar.h | 178 + src/libs/widgets/common/splashscreen.cpp | 160 + src/libs/widgets/common/splashscreen.h | 74 + src/libs/widgets/common/squeezedcombobox.cpp | 198 + src/libs/widgets/common/squeezedcombobox.h | 164 + src/libs/widgets/common/statusled.cpp | 84 + src/libs/widgets/common/statusled.h | 72 + src/libs/widgets/common/statusnavigatebar.cpp | 172 + src/libs/widgets/common/statusnavigatebar.h | 80 + src/libs/widgets/common/statusprogressbar.cpp | 171 + src/libs/widgets/common/statusprogressbar.h | 87 + src/libs/widgets/common/statuszoombar.cpp | 208 + src/libs/widgets/common/statuszoombar.h | 100 + src/libs/widgets/iccprofiles/Makefile.am | 23 + src/libs/widgets/iccprofiles/cietonguewidget.cpp | 816 + src/libs/widgets/iccprofiles/cietonguewidget.h | 115 + src/libs/widgets/iccprofiles/iccpreviewwidget.cpp | 83 + src/libs/widgets/iccprofiles/iccpreviewwidget.h | 71 + src/libs/widgets/iccprofiles/iccprofilewidget.cpp | 448 + src/libs/widgets/iccprofiles/iccprofilewidget.h | 79 + src/libs/widgets/imageplugins/Makefile.am | 22 + src/libs/widgets/imageplugins/imageguidewidget.cpp | 625 + src/libs/widgets/imageplugins/imageguidewidget.h | 132 + src/libs/widgets/imageplugins/imagepanelwidget.cpp | 335 + src/libs/widgets/imageplugins/imagepanelwidget.h | 117 + .../widgets/imageplugins/imagepaniconwidget.cpp | 198 + src/libs/widgets/imageplugins/imagepaniconwidget.h | 68 + .../widgets/imageplugins/imagepannelwidget.cpp | 477 + src/libs/widgets/imageplugins/imagepannelwidget.h | 123 + .../widgets/imageplugins/imageregionwidget.cpp | 473 + src/libs/widgets/imageplugins/imageregionwidget.h | 115 + src/libs/widgets/imageplugins/imagewidget.cpp | 347 + src/libs/widgets/imageplugins/imagewidget.h | 106 + .../widgets/imageplugins/listboxpreviewitem.cpp | 62 + src/libs/widgets/imageplugins/listboxpreviewitem.h | 80 + src/libs/widgets/metadata/Makefile.am | 17 + src/libs/widgets/metadata/exifwidget.cpp | 185 + src/libs/widgets/metadata/exifwidget.h | 73 + src/libs/widgets/metadata/gpswidget.cpp | 340 + src/libs/widgets/metadata/gpswidget.h | 95 + src/libs/widgets/metadata/iptcwidget.cpp | 167 + src/libs/widgets/metadata/iptcwidget.h | 69 + src/libs/widgets/metadata/makernotewidget.cpp | 210 + src/libs/widgets/metadata/makernotewidget.h | 70 + src/libs/widgets/metadata/mdkeylistviewitem.cpp | 94 + src/libs/widgets/metadata/mdkeylistviewitem.h | 63 + src/libs/widgets/metadata/metadatalistview.cpp | 283 + src/libs/widgets/metadata/metadatalistview.h | 85 + src/libs/widgets/metadata/metadatalistviewitem.cpp | 75 + src/libs/widgets/metadata/metadatalistviewitem.h | 59 + src/libs/widgets/metadata/metadatawidget.cpp | 454 + src/libs/widgets/metadata/metadatawidget.h | 120 + src/libs/widgets/metadata/worldmapwidget.cpp | 211 + src/libs/widgets/metadata/worldmapwidget.h | 73 + 307 files changed, 238664 insertions(+) create mode 100644 src/libs/Makefile.am create mode 100644 src/libs/curves/Makefile.am create mode 100644 src/libs/curves/imagecurves.cpp create mode 100644 src/libs/curves/imagecurves.h create mode 100644 src/libs/dialogs/Makefile.am create mode 100644 src/libs/dialogs/ctrlpaneldlg.cpp create mode 100644 src/libs/dialogs/ctrlpaneldlg.h create mode 100644 src/libs/dialogs/deletedialog.cpp create mode 100644 src/libs/dialogs/deletedialog.h create mode 100644 src/libs/dialogs/deletedialogbase.ui create mode 100644 src/libs/dialogs/dprogressdlg.cpp create mode 100644 src/libs/dialogs/dprogressdlg.h create mode 100644 src/libs/dialogs/iccprofileinfodlg.cpp create mode 100644 src/libs/dialogs/iccprofileinfodlg.h create mode 100644 src/libs/dialogs/imagedialog.cpp create mode 100644 src/libs/dialogs/imagedialog.h create mode 100644 src/libs/dialogs/imagedlgbase.cpp create mode 100644 src/libs/dialogs/imagedlgbase.h create mode 100644 src/libs/dialogs/imageguidedlg.cpp create mode 100644 src/libs/dialogs/imageguidedlg.h create mode 100644 src/libs/dialogs/rawcameradlg.cpp create mode 100644 src/libs/dialogs/rawcameradlg.h create mode 100644 src/libs/dimg/Makefile.am create mode 100644 src/libs/dimg/README create mode 100644 src/libs/dimg/dcolor.cpp create mode 100644 src/libs/dimg/dcolor.h create mode 100644 src/libs/dimg/dcolorblend.h create mode 100644 src/libs/dimg/dcolorcomposer.cpp create mode 100644 src/libs/dimg/dcolorcomposer.h create mode 100644 src/libs/dimg/dcolorpixelaccess.h create mode 100644 src/libs/dimg/ddebug.cpp create mode 100644 src/libs/dimg/ddebug.h create mode 100644 src/libs/dimg/dimg.cpp create mode 100644 src/libs/dimg/dimg.h create mode 100644 src/libs/dimg/dimgprivate.h create mode 100644 src/libs/dimg/dimgscale.cpp create mode 100644 src/libs/dimg/drawdecoding.h create mode 100644 src/libs/dimg/exposurecontainer.h create mode 100644 src/libs/dimg/filters/Makefile.am create mode 100644 src/libs/dimg/filters/bcgmodifier.cpp create mode 100644 src/libs/dimg/filters/bcgmodifier.h create mode 100644 src/libs/dimg/filters/colormodifier.cpp create mode 100644 src/libs/dimg/filters/colormodifier.h create mode 100644 src/libs/dimg/filters/dimggaussianblur.cpp create mode 100644 src/libs/dimg/filters/dimggaussianblur.h create mode 100644 src/libs/dimg/filters/dimgimagefilters.cpp create mode 100644 src/libs/dimg/filters/dimgimagefilters.h create mode 100644 src/libs/dimg/filters/dimgsharpen.cpp create mode 100644 src/libs/dimg/filters/dimgsharpen.h create mode 100644 src/libs/dimg/filters/dimgthreadedfilter.cpp create mode 100644 src/libs/dimg/filters/dimgthreadedfilter.h create mode 100644 src/libs/dimg/filters/hslmodifier.cpp create mode 100644 src/libs/dimg/filters/hslmodifier.h create mode 100644 src/libs/dimg/filters/icctransform.cpp create mode 100644 src/libs/dimg/filters/icctransform.h create mode 100644 src/libs/dimg/loaders/Makefile.am create mode 100644 src/libs/dimg/loaders/README create mode 100644 src/libs/dimg/loaders/dimgloader.cpp create mode 100644 src/libs/dimg/loaders/dimgloader.h create mode 100644 src/libs/dimg/loaders/dimgloaderobserver.h create mode 100644 src/libs/dimg/loaders/iccjpeg.c create mode 100644 src/libs/dimg/loaders/iccjpeg.h create mode 100644 src/libs/dimg/loaders/jp2kloader.cpp create mode 100644 src/libs/dimg/loaders/jp2kloader.h create mode 100644 src/libs/dimg/loaders/jp2ksettings.cpp create mode 100644 src/libs/dimg/loaders/jp2ksettings.h create mode 100644 src/libs/dimg/loaders/jpegloader.cpp create mode 100644 src/libs/dimg/loaders/jpegloader.h create mode 100644 src/libs/dimg/loaders/jpegsettings.cpp create mode 100644 src/libs/dimg/loaders/jpegsettings.h create mode 100644 src/libs/dimg/loaders/pngloader.cpp create mode 100644 src/libs/dimg/loaders/pngloader.h create mode 100644 src/libs/dimg/loaders/pngsettings.cpp create mode 100644 src/libs/dimg/loaders/pngsettings.h create mode 100644 src/libs/dimg/loaders/ppmloader.cpp create mode 100644 src/libs/dimg/loaders/ppmloader.h create mode 100644 src/libs/dimg/loaders/qimageloader.cpp create mode 100644 src/libs/dimg/loaders/qimageloader.h create mode 100644 src/libs/dimg/loaders/rawloader.cpp create mode 100644 src/libs/dimg/loaders/rawloader.h create mode 100644 src/libs/dimg/loaders/tiffloader.cpp create mode 100644 src/libs/dimg/loaders/tiffloader.h create mode 100644 src/libs/dimg/loaders/tiffsettings.cpp create mode 100644 src/libs/dimg/loaders/tiffsettings.h create mode 100644 src/libs/dmetadata/Makefile.am create mode 100644 src/libs/dmetadata/dmetadata.cpp create mode 100644 src/libs/dmetadata/dmetadata.h create mode 100644 src/libs/dmetadata/photoinfocontainer.h create mode 100644 src/libs/greycstoration/CImg.h create mode 100644 src/libs/greycstoration/LICENSE.txt create mode 100644 src/libs/greycstoration/Makefile.am create mode 100644 src/libs/greycstoration/greycstoration.h create mode 100644 src/libs/greycstoration/greycstorationiface.cpp create mode 100644 src/libs/greycstoration/greycstorationiface.h create mode 100644 src/libs/greycstoration/greycstorationsettings.h create mode 100644 src/libs/greycstoration/greycstorationwidget.cpp create mode 100644 src/libs/greycstoration/greycstorationwidget.h create mode 100644 src/libs/histogram/Makefile.am create mode 100644 src/libs/histogram/imagehistogram.cpp create mode 100644 src/libs/histogram/imagehistogram.h create mode 100644 src/libs/imageproperties/Makefile.am create mode 100644 src/libs/imageproperties/cameraitempropertiestab.cpp create mode 100644 src/libs/imageproperties/cameraitempropertiestab.h create mode 100644 src/libs/imageproperties/imagedescedittab.cpp create mode 100644 src/libs/imageproperties/imagedescedittab.h create mode 100644 src/libs/imageproperties/imagepropertiescolorstab.cpp create mode 100644 src/libs/imageproperties/imagepropertiescolorstab.h create mode 100644 src/libs/imageproperties/imagepropertiesmetadatatab.cpp create mode 100644 src/libs/imageproperties/imagepropertiesmetadatatab.h create mode 100644 src/libs/imageproperties/imagepropertiessidebar.cpp create mode 100644 src/libs/imageproperties/imagepropertiessidebar.h create mode 100644 src/libs/imageproperties/imagepropertiessidebarcamgui.cpp create mode 100644 src/libs/imageproperties/imagepropertiessidebarcamgui.h create mode 100644 src/libs/imageproperties/imagepropertiessidebardb.cpp create mode 100644 src/libs/imageproperties/imagepropertiessidebardb.h create mode 100644 src/libs/imageproperties/imagepropertiestab.cpp create mode 100644 src/libs/imageproperties/imagepropertiestab.h create mode 100644 src/libs/imageproperties/navigatebartab.cpp create mode 100644 src/libs/imageproperties/navigatebartab.h create mode 100644 src/libs/imageproperties/navigatebarwidget.cpp create mode 100644 src/libs/imageproperties/navigatebarwidget.h create mode 100644 src/libs/imageproperties/talbumlistview.cpp create mode 100644 src/libs/imageproperties/talbumlistview.h create mode 100644 src/libs/jpegutils/Makefile.am create mode 100644 src/libs/jpegutils/jinclude.h create mode 100644 src/libs/jpegutils/jpegint.h create mode 100644 src/libs/jpegutils/jpegutils.cpp create mode 100644 src/libs/jpegutils/jpegutils.h create mode 100644 src/libs/jpegutils/libjpeg62.README create mode 100644 src/libs/jpegutils/transupp.cpp create mode 100644 src/libs/jpegutils/transupp.h create mode 100644 src/libs/levels/Makefile.am create mode 100644 src/libs/levels/imagelevels.cpp create mode 100644 src/libs/levels/imagelevels.h create mode 100644 src/libs/lprof/Makefile.am create mode 100644 src/libs/lprof/cmshull.cpp create mode 100644 src/libs/lprof/cmslm.cpp create mode 100644 src/libs/lprof/cmslnr.cpp create mode 100644 src/libs/lprof/cmsmatn.cpp create mode 100644 src/libs/lprof/cmsmkmsh.cpp create mode 100644 src/libs/lprof/cmsmntr.cpp create mode 100644 src/libs/lprof/cmsoutl.cpp create mode 100644 src/libs/lprof/cmspcoll.cpp create mode 100644 src/libs/lprof/cmsprf.cpp create mode 100644 src/libs/lprof/cmsreg.cpp create mode 100644 src/libs/lprof/cmsscn.cpp create mode 100644 src/libs/lprof/cmssheet.cpp create mode 100644 src/libs/lprof/lcmsprf.h create mode 100644 src/libs/sqlite2/Makefile.am create mode 100644 src/libs/sqlite2/README create mode 100644 src/libs/sqlite2/attach.c create mode 100644 src/libs/sqlite2/auth.c create mode 100644 src/libs/sqlite2/btree.c create mode 100644 src/libs/sqlite2/btree.h create mode 100644 src/libs/sqlite2/btree_rb.c create mode 100644 src/libs/sqlite2/build.c create mode 100644 src/libs/sqlite2/copy.c create mode 100644 src/libs/sqlite2/date.c create mode 100644 src/libs/sqlite2/delete.c create mode 100644 src/libs/sqlite2/encode.c create mode 100644 src/libs/sqlite2/expr.c create mode 100644 src/libs/sqlite2/func.c create mode 100644 src/libs/sqlite2/hash.c create mode 100644 src/libs/sqlite2/hash.h create mode 100644 src/libs/sqlite2/insert.c create mode 100644 src/libs/sqlite2/main.c create mode 100644 src/libs/sqlite2/opcodes.c create mode 100644 src/libs/sqlite2/opcodes.h create mode 100644 src/libs/sqlite2/os.c create mode 100644 src/libs/sqlite2/os.h create mode 100644 src/libs/sqlite2/pager.c create mode 100644 src/libs/sqlite2/pager.h create mode 100644 src/libs/sqlite2/parse.c create mode 100644 src/libs/sqlite2/parse.h create mode 100644 src/libs/sqlite2/pragma.c create mode 100644 src/libs/sqlite2/printf.c create mode 100644 src/libs/sqlite2/random.c create mode 100644 src/libs/sqlite2/select.c create mode 100644 src/libs/sqlite2/shell.c create mode 100644 src/libs/sqlite2/sqlite.h create mode 100644 src/libs/sqlite2/sqliteInt.h create mode 100644 src/libs/sqlite2/table.c create mode 100644 src/libs/sqlite2/tokenize.c create mode 100644 src/libs/sqlite2/trigger.c create mode 100644 src/libs/sqlite2/update.c create mode 100644 src/libs/sqlite2/util.c create mode 100644 src/libs/sqlite2/vacuum.c create mode 100644 src/libs/sqlite2/vdbe.c create mode 100644 src/libs/sqlite2/vdbe.h create mode 100644 src/libs/sqlite2/vdbeInt.h create mode 100644 src/libs/sqlite2/vdbeaux.c create mode 100644 src/libs/sqlite2/where.c create mode 100644 src/libs/sqlite3/Makefile.am create mode 100644 src/libs/sqlite3/sqlite3.c create mode 100644 src/libs/sqlite3/sqlite3.h create mode 100644 src/libs/themeengine/Makefile.am create mode 100644 src/libs/themeengine/texture.cpp create mode 100644 src/libs/themeengine/texture.h create mode 100644 src/libs/themeengine/theme.cpp create mode 100644 src/libs/themeengine/theme.h create mode 100644 src/libs/themeengine/themeengine.cpp create mode 100644 src/libs/themeengine/themeengine.h create mode 100644 src/libs/threadimageio/Makefile.am create mode 100644 src/libs/threadimageio/loadingcache.cpp create mode 100644 src/libs/threadimageio/loadingcache.h create mode 100644 src/libs/threadimageio/loadingcacheinterface.cpp create mode 100644 src/libs/threadimageio/loadingcacheinterface.h create mode 100644 src/libs/threadimageio/loadingdescription.cpp create mode 100644 src/libs/threadimageio/loadingdescription.h create mode 100644 src/libs/threadimageio/loadsavetask.cpp create mode 100644 src/libs/threadimageio/loadsavetask.h create mode 100644 src/libs/threadimageio/loadsavethread.cpp create mode 100644 src/libs/threadimageio/loadsavethread.h create mode 100644 src/libs/threadimageio/managedloadsavethread.cpp create mode 100644 src/libs/threadimageio/managedloadsavethread.h create mode 100644 src/libs/threadimageio/previewloadthread.cpp create mode 100644 src/libs/threadimageio/previewloadthread.h create mode 100644 src/libs/threadimageio/previewtask.cpp create mode 100644 src/libs/threadimageio/previewtask.h create mode 100644 src/libs/threadimageio/sharedloadsavethread.cpp create mode 100644 src/libs/threadimageio/sharedloadsavethread.h create mode 100644 src/libs/thumbbar/Makefile.am create mode 100644 src/libs/thumbbar/thumbbar.cpp create mode 100644 src/libs/thumbbar/thumbbar.h create mode 100644 src/libs/thumbbar/thumbnailjob.cpp create mode 100644 src/libs/thumbbar/thumbnailjob.h create mode 100644 src/libs/whitebalance/Makefile.am create mode 100644 src/libs/whitebalance/blackbody.h create mode 100644 src/libs/whitebalance/whitebalance.cpp create mode 100644 src/libs/whitebalance/whitebalance.h create mode 100644 src/libs/widgets/Makefile.am create mode 100644 src/libs/widgets/common/Makefile.am create mode 100644 src/libs/widgets/common/colorgradientwidget.cpp create mode 100644 src/libs/widgets/common/colorgradientwidget.h create mode 100644 src/libs/widgets/common/curveswidget.cpp create mode 100644 src/libs/widgets/common/curveswidget.h create mode 100644 src/libs/widgets/common/dcursortracker.cpp create mode 100644 src/libs/widgets/common/dcursortracker.h create mode 100644 src/libs/widgets/common/dlogoaction.cpp create mode 100644 src/libs/widgets/common/dlogoaction.h create mode 100644 src/libs/widgets/common/dpopupmenu.cpp create mode 100644 src/libs/widgets/common/dpopupmenu.h create mode 100644 src/libs/widgets/common/filesaveoptionsbox.cpp create mode 100644 src/libs/widgets/common/filesaveoptionsbox.h create mode 100644 src/libs/widgets/common/histogramwidget.cpp create mode 100644 src/libs/widgets/common/histogramwidget.h create mode 100644 src/libs/widgets/common/paniconwidget.cpp create mode 100644 src/libs/widgets/common/paniconwidget.h create mode 100644 src/libs/widgets/common/previewwidget.cpp create mode 100644 src/libs/widgets/common/previewwidget.h create mode 100644 src/libs/widgets/common/searchtextbar.cpp create mode 100644 src/libs/widgets/common/searchtextbar.h create mode 100644 src/libs/widgets/common/sidebar.cpp create mode 100644 src/libs/widgets/common/sidebar.h create mode 100644 src/libs/widgets/common/splashscreen.cpp create mode 100644 src/libs/widgets/common/splashscreen.h create mode 100644 src/libs/widgets/common/squeezedcombobox.cpp create mode 100644 src/libs/widgets/common/squeezedcombobox.h create mode 100644 src/libs/widgets/common/statusled.cpp create mode 100644 src/libs/widgets/common/statusled.h create mode 100644 src/libs/widgets/common/statusnavigatebar.cpp create mode 100644 src/libs/widgets/common/statusnavigatebar.h create mode 100644 src/libs/widgets/common/statusprogressbar.cpp create mode 100644 src/libs/widgets/common/statusprogressbar.h create mode 100644 src/libs/widgets/common/statuszoombar.cpp create mode 100644 src/libs/widgets/common/statuszoombar.h create mode 100644 src/libs/widgets/iccprofiles/Makefile.am create mode 100644 src/libs/widgets/iccprofiles/cietonguewidget.cpp create mode 100644 src/libs/widgets/iccprofiles/cietonguewidget.h create mode 100644 src/libs/widgets/iccprofiles/iccpreviewwidget.cpp create mode 100644 src/libs/widgets/iccprofiles/iccpreviewwidget.h create mode 100644 src/libs/widgets/iccprofiles/iccprofilewidget.cpp create mode 100644 src/libs/widgets/iccprofiles/iccprofilewidget.h create mode 100644 src/libs/widgets/imageplugins/Makefile.am create mode 100644 src/libs/widgets/imageplugins/imageguidewidget.cpp create mode 100644 src/libs/widgets/imageplugins/imageguidewidget.h create mode 100644 src/libs/widgets/imageplugins/imagepanelwidget.cpp create mode 100644 src/libs/widgets/imageplugins/imagepanelwidget.h create mode 100644 src/libs/widgets/imageplugins/imagepaniconwidget.cpp create mode 100644 src/libs/widgets/imageplugins/imagepaniconwidget.h create mode 100644 src/libs/widgets/imageplugins/imagepannelwidget.cpp create mode 100644 src/libs/widgets/imageplugins/imagepannelwidget.h create mode 100644 src/libs/widgets/imageplugins/imageregionwidget.cpp create mode 100644 src/libs/widgets/imageplugins/imageregionwidget.h create mode 100644 src/libs/widgets/imageplugins/imagewidget.cpp create mode 100644 src/libs/widgets/imageplugins/imagewidget.h create mode 100644 src/libs/widgets/imageplugins/listboxpreviewitem.cpp create mode 100644 src/libs/widgets/imageplugins/listboxpreviewitem.h create mode 100644 src/libs/widgets/metadata/Makefile.am create mode 100644 src/libs/widgets/metadata/exifwidget.cpp create mode 100644 src/libs/widgets/metadata/exifwidget.h create mode 100644 src/libs/widgets/metadata/gpswidget.cpp create mode 100644 src/libs/widgets/metadata/gpswidget.h create mode 100644 src/libs/widgets/metadata/iptcwidget.cpp create mode 100644 src/libs/widgets/metadata/iptcwidget.h create mode 100644 src/libs/widgets/metadata/makernotewidget.cpp create mode 100644 src/libs/widgets/metadata/makernotewidget.h create mode 100644 src/libs/widgets/metadata/mdkeylistviewitem.cpp create mode 100644 src/libs/widgets/metadata/mdkeylistviewitem.h create mode 100644 src/libs/widgets/metadata/metadatalistview.cpp create mode 100644 src/libs/widgets/metadata/metadatalistview.h create mode 100644 src/libs/widgets/metadata/metadatalistviewitem.cpp create mode 100644 src/libs/widgets/metadata/metadatalistviewitem.h create mode 100644 src/libs/widgets/metadata/metadatawidget.cpp create mode 100644 src/libs/widgets/metadata/metadatawidget.h create mode 100644 src/libs/widgets/metadata/worldmapwidget.cpp create mode 100644 src/libs/widgets/metadata/worldmapwidget.h (limited to 'src/libs') diff --git a/src/libs/Makefile.am b/src/libs/Makefile.am new file mode 100644 index 00000000..a9af1e06 --- /dev/null +++ b/src/libs/Makefile.am @@ -0,0 +1,9 @@ +if with_included_sqlite3 + SQLITE3_SUBDIR = sqlite3 +endif + +COMPILE_FIRST = sqlite2 $(SQLITE3_SUBDIR) + +SUBDIRS = sqlite2 $(SQLITE3_SUBDIR) lprof histogram levels curves whitebalance dmetadata \ + dimg threadimageio themeengine widgets greycstoration \ + thumbbar jpegutils imageproperties dialogs diff --git a/src/libs/curves/Makefile.am b/src/libs/curves/Makefile.am new file mode 100644 index 00000000..f183fcc7 --- /dev/null +++ b/src/libs/curves/Makefile.am @@ -0,0 +1,16 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libcurves.la + +libcurves_la_SOURCES = imagecurves.cpp + +libcurves_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +INCLUDES = -I$(top_srcdir)/src/libs/histogram \ + -I$(top_srcdir)/src/libs/dimg \ + -I$(top_srcdir)/src/digikam \ + $(all_includes) + +digikaminclude_HEADERS = imagecurves.h + +digikamincludedir = $(includedir)/digikam diff --git a/src/libs/curves/imagecurves.cpp b/src/libs/curves/imagecurves.cpp new file mode 100644 index 00000000..c5e067eb --- /dev/null +++ b/src/libs/curves/imagecurves.cpp @@ -0,0 +1,768 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-12-01 + * Description : image curves manipulation methods. + * + * Copyright (C) 2004-2008 by Gilles Caulier + * + * Some code parts are inspired from gimp 2.0 + * app/base/curves.c, gimplut.c, and app/base/gimpcurvetool.c + * source files. + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) + +// C++ includes. + +#include +#include +#include +#include +#include + +// TQt includes. + +#include + +// Local includes. + +#include "ddebug.h" +#include "imagecurves.h" + +namespace Digikam +{ + +class ImageCurvesPriv +{ + +public: + + struct _Curves + { + ImageCurves::CurveType curve_type[5]; // Curve types by channels (Smooth or Free). + int points[5][17][2]; // Curve main points in Smooth mode ([channel][point id][x,y]). + unsigned short curve[5][65536]; // Curve values by channels. + }; + + struct _Lut + { + unsigned short **luts; + int nchannels; + }; + +public: + + ImageCurvesPriv() + { + curves = 0; + lut = 0; + dirty = false; + } + + // Curves data. + struct _Curves *curves; + + // Lut data. + struct _Lut *lut; + + int segmentMax; + + bool dirty; +}; + +ImageCurves::CRMatrix CR_basis = +{ + { -0.5, 1.5, -1.5, 0.5 }, + { 1.0, -2.5, 2.0, -0.5 }, + { -0.5, 0.0, 0.5, 0.0 }, + { 0.0, 1.0, 0.0, 0.0 }, +}; + +ImageCurves::ImageCurves(bool sixteenBit) +{ + d = new ImageCurvesPriv; + d->lut = new ImageCurvesPriv::_Lut; + d->curves = new ImageCurvesPriv::_Curves; + d->segmentMax = sixteenBit ? 65535 : 255; + + curvesReset(); +} + +ImageCurves::~ImageCurves() +{ + if (d->lut) + { + if (d->lut->luts) + { + for (int i = 0 ; i < d->lut->nchannels ; i++) + delete [] d->lut->luts[i]; + + delete [] d->lut->luts; + } + + delete d->lut; + } + + if (d->curves) + delete d->curves; + + delete d; +} + +bool ImageCurves::isDirty() +{ + return d->dirty; +} + +bool ImageCurves::isSixteenBits() +{ + return (d->segmentMax == 65535); +} + +void ImageCurves::curvesReset() +{ + memset(d->curves, 0, sizeof(struct ImageCurvesPriv::_Curves)); + d->lut->luts = NULL; + d->lut->nchannels = 0; + d->dirty = false; + + for (int channel = 0 ; channel < 5 ; channel++) + { + setCurveType(channel, CURVE_SMOOTH); + curvesChannelReset(channel); + } +} + +void ImageCurves::curvesChannelReset(int channel) +{ + int j; + + if (!d->curves) return; + + // Contruct a linear curve. + + for (j = 0 ; j <= d->segmentMax ; j++) + d->curves->curve[channel][j] = j; + + // Init coordinates points to null. + + for (j = 0 ; j < 17 ; j++) + { + d->curves->points[channel][j][0] = -1; + d->curves->points[channel][j][1] = -1; + } + + // First and last points init. + + d->curves->points[channel][0][0] = 0; + d->curves->points[channel][0][1] = 0; + d->curves->points[channel][16][0] = d->segmentMax; + d->curves->points[channel][16][1] = d->segmentMax; +} + +void ImageCurves::curvesCalculateCurve(int channel) +{ + int i; + int points[17]; + int num_pts; + int p1, p2, p3, p4; + + if (!d->curves) return; + + switch (d->curves->curve_type[channel]) + { + case CURVE_FREE: + break; + + case CURVE_SMOOTH: + { + // Cycle through the curves + + num_pts = 0; + + for (i = 0 ; i < 17 ; i++) + if (d->curves->points[channel][i][0] != -1) + points[num_pts++] = i; + + // Initialize boundary curve points + + if (num_pts != 0) + { + for (i = 0 ; i < d->curves->points[channel][points[0]][0] ; i++) + { + d->curves->curve[channel][i] = d->curves->points[channel][points[0]][1]; + } + + for (i = d->curves->points[channel][points[num_pts - 1]][0] ; i <= d->segmentMax ; i++) + { + d->curves->curve[channel][i] = d->curves->points[channel][points[num_pts - 1]][1]; + } + } + + for (i = 0 ; i < num_pts - 1 ; i++) + { + p1 = (i == 0) ? points[i] : points[(i - 1)]; + p2 = points[i]; + p3 = points[(i + 1)]; + p4 = (i == (num_pts - 2)) ? points[(num_pts - 1)] : points[(i + 2)]; + + curvesPlotCurve(channel, p1, p2, p3, p4); + } + + // Ensure that the control points are used exactly + + for (i = 0 ; i < num_pts ; i++) + { + int x, y; + + x = d->curves->points[channel][points[i]][0]; + y = d->curves->points[channel][points[i]][1]; + d->curves->curve[channel][x] = y; + } + + break; + } + } +} + +float ImageCurves::curvesLutFunc(int n_channels, int channel, float value) +{ + float f; + int index; + double inten; + int j; + + if (!d->curves) return 0.0; + + if (n_channels == 1) + j = 0; + else + j = channel + 1; + + inten = value; + + // For color images this runs through the loop with j = channel +1 + // the first time and j = 0 the second time. + + // For bw images this runs through the loop with j = 0 the first and + // only time. + + for ( ; j >= 0 ; j -= (channel + 1)) + { + // Don't apply the overall curve to the alpha channel. + + if (j == 0 && (n_channels == 2 || n_channels == 4) && channel == n_channels -1) + return inten; + + if (inten < 0.0) + inten = d->curves->curve[j][0]/(float)d->segmentMax; + else if (inten >= 1.0) + inten = d->curves->curve[j][d->segmentMax]/(float)(d->segmentMax); + else // interpolate the curve. + { + index = (int)floor(inten * (float)(d->segmentMax)); + f = inten * (float)(d->segmentMax) - index; + inten = ((1.0 - f) * d->curves->curve[j][index ] + + ( f) * d->curves->curve[j][index + 1] ) / (float)(d->segmentMax); + } + } + + return inten; +} + +void ImageCurves::curvesPlotCurve(int channel, int p1, int p2, int p3, int p4) +{ + CRMatrix geometry; + CRMatrix tmp1, tmp2; + CRMatrix deltas; + double x, dx, dx2, dx3; + double y, dy, dy2, dy3; + double d1, d2, d3; + int lastx, lasty; + int newx, newy; + int i; + int loopdiv = d->segmentMax * 3; + + if (!d->curves) return; + + // Construct the geometry matrix from the segment. + + for (i = 0 ; i < 4 ; i++) + { + geometry[i][2] = 0; + geometry[i][3] = 0; + } + + for (i = 0 ; i < 2 ; i++) + { + geometry[0][i] = d->curves->points[channel][p1][i]; + geometry[1][i] = d->curves->points[channel][p2][i]; + geometry[2][i] = d->curves->points[channel][p3][i]; + geometry[3][i] = d->curves->points[channel][p4][i]; + } + + // Subdivide the curve 1000 times. + // n can be adjusted to give a finer or coarser curve. + + d1 = 1.0 / loopdiv; + d2 = d1 * d1; + d3 = d1 * d1 * d1; + + // Construct a temporary matrix for determining the forward differencing deltas. + + tmp2[0][0] = 0; tmp2[0][1] = 0; tmp2[0][2] = 0; tmp2[0][3] = 1; + tmp2[1][0] = d3; tmp2[1][1] = d2; tmp2[1][2] = d1; tmp2[1][3] = 0; + tmp2[2][0] = 6*d3; tmp2[2][1] = 2*d2; tmp2[2][2] = 0; tmp2[2][3] = 0; + tmp2[3][0] = 6*d3; tmp2[3][1] = 0; tmp2[3][2] = 0; tmp2[3][3] = 0; + + // Compose the basis and geometry matrices. + + curvesCRCompose(CR_basis, geometry, tmp1); + + // Compose the above results to get the deltas matrix. + + curvesCRCompose(tmp2, tmp1, deltas); + + // Extract the x deltas. + + x = deltas[0][0]; + dx = deltas[1][0]; + dx2 = deltas[2][0]; + dx3 = deltas[3][0]; + + // Extract the y deltas. + + y = deltas[0][1]; + dy = deltas[1][1]; + dy2 = deltas[2][1]; + dy3 = deltas[3][1]; + + lastx = (int)CLAMP (x, 0, d->segmentMax); + lasty = (int)CLAMP (y, 0, d->segmentMax); + + d->curves->curve[channel][lastx] = lasty; + + // Loop over the curve. + + for (i = 0 ; i < loopdiv ; i++) + { + // Increment the x values. + + x += dx; + dx += dx2; + dx2 += dx3; + + // Increment the y values. + + y += dy; + dy += dy2; + dy2 += dy3; + + newx = CLAMP(ROUND (x), 0, d->segmentMax); + newy = CLAMP(ROUND (y), 0, d->segmentMax); + + // If this point is different than the last one...then draw it. + + if ((lastx != newx) || (lasty != newy)) + d->curves->curve[channel][newx] = newy; + + lastx = newx; + lasty = newy; + } +} + +void ImageCurves::curvesCRCompose(CRMatrix a, CRMatrix b, CRMatrix ab) +{ + int i, j; + + for (i = 0 ; i < 4 ; i++) + { + for (j = 0 ; j < 4 ; j++) + { + ab[i][j] = (a[i][0] * b[0][j] + + a[i][1] * b[1][j] + + a[i][2] * b[2][j] + + a[i][3] * b[3][j]); + } + } +} + +void ImageCurves::curvesLutSetup(int nchannels) +{ + int i; + uint v; + double val; + + if (d->lut->luts) + { + for (i = 0 ; i < d->lut->nchannels ; i++) + delete [] d->lut->luts[i]; + + delete [] d->lut->luts; + } + + d->lut->nchannels = nchannels; + d->lut->luts = new unsigned short*[d->lut->nchannels]; + + for (i = 0 ; i < d->lut->nchannels ; i++) + { + d->lut->luts[i] = new unsigned short[d->segmentMax+1]; + + for (v = 0 ; v <= (uint)d->segmentMax ; v++) + { + // To add gamma correction use func(v ^ g) ^ 1/g instead. + + val = (float)(d->segmentMax) * curvesLutFunc( d->lut->nchannels, i, v / (float)(d->segmentMax)) + 0.5; + + d->lut->luts[i][v] = (unsigned short)CLAMP (val, 0, d->segmentMax); + } + } +} + +void ImageCurves::curvesLutProcess(uchar *srcPR, uchar *destPR, int w, int h) +{ + unsigned short *lut0 = NULL, *lut1 = NULL, *lut2 = NULL, *lut3 = NULL; + + int i; + + if (d->lut->nchannels > 0) + lut0 = d->lut->luts[0]; + if (d->lut->nchannels > 1) + lut1 = d->lut->luts[1]; + if (d->lut->nchannels > 2) + lut2 = d->lut->luts[2]; + if (d->lut->nchannels > 3) + lut3 = d->lut->luts[3]; + + if (d->segmentMax == 255) // 8 bits image. + { + uchar red, green, blue, alpha; + uchar *ptr = srcPR; + uchar *dst = destPR; + + for (i = 0 ; i < w*h ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + alpha = ptr[3]; + + if ( d->lut->nchannels > 0 ) + red = lut0[red]; + + if ( d->lut->nchannels > 1 ) + green = lut1[green]; + + if ( d->lut->nchannels > 2 ) + blue = lut2[blue]; + + if ( d->lut->nchannels > 3 ) + alpha = lut3[alpha]; + + dst[0] = blue; + dst[1] = green; + dst[2] = red; + dst[3] = alpha; + + ptr += 4; + dst += 4; + } + } + else // 16 bits image. + { + unsigned short red, green, blue, alpha; + unsigned short *ptr = (unsigned short *)srcPR; + unsigned short *dst = (unsigned short *)destPR; + + for (i = 0 ; i < w*h ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + alpha = ptr[3]; + + if ( d->lut->nchannels > 0 ) + red = lut0[red]; + + if ( d->lut->nchannels > 1 ) + green = lut1[green]; + + if ( d->lut->nchannels > 2 ) + blue = lut2[blue]; + + if ( d->lut->nchannels > 3 ) + alpha = lut3[alpha]; + + dst[0] = blue; + dst[1] = green; + dst[2] = red; + dst[3] = alpha; + + ptr += 4; + dst += 4; + } + } +} + +int ImageCurves::getCurveValue(int channel, int bin) +{ + if ( d->curves && + channel>=0 && channel<5 && + bin>=0 && bin<=d->segmentMax ) + return(d->curves->curve[channel][bin]); + + return 0; +} + +TQPoint ImageCurves::getCurvePoint(int channel, int point) +{ + if ( d->curves && + channel>=0 && channel<5 && + point>=0 && point<=17 ) + return(TQPoint(d->curves->points[channel][point][0], + d->curves->points[channel][point][1]) ); + + return TQPoint(-1, -1); +} + +TQPointArray ImageCurves::getCurvePoints(int channel) +{ + TQPointArray array(18); + + if ( d->curves && + channel>=0 && channel<5) + { + for (int j = 0 ; j <= 17 ; j++) + array.setPoint(j, getCurvePoint(channel, j)); + } + + return array; +} + +int ImageCurves::getCurvePointX(int channel, int point) +{ + if ( d->curves && + channel>=0 && channel<5 && + point>=0 && point<=17 ) + return(d->curves->points[channel][point][0]); + + return(-1); +} + +int ImageCurves::getCurvePointY(int channel, int point) +{ + if ( d->curves && + channel>=0 && channel<5 && + point>=0 && point<=17 ) + return(d->curves->points[channel][point][1]); + + return (-1); +} + +int ImageCurves::getCurveType(int channel) +{ + if ( d->curves && + channel>=0 && channel<5 ) + return ( d->curves->curve_type[channel] ); + + return (-1); +} + +void ImageCurves::setCurveValue(int channel, int bin, int val) +{ + if ( d->curves && + channel>=0 && channel<5 && + bin>=0 && bin<=d->segmentMax ) + { + d->dirty = true; + d->curves->curve[channel][bin] = val; + } +} + +void ImageCurves::setCurvePoint(int channel, int point, const TQPoint& val) +{ + if ( d->curves && + channel>=0 && channel<5 && + point>=0 && point<=17 && + val.x()>=-1 && val.x()<=d->segmentMax && // x can be egal to -1 + val.y()>=0 && val.y()<=d->segmentMax) // if the current point is disable !!! + { + d->dirty = true; + d->curves->points[channel][point][0] = val.x(); + d->curves->points[channel][point][1] = val.y(); + } +} + +void ImageCurves::setCurvePoints(int channel, const TQPointArray& vals) +{ + if ( d->curves && + channel>=0 && channel<5 && + vals.size() == 18 ) + { + d->dirty = true; + for (int j = 0 ; j <= 17 ; j++) + { + setCurvePoint(channel, j, vals.point(j)); + } + } +} + +void ImageCurves::setCurvePointX(int channel, int point, int x) +{ + if ( d->curves && + channel>=0 && channel<5 && + point>=0 && point<=17 && + x>=-1 && x<=d->segmentMax) // x can be egal to -1 if the current point is disable !!! + { + d->dirty = true; + d->curves->points[channel][point][0] = x; + } +} + +void ImageCurves::setCurvePointY(int channel, int point, int y) +{ + if ( d->curves && + channel>=0 && channel<5 && + point>=0 && point<=17 && + y>=0 && y<=d->segmentMax) + { + d->dirty = true; + d->curves->points[channel][point][1] = y; + } +} + +void ImageCurves::setCurveType(int channel, CurveType type) +{ + if ( d->curves && + channel>=0 && channel<5 && + type>=CURVE_SMOOTH && type<=CURVE_FREE ) + d->curves->curve_type[channel] = type; +} + +bool ImageCurves::loadCurvesFromGimpCurvesFile(const KURL& fileUrl) +{ + // TODO : support KURL ! + + FILE *file; + int i, j; + int fields; + char buf[50]; + int index[5][17]; + int value[5][17]; + + file = fopen(TQFile::encodeName(fileUrl.path()), "r"); + if (!file) + return false; + + if (! fgets (buf, sizeof (buf), file)) + { + fclose(file); + return false; + } + + if (strcmp (buf, "# GIMP Curves File\n") != 0) + return false; + + for (i = 0 ; i < 5 ; i++) + { + for (j = 0 ; j < 17 ; j++) + { + fields = fscanf (file, "%d %d ", &index[i][j], &value[i][j]); + if (fields != 2) + { + DWarning() << "Invalid Gimp curves file!" << endl; + fclose(file); + return false; + } + } + } + + curvesReset(); + + for (i = 0 ; i < 5 ; i++) + { + d->curves->curve_type[i] = CURVE_SMOOTH; + + for (j = 0 ; j < 17 ; j++) + { + d->curves->points[i][j][0] = ((d->segmentMax == 65535) && (index[i][j] !=-1) ? + index[i][j]*255 : index[i][j]); + d->curves->points[i][j][1] = ((d->segmentMax == 65535) && (value[i][j] !=-1) ? + value[i][j]*255 : value[i][j]); + } + } + + for (i = 0 ; i < 5 ; i++) + curvesCalculateCurve(i); + + fclose(file); + return true; +} + +bool ImageCurves::saveCurvesToGimpCurvesFile(const KURL& fileUrl) +{ + // TODO : support KURL ! + + FILE *file; + int i, j; + int index; + + file = fopen(TQFile::encodeName(fileUrl.path()), "w"); + + if (!file) + return false; + + for (i = 0 ; i < 5 ; i++) + { + if (d->curves->curve_type[i] == CURVE_FREE) + { + // Pick representative points from the curve and make them control points. + + for (j = 0 ; j <= 8 ; j++) + { + index = CLAMP(j * 32, 0, d->segmentMax); + d->curves->points[i][j * 2][0] = index; + d->curves->points[i][j * 2][1] = d->curves->curve[i][index]; + } + } + } + + fprintf (file, "# GIMP Curves File\n"); + + for (i = 0 ; i < 5 ; i++) + { + for (j = 0 ; j < 17 ; j++) + { + fprintf (file, "%d %d ", + ((d->segmentMax == 65535) && (d->curves->points[i][j][0]!=-1) ? + d->curves->points[i][j][0]/255 : d->curves->points[i][j][0]), + ((d->segmentMax == 65535) && (d->curves->points[i][j][1]!=-1) ? + d->curves->points[i][j][1]/255 : d->curves->points[i][j][1])); + + fprintf (file, "\n"); + } + } + + fflush(file); + fclose(file); + + return true; +} + +} // NameSpace Digikam diff --git a/src/libs/curves/imagecurves.h b/src/libs/curves/imagecurves.h new file mode 100644 index 00000000..69d33c08 --- /dev/null +++ b/src/libs/curves/imagecurves.h @@ -0,0 +1,111 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-12-01 + * Description : image curves manipulation methods. + * + * Copyright (c) 2004-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef IMAGECURVES_H +#define IMAGECURVES_H + +#define ROUND(x) ((int) ((x) + 0.5)) + +// TQt includes. + +#include +#include + +// KDE includes. + +#include + +// Digikam includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class ImageCurvesPriv; + +class DIGIKAM_EXPORT ImageCurves +{ + +public: + + enum CurveType + { + CURVE_SMOOTH = 0, // Smooth curve type + CURVE_FREE // Freehand curve type. + }; + + typedef double CRMatrix[4][4]; + +public: + + ImageCurves(bool sixteenBit); + ~ImageCurves(); + + // Methods for to manipulate the curves data. + + bool isDirty(); + bool isSixteenBits(); + void curvesReset(); + void curvesChannelReset(int channel); + void curvesCalculateCurve(int channel); + float curvesLutFunc(int n_channels, int channel, float value); + void curvesLutSetup(int nchannels); + void curvesLutProcess(uchar *srcPR, uchar *destPR, int w, int h); + + // Methods for to set manually the curves values. + + void setCurveValue(int channel, int bin, int val); + void setCurvePointX(int channel, int point, int x); + void setCurvePointY(int channel, int point, int y); + void setCurveType(int channel, CurveType type); + + void setCurvePoint(int channel, int point, const TQPoint& val); + void setCurvePoints(int channel, const TQPointArray& vals); + + int getCurveValue(int channel, int bin); + int getCurvePointX(int channel, int point); + int getCurvePointY(int channel, int point); + int getCurveType(int channel); + + TQPoint getCurvePoint(int channel, int point); + TQPointArray getCurvePoints(int channel); + + // Methods for to save/load the curves values to/from a Gimp curves text file. + + bool saveCurvesToGimpCurvesFile(const KURL& fileUrl); + bool loadCurvesFromGimpCurvesFile(const KURL& fileUrl); + +private: + + void curvesPlotCurve(int channel, int p1, int p2, int p3, int p4); + void curvesCRCompose(CRMatrix a, CRMatrix b, CRMatrix ab); + +private: + + ImageCurvesPriv* d; +}; + +} // NameSpace Digikam + +#endif /* IMAGECURVES_H */ diff --git a/src/libs/dialogs/Makefile.am b/src/libs/dialogs/Makefile.am new file mode 100644 index 00000000..19d50423 --- /dev/null +++ b/src/libs/dialogs/Makefile.am @@ -0,0 +1,33 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libdialog.la libdialogshowfoto.la + +# Dialogs collection used by Showfoto. + +libdialogshowfoto_la_SOURCES = iccprofileinfodlg.cpp imagedialog.cpp rawcameradlg.cpp + +libdialogshowfoto_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +# Dialogs collection used by digiKam. + +libdialog_la_SOURCES = deletedialogbase.ui imagedialog.cpp rawcameradlg.cpp \ + iccprofileinfodlg.cpp deletedialog.cpp dprogressdlg.cpp + +libdialog_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +INCLUDES = -I$(top_srcdir)/src/digikam \ + -I$(top_srcdir)/src/libs/dimg \ + -I$(top_srcdir)/src/libs/dmetadata \ + -I$(top_srcdir)/src/libs/thumbbar \ + -I$(top_srcdir)/src/libs/dimg/filters \ + -I$(top_srcdir)/src/libs/widgets/common \ + -I$(top_srcdir)/src/libs/widgets/metadata \ + -I$(top_srcdir)/src/libs/widgets/iccprofiles \ + -I$(top_srcdir)/src/libs/widgets/imageplugins \ + -I$(top_srcdir)/src/utilities/imageeditor/canvas \ + $(LIBKDCRAW_CFLAGS) \ + $(LIBKEXIV2_CFLAGS) \ + $(all_includes) + +digikaminclude_HEADERS = iccprofileinfodlg.h dprogressdlg.h imagedialog.h rawcameradlg.h +digikamincludedir = $(includedir)/digikam diff --git a/src/libs/dialogs/ctrlpaneldlg.cpp b/src/libs/dialogs/ctrlpaneldlg.cpp new file mode 100644 index 00000000..450d380f --- /dev/null +++ b/src/libs/dialogs/ctrlpaneldlg.cpp @@ -0,0 +1,445 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-05-07 + * Description : A threaded filter control panel dialog for + * image editor plugins using DImg + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dimgthreadedfilter.h" +#include "dimginterface.h" +#include "ctrlpaneldlg.h" +#include "ctrlpaneldlg.moc" + +namespace Digikam +{ + +class CtrlPanelDlgPriv +{ +public: + + enum RunningMode + { + NoneRendering=0, + PreviewRendering, + FinalRendering + }; + + CtrlPanelDlgPriv() + { + parent = 0; + timer = 0; + aboutData = 0; + progressBar = true; + tryAction = false; + currentRenderingMode = NoneRendering; + } + + bool tryAction; + bool progressBar; + + int currentRenderingMode; + + TQWidget *parent; + + TQTimer *timer; + + TQString name; + + TDEAboutData *aboutData; +}; + +CtrlPanelDlg::CtrlPanelDlg(TQWidget* parent, TQString title, TQString name, + bool loadFileSettings, bool tryAction, bool progressBar, + int separateViewMode, TQFrame* bannerFrame) + : KDialogBase(Plain, 0, + Help|Default|User1|User2|User3|Try|Ok|Cancel, Ok, + parent, 0, true, true, + i18n("&Abort"), + i18n("&Save As..."), + i18n("&Load...")) +{ + kapp->setOverrideCursor( KCursor::waitCursor() ); + setCaption(DImgInterface::defaultInterface()->getImageFileName() + TQString(" - ") + title); + + d = new CtrlPanelDlgPriv; + d->parent = parent; + d->name = name; + d->tryAction = tryAction; + d->progressBar = progressBar; + m_threadedFilter = 0; + TQString whatsThis; + + setButtonWhatsThis ( Default, i18n("

Reset all filter parameters to their default values.") ); + setButtonWhatsThis ( User1, i18n("

Abort the current image rendering.") ); + setButtonWhatsThis ( User3, i18n("

Load all filter parameters from settings text file.") ); + setButtonWhatsThis ( User2, i18n("

Save all filter parameters to settings text file.") ); + showButton(User2, loadFileSettings); + showButton(User3, loadFileSettings); + showButton(Try, tryAction); + + // disable Abort button on startup + enableButton(User1, false); + + resize(configDialogSize(name + TQString(" Tool Dialog"))); + TQVBoxLayout *topLayout = new TQVBoxLayout( plainPage(), 0, spacingHint()); + + // ------------------------------------------------------------- + + if (bannerFrame) + { + bannerFrame->reparent( plainPage(), TQPoint(0, 0) ); + topLayout->addWidget(bannerFrame); + } + + // ------------------------------------------------------------- + + m_imagePreviewWidget = new ImagePannelWidget(470, 350, name + TQString(" Tool Dialog"), + plainPage(), separateViewMode); + topLayout->addWidget(m_imagePreviewWidget); + + // ------------------------------------------------------------- + + TQTimer::singleShot(0, this, TQ_SLOT(slotInit())); + kapp->restoreOverrideCursor(); +} + +CtrlPanelDlg::~CtrlPanelDlg() +{ + if (d->aboutData) + delete d->aboutData; + + if (d->timer) + delete d->timer; + + if (m_threadedFilter) + delete m_threadedFilter; + + delete d; +} + +void CtrlPanelDlg::slotInit() +{ + // Reset values to defaults. + TQTimer::singleShot(0, this, TQ_SLOT(readUserSettings())); + + if (!d->tryAction) + { + connect(m_imagePreviewWidget, TQ_SIGNAL(signalOriginalClipFocusChanged()), + this, TQ_SLOT(slotFocusChanged())); + } + else + { + connect(m_imagePreviewWidget, TQ_SIGNAL(signalResized()), + this, TQ_SLOT(slotFocusChanged())); + } +} + +void CtrlPanelDlg::setAboutData(TDEAboutData *about) +{ + d->aboutData = about; + TQPushButton *helpButton = actionButton( Help ); + KHelpMenu* helpMenu = new KHelpMenu(this, d->aboutData, false); + helpMenu->menu()->removeItemAt(0); + helpMenu->menu()->insertItem(i18n("digiKam Handbook"), this, TQ_SLOT(slotHelp()), 0, -1, 0); + helpButton->setPopup( helpMenu->menu() ); +} + +void CtrlPanelDlg::abortPreview() +{ + d->currentRenderingMode = CtrlPanelDlgPriv::NoneRendering; + m_imagePreviewWidget->setProgress(0); + m_imagePreviewWidget->setPreviewImageWaitCursor(false); + m_imagePreviewWidget->setEnable(true); + m_imagePreviewWidget->setProgressVisible(false); + enableButton(Ok, true); + enableButton(User1, false); + enableButton(User2, true); + enableButton(User3, true); + enableButton(Try, true); + enableButton(Default, true); + renderingFinished(); +} + +void CtrlPanelDlg::slotTry() +{ + slotEffect(); +} + +void CtrlPanelDlg::slotUser1() +{ + if (d->currentRenderingMode != CtrlPanelDlgPriv::NoneRendering) + if (m_threadedFilter) + m_threadedFilter->stopComputation(); +} + +void CtrlPanelDlg::slotDefault() +{ + resetValues(); + slotEffect(); +} + +void CtrlPanelDlg::slotCancel() +{ + if (d->currentRenderingMode != CtrlPanelDlgPriv::NoneRendering) + { + if (m_threadedFilter) + m_threadedFilter->stopComputation(); + + kapp->restoreOverrideCursor(); + } + + saveDialogSize(d->name + TQString(" Tool Dialog")); + done(Cancel); +} + +void CtrlPanelDlg::closeEvent(TQCloseEvent *e) +{ + if (d->currentRenderingMode != CtrlPanelDlgPriv::NoneRendering) + { + if (m_threadedFilter) + m_threadedFilter->stopComputation(); + + kapp->restoreOverrideCursor(); + } + + saveDialogSize(d->name + TQString(" Tool Dialog")); + e->accept(); +} + +void CtrlPanelDlg::slotFocusChanged(void) +{ + if (d->currentRenderingMode == CtrlPanelDlgPriv::FinalRendering) + { + m_imagePreviewWidget->update(); + return; + } + else if (d->currentRenderingMode == CtrlPanelDlgPriv::PreviewRendering) + { + if (m_threadedFilter) + m_threadedFilter->stopComputation(); + } + + TQTimer::singleShot(0, this, TQ_SLOT(slotEffect())); +} + +void CtrlPanelDlg::slotHelp() +{ + // If setAboutData() is called by plugin, well DigikamImagePlugins help is lauched, + // else digiKam help. In this case, setHelp() method must be used to set anchor and handbook name. + + if (d->aboutData) + TDEApplication::kApplication()->invokeHelp(d->name, "digikam"); + else + KDialogBase::slotHelp(); +} + +void CtrlPanelDlg::slotTimer() +{ + if (d->timer) + { + d->timer->stop(); + delete d->timer; + } + + d->timer = new TQTimer( this ); + connect( d->timer, TQ_SIGNAL(timeout()), + this, TQ_SLOT(slotEffect()) ); + d->timer->start(500, true); +} + +void CtrlPanelDlg::slotEffect() +{ + // Computation already in process. + if (d->currentRenderingMode != CtrlPanelDlgPriv::NoneRendering) + return; + + d->currentRenderingMode = CtrlPanelDlgPriv::PreviewRendering; + DDebug() << "Preview " << d->name << " started..." << endl; + + m_imagePreviewWidget->setEnable(false); + m_imagePreviewWidget->setProgressVisible(true); + enableButton(Ok, false); + enableButton(User1, true); + enableButton(User2, false); + enableButton(User3, false); + enableButton(Try, false); + enableButton(Default, false); + m_imagePreviewWidget->setPreviewImageWaitCursor(true); + m_imagePreviewWidget->setProgress(0); + + if (m_threadedFilter) + { + delete m_threadedFilter; + m_threadedFilter = 0; + } + + prepareEffect(); +} + +void CtrlPanelDlg::slotOk() +{ + d->currentRenderingMode = CtrlPanelDlgPriv::FinalRendering; + DDebug() << "Final " << d->name << " started..." << endl; + saveDialogSize(d->name + TQString(" Tool Dialog")); + writeUserSettings(); + + m_imagePreviewWidget->setEnable(false); + m_imagePreviewWidget->setProgressVisible(true); + enableButton(Ok, false); + enableButton(User1, false); + enableButton(User2, false); + enableButton(User3, false); + enableButton(Try, false); + enableButton(Default, false); + kapp->setOverrideCursor( KCursor::waitCursor() ); + m_imagePreviewWidget->setProgress(0); + + if (m_threadedFilter) + { + delete m_threadedFilter; + m_threadedFilter = 0; + } + + prepareFinal(); +} + +void CtrlPanelDlg::customEvent(TQCustomEvent *event) +{ + if (!event) return; + + DImgThreadedFilter::EventData *ed = (DImgThreadedFilter::EventData*) event->data(); + + if (!ed) return; + + if (ed->starting) // Computation in progress ! + { + m_imagePreviewWidget->setProgress(ed->progress); + } + else + { + if (ed->success) // Computation Completed ! + { + switch (d->currentRenderingMode) + { + case CtrlPanelDlgPriv::PreviewRendering: + { + DDebug() << "Preview " << d->name << " completed..." << endl; + putPreviewData(); + abortPreview(); + break; + } + + case CtrlPanelDlgPriv::FinalRendering: + { + DDebug() << "Final" << d->name << " completed..." << endl; + putFinalData(); + kapp->restoreOverrideCursor(); + accept(); + break; + } + } + } + else // Computation Failed ! + { + switch (d->currentRenderingMode) + { + case CtrlPanelDlgPriv::PreviewRendering: + { + DDebug() << "Preview " << d->name << " failed..." << endl; + // abortPreview() must be call here for set progress bar to 0 properly. + abortPreview(); + break; + } + + case CtrlPanelDlgPriv::FinalRendering: + break; + } + } + } + + delete ed; +} + +// Backport KDialog::keyPressEvent() implementation from KDELibs to ignore Enter/Return Key events +// to prevent any conflicts between dialog keys events and SpinBox keys events. + +void CtrlPanelDlg::keyPressEvent(TQKeyEvent *e) +{ + if ( e->state() == 0 ) + { + switch ( e->key() ) + { + case Key_Escape: + e->accept(); + reject(); + break; + case Key_Enter: + case Key_Return: + e->ignore(); + break; + default: + e->ignore(); + return; + } + } + else + { + // accept the dialog when Ctrl-Return is pressed + if ( e->state() == ControlButton && + (e->key() == Key_Return || e->key() == Key_Enter) ) + { + e->accept(); + accept(); + } + else + { + e->ignore(); + } + } +} + +} // NameSpace Digikam + diff --git a/src/libs/dialogs/ctrlpaneldlg.h b/src/libs/dialogs/ctrlpaneldlg.h new file mode 100644 index 00000000..eb60877e --- /dev/null +++ b/src/libs/dialogs/ctrlpaneldlg.h @@ -0,0 +1,110 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-05-07 + * Description : A threaded filter control panel dialog for + * image editor plugins using DImg + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef CTRLPANELDLG_H +#define CTRLPANELDLG_H + +// TQt includes + +#include + +// KDE include. + +#include + +// Local includes + +#include "imagepannelwidget.h" +#include "digikam_export.h" + +class TQFrame; + +namespace Digikam +{ + +class CtrlPanelDlgPriv; +class DImgThreadedFilter; + +class DIGIKAM_EXPORT CtrlPanelDlg : public KDialogBase +{ + TQ_OBJECT + + +public: + + CtrlPanelDlg(TQWidget* parent, TQString title, TQString name, + bool loadFileSettings=false, bool tryAction=false, bool progressBar=true, + int separateViewMode=ImagePannelWidget::SeparateViewAll, + TQFrame* bannerFrame=0); + ~CtrlPanelDlg(); + + void setAboutData(TDEAboutData *about); + +public: + + ImagePannelWidget *m_imagePreviewWidget; + + DImgThreadedFilter *m_threadedFilter; + +public slots: + + void slotTimer(); + void slotEffect(); + void slotOk(); + void slotTry(); + +private slots: + + virtual void slotDefault(); + virtual void slotCancel(); + virtual void slotUser1(); + virtual void slotInit(); + virtual void readUserSettings(void){ slotDefault(); }; + + void slotHelp(); + void slotFocusChanged(void); + +protected: + + void closeEvent(TQCloseEvent *e); + void customEvent(TQCustomEvent *event); + void abortPreview(void); + void keyPressEvent(TQKeyEvent *e); + + virtual void writeUserSettings(void){}; + virtual void resetValues(void){}; + virtual void prepareEffect(void){}; + virtual void prepareFinal(void){}; + virtual void putPreviewData(void){}; + virtual void putFinalData(void){}; + virtual void renderingFinished(void){}; + +private: + + CtrlPanelDlgPriv* d; +}; + +} // NameSpace Digikam + +#endif /* CTRLPANELDLG_H */ diff --git a/src/libs/dialogs/deletedialog.cpp b/src/libs/dialogs/deletedialog.cpp new file mode 100644 index 00000000..1db9b9c4 --- /dev/null +++ b/src/libs/dialogs/deletedialog.cpp @@ -0,0 +1,309 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-05-07 + * Description : a dialog to delete item. + * + * Copyright (C) 2004 by Michael Pyne + * Copyright (C) 2006 by Ian Monroe + * Copyright (C) 2006-2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "deletedialog.h" +#include "albumsettings.h" +#include "deletedialog.moc" + +namespace Digikam +{ + +////////////////////////////////////////////////////////////////////////////// +// DeleteWidget implementation +////////////////////////////////////////////////////////////////////////////// + +DeleteWidget::DeleteWidget(TQWidget *parent, const char *name) + : DeleteDialogBase(parent, name), + m_listMode(DeleteDialogMode::Files), + m_deleteMode(DeleteDialogMode::UseTrash) +{ + ddCheckBoxStack->raiseWidget(ddShouldDeletePage); + + bool deleteInstead = !AlbumSettings::instance()->getUseTrash(); + slotShouldDelete(deleteInstead); + ddShouldDelete->setChecked(deleteInstead); +} + +void DeleteWidget::setFiles(const KURL::List &files) +{ + ddFileList->clear(); + for( KURL::List::ConstIterator it = files.begin(); it != files.end(); it++) + { + if( (*it).isLocalFile() ) //path is nil for non-local + ddFileList->insertItem( (*it).path() ); + else if ( (*it).protocol() == "digikamalbums") + ddFileList->insertItem( (*it).path() ); + else + ddFileList->insertItem( (*it).prettyURL() ); + } + updateText(); +} + +void DeleteWidget::slotShouldDelete(bool shouldDelete) +{ + setDeleteMode(shouldDelete ? DeleteDialogMode::DeletePermanently : DeleteDialogMode::UseTrash); +} + +void DeleteWidget::setDeleteMode(DeleteDialogMode::DeleteMode deleteMode) +{ + m_deleteMode = deleteMode; + updateText(); +} + +void DeleteWidget::setListMode(DeleteDialogMode::ListMode listMode) +{ + m_listMode = listMode; + updateText(); +} + +void DeleteWidget::updateText() +{ + switch (m_listMode) + { + case DeleteDialogMode::Files: + + // Delete files + + if (m_deleteMode == DeleteDialogMode::DeletePermanently) + { + ddDeleteText->setText(i18n("These items will be permanently " + "deleted from your hard disk.")); + ddWarningIcon->setPixmap(TDEGlobal::iconLoader()->loadIcon("messagebox_warning", + TDEIcon::Desktop, TDEIcon::SizeLarge)); + } + else + { + ddDeleteText->setText(i18n("These items will be moved to Trash.")); + ddWarningIcon->setPixmap(TDEGlobal::iconLoader()->loadIcon("trashcan_full", + TDEIcon::Desktop, TDEIcon::SizeLarge)); + } + ddNumFiles->setText(i18n("1 file selected.", "%n files selected.", ddFileList->count())); + break; + + case DeleteDialogMode::Albums: + + // Delete albums = folders + + if (m_deleteMode == DeleteDialogMode::DeletePermanently) + { + ddDeleteText->setText(i18n("These albums will be permanently " + "deleted from your hard disk.")); + ddWarningIcon->setPixmap(TDEGlobal::iconLoader()->loadIcon("messagebox_warning", + TDEIcon::Desktop, TDEIcon::SizeLarge)); + } + else + { + ddDeleteText->setText(i18n("These albums will be moved to Trash.")); + ddWarningIcon->setPixmap(TDEGlobal::iconLoader()->loadIcon("trashcan_full", + TDEIcon::Desktop, TDEIcon::SizeLarge)); + } + ddNumFiles->setText(i18n("1 album selected.", "%n albums selected.", ddFileList->count())); + break; + + case DeleteDialogMode::Subalbums: + + // As above, but display additional warning + + if (m_deleteMode == DeleteDialogMode::DeletePermanently) + { + ddDeleteText->setText(i18n("These albums will be permanently " + "deleted from your hard disk.
" + "Note that all subalbums " + "are included in this list and will " + "be deleted permanently as well.
")); + ddWarningIcon->setPixmap(TDEGlobal::iconLoader()->loadIcon("messagebox_warning", + TDEIcon::Desktop, TDEIcon::SizeLarge)); + } + else + { + ddDeleteText->setText(i18n("These albums will be moved to Trash.
" + "Note that all subalbums " + "are included in this list and will " + "be moved to Trash as well.
")); + ddWarningIcon->setPixmap(TDEGlobal::iconLoader()->loadIcon("trashcan_full", + TDEIcon::Desktop, TDEIcon::SizeLarge)); + } + ddNumFiles->setText(i18n("1 album selected.", "%n albums selected.", ddFileList->count())); + break; + + } +} + +////////////////////////////////////////////////////////////////////////////// +// DeleteDialog implementation +////////////////////////////////////////////////////////////////////////////// + +DeleteDialog::DeleteDialog(TQWidget *parent, const char *name) + : KDialogBase(Swallow, WStyle_DialogBorder, parent, name, + true, // modal + i18n("About to delete selected files"), // caption + Ok | Cancel, // available buttons + Ok, // default button + true // use separator between buttons and the main widget + ), + m_saveShouldDeleteUserPreference(true), + m_saveDoNotShowAgain(false), + m_trashGuiItem(i18n("&Move to Trash"), "trashcan_full") +{ + m_widget = new DeleteWidget(this, "delete_dialog_widget"); + setMainWidget(m_widget); + + m_widget->setMinimumSize(400, 300); + setMinimumSize(410, 326); + adjustSize(); + + slotShouldDelete(shouldDelete()); + connect(m_widget->ddShouldDelete, TQ_SIGNAL(toggled(bool)), + this, TQ_SLOT(slotShouldDelete(bool))); + + actionButton(Ok)->setFocus(); +} + +bool DeleteDialog::confirmDeleteList(const KURL::List& condemnedFiles, + DeleteDialogMode::ListMode listMode, + DeleteDialogMode::DeleteMode deleteMode) +{ + setURLs(condemnedFiles); + presetDeleteMode(deleteMode); + setListMode(listMode); + + if (deleteMode == DeleteDialogMode::NoChoiceTrash) + { + if (!AlbumSettings::instance()->getShowTrashDeleteDialog()) + return true; + } + return exec() == TQDialog::Accepted; +} + +void DeleteDialog::setURLs(const KURL::List &files) +{ + m_widget->setFiles(files); +} + +void DeleteDialog::accept() +{ + // Save user's preference + AlbumSettings *settings = AlbumSettings::instance(); + + if (m_saveShouldDeleteUserPreference) + { + settings->setUseTrash(!shouldDelete()); + } + if (m_saveDoNotShowAgain) + { + settings->setShowTrashDeleteDialog(!m_widget->ddDoNotShowAgain->isChecked()); + } + + settings->saveSettings(); + + KDialogBase::accept(); +} + +void DeleteDialog::slotShouldDelete(bool shouldDelete) +{ + // This is called once from constructor, and then when the user changed the checkbox state. + // In that case, save the user's preference. + m_saveShouldDeleteUserPreference = true; + setButtonGuiItem(Ok, shouldDelete ? KStdGuiItem::del() : m_trashGuiItem); +} + +void DeleteDialog::presetDeleteMode(DeleteDialogMode::DeleteMode mode) +{ + switch (mode) + { + case DeleteDialogMode::NoChoiceTrash: + { + // access the widget directly, signals will be fired to DeleteDialog and DeleteWidget + m_widget->ddShouldDelete->setChecked(false); + m_widget->ddCheckBoxStack->raiseWidget(m_widget->ddDoNotShowAgainPage); + m_saveDoNotShowAgain = true; + break; + } + case DeleteDialogMode::NoChoiceDeletePermanently: + { + m_widget->ddShouldDelete->setChecked(true); + m_widget->ddCheckBoxStack->hide(); + break; + } + case DeleteDialogMode::UserPreference: + { + break; + } + case DeleteDialogMode::UseTrash: + case DeleteDialogMode::DeletePermanently: + { + // toggles signals which do the rest + m_widget->ddShouldDelete->setChecked(mode == DeleteDialogMode::DeletePermanently); + + // the preference set by this preset method will be ignored + // for the next DeleteDialog instance and not stored as user preference. + // Only if the user once changes this value, it will be taken as user preference. + m_saveShouldDeleteUserPreference = false; + break; + } + } +} + +void DeleteDialog::setListMode(DeleteDialogMode::ListMode mode) +{ + m_widget->setListMode(mode); + switch (mode) + { + case DeleteDialogMode::Files: + setCaption(i18n("About to delete selected files")); + break; + + case DeleteDialogMode::Albums: + case DeleteDialogMode::Subalbums: + setCaption(i18n("About to delete selected albums")); + break; + } +} + +} // namespace Digikam diff --git a/src/libs/dialogs/deletedialog.h b/src/libs/dialogs/deletedialog.h new file mode 100644 index 00000000..16ab9f89 --- /dev/null +++ b/src/libs/dialogs/deletedialog.h @@ -0,0 +1,140 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-05-07 + * Description : a dialog to delete item. + * + * Copyright (C) 2004 by Michael Pyne + * Copyright (C) 2006 by Ian Monroe + * Copyright (C) 2006-2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef _DELETEDIALOG_H +#define _DELETEDIALOG_H + +// TQt includes. + +#include + +// KDE includes. + +#include +#include + +// Local includes. + +#include "digikam_export.h" +#include "deletedialogbase.h" + +class TQStringList; +class TDEListBox; +class KGuiItem; +class TQLabel; +class TQWidgetStack; + +namespace Digikam +{ + +namespace DeleteDialogMode +{ + enum ListMode + { + Files, + Albums, + Subalbums + }; + + enum DeleteMode + { + NoChoiceTrash, // "Do not show again" checkbox, does not show if config entry is set + NoChoiceDeletePermanently, // No checkbox + UserPreference, // Checkbox to toggle trash/permanent, preset to user's last preference + UseTrash, // same beckbox as above, preset to trash + DeletePermanently // same checkbox as above, preset to permanent + }; +} + +class DeleteWidget : public DeleteDialogBase +{ + TQ_OBJECT + + +public: + + DeleteWidget(TQWidget *parent = 0, const char *name = 0); + + void setFiles(const KURL::List &files); + void setListMode(DeleteDialogMode::ListMode mode); + void setDeleteMode(DeleteDialogMode::DeleteMode deleteMode); + +protected slots: + + void slotShouldDelete(bool shouldDelete); + +protected: + + void updateText(); + DeleteDialogMode::ListMode m_listMode; + DeleteDialogMode::DeleteMode m_deleteMode; +}; + +class DIGIKAM_EXPORT DeleteDialog : public KDialogBase +{ + TQ_OBJECT + + +public: + + enum Mode + { + ModeFiles, + ModeAlbums, + ModeSubalbums + }; + +public: + + DeleteDialog(TQWidget *parent, const char *name = "delete_dialog"); + + bool confirmDeleteList(const KURL::List &condemnedURLs, + DeleteDialogMode::ListMode listMode, + DeleteDialogMode::DeleteMode deleteMode); + bool shouldDelete() const { return m_widget->ddShouldDelete->isChecked(); } + + void setURLs(const KURL::List &files); + void presetDeleteMode(DeleteDialogMode::DeleteMode mode); + void setListMode(DeleteDialogMode::ListMode mode); + +protected slots: + + virtual void accept(); + void slotShouldDelete(bool shouldDelete); + +private: + + bool m_saveShouldDeleteUserPreference; + bool m_saveDoNotShowAgain; + + KGuiItem m_trashGuiItem; + + DeleteWidget *m_widget; +}; + +} // namespace Digikam + +#endif // _DELETEDIALOG_H + diff --git a/src/libs/dialogs/deletedialogbase.ui b/src/libs/dialogs/deletedialogbase.ui new file mode 100644 index 00000000..8527a903 --- /dev/null +++ b/src/libs/dialogs/deletedialogbase.ui @@ -0,0 +1,188 @@ + + DeleteDialogBase + + + DeleteDialogBase + + + + 0 + 0 + 517 + 162 + + + + DeleteDialogBase + + + + 0 + 0 + 542 + 374 + + + + + 420 + 320 + + + + + unnamed + + + 0 + + + + layout4 + + + + unnamed + + + + ddWarningIcon + + + + 4 + 4 + 0 + 0 + + + + Icon Placeholder, not in GUI + + + + + layout3 + + + + unnamed + + + + ddDeleteText + + + Deletion method placeholder, never shown to user. + + + WordBreak|AlignCenter + + + + + + + + + ddFileList + + + NoSelection + + + List of files that are about to be deleted. + + + This is the list of items that are about to be deleted. + + + + + ddNumFiles + + + Placeholder for number of files, not in GUI + + + AlignVCenter|AlignRight + + + + + ddCheckBoxStack + + + + ddShouldDeletePage + + + 0 + + + + unnamed + + + 0 + + + + ddShouldDelete + + + &Delete files instead of moving them to the trash + + + If checked, files will be permanently removed instead of being placed in the Trash Bin + + + <qt><p>If this box is checked, files will be <b>permanently removed</b> instead of being placed in the Trash Bin.</p> + + <p><em>Use this option with caution</em>: most filesystems are unable to undelete deleted files reliably.</p></qt> + + + + + + + ddDoNotShowAgainPage + + + 1 + + + + unnamed + + + 0 + + + + ddDoNotShowAgain + + + Do not &ask again + + + If checked, this dialog will no longer be shown, and files will be directly moved to the Trash Bin + + + <qt><p>If this box is checked, this dialog will no longer be shown, and files will be directly moved to the Trash Bin</p> + + + + + + + + + + + + + + tdelistbox.h + + diff --git a/src/libs/dialogs/dprogressdlg.cpp b/src/libs/dialogs/dprogressdlg.cpp new file mode 100644 index 00000000..d4b0dc29 --- /dev/null +++ b/src/libs/dialogs/dprogressdlg.cpp @@ -0,0 +1,224 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-30-08 + * Description : a progress dialog for digiKam + * + * Copyright (C) 2006-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dprogressdlg.h" +#include "dprogressdlg.moc" + +namespace Digikam +{ + +class DProgressDlgPriv +{ +public: + + DProgressDlgPriv() + { + progress = 0; + actionsList = 0; + logo = 0; + title = 0; + label = 0; + allowCancel = true; + cancelled = false; + } + + bool allowCancel; + bool cancelled; + + TQLabel *logo; + TQLabel *title; + TQLabel *label; + + TQListView *actionsList; + + KProgress *progress; +}; + +DProgressDlg::DProgressDlg(TQWidget *parent, const TQString &caption) + : KDialogBase(parent, 0, true, caption, Cancel) +{ + d = new DProgressDlgPriv; + + TQFrame *page = makeMainWidget(); + TQGridLayout* grid = new TQGridLayout(page, 1, 1, 0, spacingHint()); + TQVBoxLayout *vlay = new TQVBoxLayout(); + d->actionsList = new TQListView(page); + d->label = new TQLabel(page); + d->title = new TQLabel(page); + d->logo = new TQLabel(page); + d->progress = new KProgress(page); + vlay->addWidget(d->logo); + vlay->addWidget(d->progress); + vlay->addWidget(d->title); + vlay->addStretch(); + + TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader(); + d->logo->setPixmap(iconLoader->loadIcon("digikam", TDEIcon::NoGroup, 128, TDEIcon::DefaultState, 0, true)); + + d->actionsList->addColumn("Thumb"); // no i18n here: hiden column + d->actionsList->addColumn("Status"); // no i18n here: hiden column + d->actionsList->setSorting(-1); + d->actionsList->setItemMargin(1); + d->actionsList->setSelectionMode(TQListView::NoSelection); + d->actionsList->header()->hide(); + d->actionsList->setResizeMode(TQListView::LastColumn); + + grid->addMultiCellLayout(vlay, 0, 1, 0, 0); + grid->addMultiCellWidget(d->label, 0, 0, 1, 1); + grid->addMultiCellWidget(d->actionsList, 1, 1, 1, 1); + grid->setRowStretch(1, 10); + grid->setColStretch(1, 10); +} + +DProgressDlg::~DProgressDlg() +{ + delete d; +} + +void DProgressDlg::slotCancel() +{ + d->cancelled = true; + + if (d->allowCancel) + { + KDialogBase::slotCancel(); + } +} + +void DProgressDlg::setButtonText(const TQString &text) +{ + KDialogBase::setButtonText(Cancel, text); +} + +void DProgressDlg::addedAction(const TQPixmap& pix, const TQString &text) +{ + TQImage img; + TQListViewItem *item = new TQListViewItem(d->actionsList, + d->actionsList->lastItem(), TQString(), text); + + if (pix.isNull()) + { + TQString dir = TDEGlobal::dirs()->findResourceDir("digikam_imagebroken", + "image-broken.png"); + dir = dir + "/image-broken.png"; + TQPixmap pixbi(dir); + img = pixbi.convertToImage().scale(32, 32, TQImage::ScaleMin); + } + else + { + img = pix.convertToImage().scale(32, 32, TQImage::ScaleMin); + } + + TQPixmap pixmap(img); + item->setPixmap(0, pixmap); + d->actionsList->ensureItemVisible(item); +} + +void DProgressDlg::reset() +{ + d->actionsList->clear(); + d->progress->setValue(0); +} + +void DProgressDlg::setTotalSteps(int total) +{ + d->progress->setTotalSteps(total); +} + +void DProgressDlg::setValue(int value) +{ + d->progress->setValue(value); +} + +void DProgressDlg::advance(int value) +{ + d->progress->advance(value); +} + +void DProgressDlg::setLabel(const TQString &text) +{ + d->label->setText(text); +} + +void DProgressDlg::setTitle(const TQString &text) +{ + d->title->setText(text); +} + +void DProgressDlg::showCancelButton(bool show) +{ + showButtonCancel(show); +} + +void DProgressDlg::setAllowCancel(bool allowCancel) +{ + d->allowCancel = allowCancel; + showCancelButton(allowCancel); +} + +bool DProgressDlg::allowCancel() const +{ + return d->allowCancel; +} + +bool DProgressDlg::wasCancelled() const +{ + return d->cancelled; +} + +KProgress *DProgressDlg::progressBar() const +{ + return d->progress; +} + +void DProgressDlg::setActionListVSBarVisible(bool visible) +{ + if (!visible) + d->actionsList->setVScrollBarMode(TQScrollView::AlwaysOff); + else + d->actionsList->setVScrollBarMode(TQScrollView::Auto); +} + +} // NameSpace Digikam diff --git a/src/libs/dialogs/dprogressdlg.h b/src/libs/dialogs/dprogressdlg.h new file mode 100644 index 00000000..d75f509d --- /dev/null +++ b/src/libs/dialogs/dprogressdlg.h @@ -0,0 +1,79 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-30-08 + * Description : a progress dialog for digiKam + * + * Copyright (C) 2006-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DPROGRESSDLG_H +#define DPROGRESSDLG_H + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +class KProgress; + +namespace Digikam +{ + +class DProgressDlgPriv; + +class DIGIKAM_EXPORT DProgressDlg : public KDialogBase +{ +TQ_OBJECT + + + public: + + DProgressDlg(TQWidget *parent=0, const TQString &caption=TQString()); + ~DProgressDlg(); + + void setButtonText(const TQString &text); + void addedAction(const TQPixmap& pix, const TQString &text); + void reset(); + void setTotalSteps(int total); + void setValue(int value); + void advance(int value); + void setLabel(const TQString &text); + void setTitle(const TQString &text); + void setActionListVSBarVisible(bool visible); + void showCancelButton(bool show); + void setAllowCancel(bool allowCancel); + bool wasCancelled() const; + bool allowCancel() const; + + KProgress *progressBar() const; + + protected slots: + + void slotCancel(); + + private: + + DProgressDlgPriv* d; +}; + +} // NameSpace Digikam + +#endif // DPROGRESSDLG_H diff --git a/src/libs/dialogs/iccprofileinfodlg.cpp b/src/libs/dialogs/iccprofileinfodlg.cpp new file mode 100644 index 00000000..04f30c0c --- /dev/null +++ b/src/libs/dialogs/iccprofileinfodlg.cpp @@ -0,0 +1,60 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-02-16 + * Description : a dialog to display icc profile information. + * + * Copyright (C) 2006-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// KDE includes. + +#include + +// Local includes. + +#include "ddebug.h" +#include "iccprofilewidget.h" +#include "iccprofileinfodlg.h" + +namespace Digikam +{ + +ICCProfileInfoDlg::ICCProfileInfoDlg(TQWidget* parent, const TQString& profilePath, + const TQByteArray& profileData) + : KDialogBase(parent, 0, true, i18n("Color Profile Info"), + Help|Ok, Ok, true) +{ + setHelp("iccprofile.anchor", "digikam"); + setCaption(profilePath); + + ICCProfileWidget *profileWidget = new ICCProfileWidget(this, 0, 340, 256); + + if (profileData.isEmpty()) + profileWidget->loadFromURL(KURL(profilePath)); + else + profileWidget->loadFromData(profilePath, profileData); + + setMainWidget(profileWidget); +} + +ICCProfileInfoDlg::~ICCProfileInfoDlg() +{ +} + +} // NameSpace Digikam + diff --git a/src/libs/dialogs/iccprofileinfodlg.h b/src/libs/dialogs/iccprofileinfodlg.h new file mode 100644 index 00000000..abcc1ed8 --- /dev/null +++ b/src/libs/dialogs/iccprofileinfodlg.h @@ -0,0 +1,58 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-02-16 + * Description : a dialog to display ICC profile information. + * + * Copyright (C) 2006-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef ICCPROFILEINFODLG_H +#define ICCPROFILEINFODLG_H + +// TQt includes. + +#include + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +class TQWidget; + +namespace Digikam +{ + +class ICCProfileInfoDlgPriv; + +class DIGIKAM_EXPORT ICCProfileInfoDlg : public KDialogBase +{ + +public: + + ICCProfileInfoDlg(TQWidget *parent, const TQString& profilePath, const TQByteArray& profileData=TQByteArray()); + ~ICCProfileInfoDlg(); + +}; + +} // Namespace Digikam + +#endif /* ICCPROFILEINFODLG_H */ diff --git a/src/libs/dialogs/imagedialog.cpp b/src/libs/dialogs/imagedialog.cpp new file mode 100644 index 00000000..d052a2af --- /dev/null +++ b/src/libs/dialogs/imagedialog.cpp @@ -0,0 +1,366 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2008-03-13 + * Description : image files selector dialog. + * + * Copyright (C) 2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include + +// LibKDcraw includes. + +#include +#include + +#if KDCRAW_VERSION < 0x000106 +#include +#endif + +// Local includes. + +#include "ddebug.h" +#include "dmetadata.h" +#include "thumbnailsize.h" +#include "thumbnailjob.h" +#include "imagedialog.h" +#include "imagedialog.moc" + +namespace Digikam +{ + +class ImageDialogPreviewPrivate +{ + +public: + + ImageDialogPreviewPrivate() + { + imageLabel = 0; + infoLabel = 0; + thumbJob = 0; + timer = 0; + } + + TQTimer *timer; + + TQLabel *imageLabel; + TQLabel *infoLabel; + + KURL currentURL; + + DMetadata metaIface; + + TQGuardedPtr thumbJob; +}; + +ImageDialogPreview::ImageDialogPreview(TQWidget *parent) + : KPreviewWidgetBase(parent) +{ + d = new ImageDialogPreviewPrivate; + + TQVBoxLayout *vlay = new TQVBoxLayout(this); + d->imageLabel = new TQLabel(this); + d->imageLabel->setAlignment(TQt::AlignHCenter | TQt::AlignVCenter); + d->imageLabel->setSizePolicy(TQSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding)); + + d->infoLabel = new TQLabel(this); + + vlay->setMargin(0); + vlay->setSpacing(KDialog::spacingHint()); + vlay->addWidget(d->imageLabel); + vlay->addWidget(d->infoLabel); + + setSupportedMimeTypes(KImageIO::mimeTypes()); + + d->timer = new TQTimer(this); + + connect(d->timer, TQ_SIGNAL(timeout()), + this, TQ_SLOT(showPreview()) ); +} + +ImageDialogPreview::~ImageDialogPreview() +{ + if (!d->thumbJob.isNull()) + { + d->thumbJob->kill(); + d->thumbJob = 0; + } + delete d; +} + +TQSize ImageDialogPreview::sizeHint() const +{ + return TQSize(256, 256); +} + +void ImageDialogPreview::resizeEvent(TQResizeEvent *) +{ + d->timer->start(100, true); +} + +void ImageDialogPreview::showPreview() +{ + KURL url(d->currentURL); + clearPreview(); + showPreview(url); +} + +void ImageDialogPreview::showPreview(const KURL& url) +{ + if (!url.isValid()) + { + clearPreview(); + return; + } + + if (url != d->currentURL) + { + clearPreview(); + d->currentURL = url; + + if (!d->thumbJob.isNull()) + { + d->thumbJob->kill(); + d->thumbJob = 0; + } + + d->thumbJob = new ThumbnailJob(url, ThumbnailSize::Huge, true, true); + + connect(d->thumbJob, TQ_SIGNAL(signalThumbnail(const KURL&, const TQPixmap&)), + this, TQ_SLOT(slotGotThumbnail(const KURL&, const TQPixmap&))); + + connect(d->thumbJob, TQ_SIGNAL(signalFailed(const KURL&)), + this, TQ_SLOT(slotFailedThumbnail(const KURL&))); + + d->metaIface.load(d->currentURL.path()); + PhotoInfoContainer info = d->metaIface.getPhotographInformations(); + if (!info.isEmpty()) + { + TQString identify; + TQString make, model, dateTime, aperture, focalLength, exposureTime, sensitivity; + TQString unavailable(i18n("unavailable")); + TQString cellBeg(""); + TQString cellMid(""); + TQString cellEnd(""); + + if (info.make.isEmpty()) make = unavailable; + else make = info.make; + + if (info.model.isEmpty()) model = unavailable; + else model = info.model; + + if (!info.dateTime.isValid()) dateTime = unavailable; + else dateTime = TDEGlobal::locale()->formatDateTime(info.dateTime, true, true); + + if (info.aperture.isEmpty()) aperture = unavailable; + else aperture = info.aperture; + + if (info.focalLength.isEmpty()) focalLength = unavailable; + else focalLength = info.focalLength; + + if (info.exposureTime.isEmpty()) exposureTime = unavailable; + else exposureTime = info.exposureTime; + + if (info.sensitivity.isEmpty()) sensitivity = unavailable; + else sensitivity = i18n("%1 ISO").arg(info.sensitivity); + + identify = ""; + identify += cellBeg + i18n("Make:") + cellMid + make + cellEnd; + identify += cellBeg + i18n("Model:") + cellMid + model + cellEnd; + identify += cellBeg + i18n("Created:") + cellMid + dateTime + cellEnd; + identify += cellBeg + i18n("Aperture:") + cellMid + aperture + cellEnd; + identify += cellBeg + i18n("Focal:") + cellMid + focalLength + cellEnd; + identify += cellBeg + i18n("Exposure:") + cellMid + exposureTime + cellEnd; + identify += cellBeg + i18n("Sensitivity:") + cellMid + sensitivity + cellEnd; + identify += "
"; + + d->infoLabel->setText(identify); + } + else + d->infoLabel->clear(); + } +} + +void ImageDialogPreview::slotGotThumbnail(const KURL& url, const TQPixmap& pix) +{ + if (url == d->currentURL) + { + TQPixmap pixmap; + TQSize s = d->imageLabel->contentsRect().size(); + + if (s.width() < pix.width() || s.height() < pix.height()) + pixmap = pix.convertToImage().smoothScale(s, TQImage::ScaleMin); + else + pixmap = pix; + + d->imageLabel->setPixmap(pixmap); + } +} + +void ImageDialogPreview::slotFailedThumbnail(const KURL& /*url*/) +{ + TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader(); + d->imageLabel->setPixmap(iconLoader->loadIcon("image-x-generic", TDEIcon::NoGroup, 128, + TDEIcon::DefaultState, 0, true)); +} + +void ImageDialogPreview::clearPreview() +{ + d->imageLabel->clear(); + d->infoLabel->clear(); + d->currentURL = KURL(); +} + +// ------------------------------------------------------------------------ + +class ImageDialogPrivate +{ + +public: + + ImageDialogPrivate() + { + singleSelect = false; + } + + bool singleSelect; + + TQString fileformats; + + KURL url; + KURL::List urls; +}; + +ImageDialog::ImageDialog(TQWidget* parent, const KURL &url, bool singleSelect, const TQString& caption) +{ + d = new ImageDialogPrivate; + d->singleSelect = singleSelect; + + TQStringList patternList = TQStringList::split('\n', KImageIO::pattern(KImageIO::Reading)); + + // All Images from list must been always the first entry given by KDE API + TQString allPictures = patternList[0]; + +#if KDCRAW_VERSION < 0x000106 + // Add other files format witch are missing to All Images" type mime provided by KDE and remplace current. + if (KDcrawIface::DcrawBinary::instance()->versionIsRight()) + { + allPictures.insert(allPictures.find("|"), TQString(KDcrawIface::DcrawBinary::instance()->rawFiles()) + TQString(" *.JPE *.TIF")); + patternList.remove(patternList[0]); + patternList.prepend(allPictures); + // Added RAW file formats supported by dcraw program like a type mime. + // Nota: we cannot use here "image/x-raw" type mime from KDE because it uncomplete + // or unavailable (see file #121242 in B.K.O). + patternList.append(i18n("\n%1|Camera RAW files").arg(TQString(KDcrawIface::DcrawBinary::instance()->rawFiles()))); + } +#else + allPictures.insert(allPictures.find("|"), TQString(KDcrawIface::KDcraw::rawFiles()) + TQString(" *.JPE *.TIF")); + patternList.remove(patternList[0]); + patternList.prepend(allPictures); + // Added RAW file formats supported by dcraw program like a type mime. + // Nota: we cannot use here "image/x-raw" type mime from KDE because it uncomplete + // or unavailable (see file #121242 in B.K.O). + patternList.append(i18n("\n%1|Camera RAW files").arg(TQString(KDcrawIface::KDcraw::rawFiles()))); +#endif + + d->fileformats = patternList.join("\n"); + + DDebug() << "fileformats=" << d->fileformats << endl; + + KFileDialog dlg(url.path(), d->fileformats, parent, "imageFileOpenDialog", false); + ImageDialogPreview *preview = new ImageDialogPreview(&dlg); + dlg.setPreviewWidget(preview); + dlg.setOperationMode(KFileDialog::Opening); + + if (d->singleSelect) + { + dlg.setMode(KFile::File); + if (caption.isEmpty()) dlg.setCaption(i18n("Select an Image")); + else dlg.setCaption(caption); + dlg.exec(); + d->url = dlg.selectedURL(); + } + else + { + dlg.setMode(KFile::Files); + if (caption.isEmpty()) dlg.setCaption(i18n("Select Images")); + else dlg.setCaption(caption); + dlg.exec(); + d->urls = dlg.selectedURLs(); + } +} + +ImageDialog::~ImageDialog() +{ + delete d; +} + +bool ImageDialog::singleSelect() const +{ + return d->singleSelect; +} + +TQString ImageDialog::fileformats() const +{ + return d->fileformats; +} + +KURL ImageDialog::url() const +{ + return d->url; +} + +KURL::List ImageDialog::urls() const +{ + return d->urls; +} + +KURL::List ImageDialog::getImageURLs(TQWidget* parent, const KURL& url, const TQString& caption) +{ + ImageDialog dlg(parent, url, false, caption); + if (!dlg.urls().isEmpty()) + return dlg.urls(); + else + return KURL::List(); +} + +KURL ImageDialog::getImageURL(TQWidget* parent, const KURL& url, const TQString& caption) +{ + ImageDialog dlg(parent, url, true, caption); + if (dlg.url() != KURL()) + return dlg.url(); + else + return KURL(); +} + +} // namespace Digikam diff --git a/src/libs/dialogs/imagedialog.h b/src/libs/dialogs/imagedialog.h new file mode 100644 index 00000000..275765cd --- /dev/null +++ b/src/libs/dialogs/imagedialog.h @@ -0,0 +1,100 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2008-03-13 + * Description : image files selector dialog. + * + * Copyright (C) 2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef IMAGEDIALOG_H +#define IMAGEDIALOG_H + +// KDE includes. + +#include +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class ImageDialogPrivate; +class ImageDialogPreviewPrivate; + +class DIGIKAM_EXPORT ImageDialogPreview : public KPreviewWidgetBase +{ + TQ_OBJECT + + +public: + + ImageDialogPreview(TQWidget *parent=0); + ~ImageDialogPreview(); + + TQSize sizeHint() const; + +public slots: + + void showPreview(const KURL &url); + +private slots: + + void showPreview(); + void slotGotThumbnail(const KURL& url, const TQPixmap& pix); + void slotFailedThumbnail(const KURL& url); + void clearPreview(); + +private: + + void resizeEvent(TQResizeEvent *e); + +private: + + class ImageDialogPreviewPrivate *d; +}; + +// ------------------------------------------------------------------------ + +class DIGIKAM_EXPORT ImageDialog +{ + +public: + + ImageDialog(TQWidget* parent, const KURL &url, bool singleSelect=false, const TQString& caption=TQString()); + ~ImageDialog(); + + KURL url() const; + KURL::List urls() const; + + bool singleSelect() const; + TQString fileformats() const; + + static KURL::List getImageURLs(TQWidget* parent, const KURL& url, const TQString& caption=TQString()); + static KURL getImageURL(TQWidget* parent, const KURL& url, const TQString& caption=TQString()); + +private: + + ImageDialogPrivate* d; +}; + +} // namespace Digikam + +#endif /* IMAGEDIALOG_H */ diff --git a/src/libs/dialogs/imagedlgbase.cpp b/src/libs/dialogs/imagedlgbase.cpp new file mode 100644 index 00000000..61666406 --- /dev/null +++ b/src/libs/dialogs/imagedlgbase.cpp @@ -0,0 +1,261 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-07-23 + * Description : simple plugins dialog without threadable + * filter interface. The dialog layout is + * designed to accept custom widgets in + * preview and settings area. + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "sidebar.h" +#include "dimginterface.h" +#include "imagedlgbase.h" +#include "imagedlgbase.moc" + +namespace Digikam +{ + +class ImageDlgBasePriv +{ +public: + + ImageDlgBasePriv() + { + aboutData = 0; + timer = 0; + parent = 0; + mainLayout = 0; + hbox = 0; + settingsSideBar = 0; + splitter = 0; + } + + bool tryAction; + + TQGridLayout *mainLayout; + + TQWidget *parent; + + TQString name; + + TQTimer *timer; + + TQHBox *hbox; + + TQSplitter *splitter; + + TDEAboutData *aboutData; + + Sidebar *settingsSideBar; +}; + +ImageDlgBase::ImageDlgBase(TQWidget* parent, TQString title, TQString name, + bool loadFileSettings, bool tryAction, TQFrame* bannerFrame) + : KDialogBase(Plain, 0, Help|Default|User1|User2|User3|Try|Ok|Cancel, Ok, + parent, 0, true, true, + TQString(), + i18n("&Save As..."), + i18n("&Load...")) +{ + kapp->setOverrideCursor( KCursor::waitCursor() ); + setCaption(DImgInterface::defaultInterface()->getImageFileName() + TQString(" - ") + title); + showButton(User1, false); + + d = new ImageDlgBasePriv; + d->parent = parent; + d->name = name; + d->tryAction = tryAction; + + setButtonWhatsThis ( Default, i18n("

Reset all filter parameters to their default values.") ); + setButtonWhatsThis ( User3, i18n("

Load all filter parameters from settings text file.") ); + setButtonWhatsThis ( User2, i18n("

Save all filter parameters to settings text file.") ); + showButton(User2, loadFileSettings); + showButton(User3, loadFileSettings); + showButton(Try, tryAction); + + resize(configDialogSize(name + TQString(" Tool Dialog"))); + + // ------------------------------------------------------------- + + d->mainLayout = new TQGridLayout( plainPage(), 2, 1); + if (bannerFrame) + { + bannerFrame->reparent( plainPage(), TQPoint(0, 0) ); + d->mainLayout->addMultiCellWidget(bannerFrame, 0, 0, 0, 1); + } + + // ------------------------------------------------------------- + + d->hbox = new TQHBox(plainPage()); + d->splitter = new TQSplitter(d->hbox); + d->splitter->setFrameStyle( TQFrame::NoFrame ); + d->splitter->setFrameShadow( TQFrame::Plain ); + d->splitter->setFrameShape( TQFrame::NoFrame ); + d->splitter->setOpaqueResize(false); + + d->mainLayout->addMultiCellWidget(d->hbox, 1, 2, 0, 1); + d->mainLayout->setColStretch(0, 10); + d->mainLayout->setRowStretch(2, 10); + + kapp->restoreOverrideCursor(); +} + +ImageDlgBase::~ImageDlgBase() +{ + if (d->timer) + delete d->timer; + + if (d->aboutData) + delete d->aboutData; + + delete d->settingsSideBar; + delete d; +} + +void ImageDlgBase::readSettings(void) +{ + TDEConfig *config = kapp->config(); + config->setGroup(d->name + TQString(" Tool Dialog")); + if(config->hasKey("SplitterSizes")) + d->splitter->setSizes(config->readIntListEntry("SplitterSizes")); + + readUserSettings(); +} + +void ImageDlgBase::writeSettings() +{ + TDEConfig *config = kapp->config(); + config->setGroup(d->name + TQString(" Tool Dialog")); + config->writeEntry("SplitterSizes", d->splitter->sizes()); + config->sync(); + saveDialogSize(d->name + TQString(" Tool Dialog")); +} + +void ImageDlgBase::closeEvent(TQCloseEvent *e) +{ + writeSettings(); + e->accept(); +} + +void ImageDlgBase::slotCancel() +{ + writeSettings(); + done(Cancel); +} + +void ImageDlgBase::slotOk() +{ + writeSettings(); + writeUserSettings(); + finalRendering(); +} + +void ImageDlgBase::slotDefault() +{ + resetValues(); + slotEffect(); +} + +void ImageDlgBase::slotHelp() +{ + // If setAboutData() is called by plugin, well DigikamImagePlugins help is launched, + // else digiKam help. In this case, setHelp() method must be used to set anchor and handbook name. + + if (d->aboutData) + TDEApplication::kApplication()->invokeHelp(d->name, "digikam"); + else + KDialogBase::slotHelp(); +} + +void ImageDlgBase::setAboutData(TDEAboutData *about) +{ + d->aboutData = about; + TQPushButton *helpButton = actionButton( Help ); + KHelpMenu* helpMenu = new KHelpMenu(this, d->aboutData, false); + helpMenu->menu()->removeItemAt(0); + helpMenu->menu()->insertItem(i18n("digiKam Handbook"), this, TQ_SLOT(slotHelp()), 0, -1, 0); + helpButton->setPopup( helpMenu->menu() ); +} + +void ImageDlgBase::setPreviewAreaWidget(TQWidget *w) +{ + w->reparent( d->splitter, TQPoint(0, 0) ); + TQSizePolicy rightSzPolicy(TQSizePolicy::Preferred, + TQSizePolicy::Expanding, + 2, 1); + w->setSizePolicy(rightSzPolicy); +} + +void ImageDlgBase::setUserAreaWidget(TQWidget *w) +{ + TQString sbName(d->name + TQString(" Image Plugin Sidebar")); + d->settingsSideBar = new Sidebar(d->hbox, sbName.ascii(), Sidebar::Right); + d->settingsSideBar->setSplitter(d->splitter); + d->settingsSideBar->appendTab(w, SmallIcon("configure"), i18n("Settings")); + d->settingsSideBar->loadViewState(); + + readSettings(); +} + +void ImageDlgBase::slotTimer() +{ + if (d->timer) + { + d->timer->stop(); + delete d->timer; + } + + d->timer = new TQTimer( this ); + connect( d->timer, TQ_SIGNAL(timeout()), + this, TQ_SLOT(slotEffect()) ); + d->timer->start(500, true); +} + +} // NameSpace Digikam + diff --git a/src/libs/dialogs/imagedlgbase.h b/src/libs/dialogs/imagedlgbase.h new file mode 100644 index 00000000..97dc6a35 --- /dev/null +++ b/src/libs/dialogs/imagedlgbase.h @@ -0,0 +1,98 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-07-23 + * Description : simple plugins dialog without threadable + * filter interface. The dialog layout is + * designed to accept custom widgets in + * preview and settings area. + * + * Copyright 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef IMAGEDLGBASE_H +#define IMAGEDLGBASE_H + +// TQt includes + +#include + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +class TQWidget; + +class TDEAboutData; + +namespace Digikam +{ + +class ImageDlgBasePriv; + +class DIGIKAM_EXPORT ImageDlgBase : public KDialogBase +{ + TQ_OBJECT + + +public: + + ImageDlgBase(TQWidget *parent, TQString title, TQString name, + bool loadFileSettings=true, bool tryAction=false, TQFrame* bannerFrame=0); + ~ImageDlgBase(); + + void setAboutData(TDEAboutData *about); + void setPreviewAreaWidget(TQWidget *w); + void setUserAreaWidget(TQWidget *w); + +protected slots: + + virtual void slotDefault(); + virtual void slotTimer(); + +protected: + + void closeEvent(TQCloseEvent *e); + virtual void finalRendering(){}; + virtual void writeUserSettings(void){}; + virtual void readUserSettings(void){ slotDefault(); }; + virtual void resetValues(void){}; + +private slots: + + void slotHelp(); + void slotCancel(); + void slotOk(); + virtual void slotEffect(){}; + +private: + + void readSettings(void); + void writeSettings(void); + +private: + + ImageDlgBasePriv* d; +}; + +} // NameSpace Digikam + +#endif /* IMAGEDLGBASE */ diff --git a/src/libs/dialogs/imageguidedlg.cpp b/src/libs/dialogs/imageguidedlg.cpp new file mode 100644 index 00000000..9713a872 --- /dev/null +++ b/src/libs/dialogs/imageguidedlg.cpp @@ -0,0 +1,597 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-05-07 + * Description : A threaded filter plugin dialog with a preview + * image guide widget and a settings user area + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "sidebar.h" +#include "dimgthreadedfilter.h" +#include "dimginterface.h" +#include "imageguidedlg.h" +#include "imageguidedlg.moc" + +namespace Digikam +{ + +class ImageGuideDlgPriv +{ +public: + + enum RunningMode + { + NoneRendering=0, + PreviewRendering, + FinalRendering + }; + + ImageGuideDlgPriv() + { + tryAction = false; + progress = true; + currentRenderingMode = NoneRendering; + parent = 0; + settings = 0; + timer = 0; + aboutData = 0; + guideColorBt = 0; + progressBar = 0; + guideSize = 0; + mainLayout = 0; + settingsLayout = 0; + hbox = 0; + settingsSideBar = 0; + splitter = 0; + } + + bool tryAction; + bool progress; + + int currentRenderingMode; + + TQWidget *parent; + TQWidget *settings; + + TQTimer *timer; + + TQString name; + + TQGridLayout *mainLayout; + TQGridLayout *settingsLayout; + + TQSpinBox *guideSize; + + TQHBox *hbox; + + TQSplitter *splitter; + + KProgress *progressBar; + + KColorButton *guideColorBt; + + TDEAboutData *aboutData; + + Sidebar *settingsSideBar; +}; + +ImageGuideDlg::ImageGuideDlg(TQWidget* parent, TQString title, TQString name, + bool loadFileSettings, bool progress, + bool guideVisible, int guideMode, TQFrame* bannerFrame, + bool prevModeOptions, bool useImageSelection, + bool tryAction) + : KDialogBase(Plain, 0, + Help|Default|User1|User2|User3|Try|Ok|Cancel, Ok, + parent, 0, true, true, + i18n("&Abort"), + i18n("&Save As..."), + i18n("&Load...")) +{ + kapp->setOverrideCursor( KCursor::waitCursor() ); + setCaption(DImgInterface::defaultInterface()->getImageFileName() + TQString(" - ") + title); + + d = new ImageGuideDlgPriv; + d->parent = parent; + d->name = name; + d->progress = progress; + d->tryAction = tryAction; + m_threadedFilter = 0; + TQString whatsThis; + + setButtonWhatsThis ( Default, i18n("

Reset all filter parameters to their default values.") ); + setButtonWhatsThis ( User1, i18n("

Abort the current image rendering.") ); + setButtonWhatsThis ( User3, i18n("

Load all filter parameters from settings text file.") ); + setButtonWhatsThis ( User2, i18n("

Save all filter parameters to settings text file.") ); + showButton(User2, loadFileSettings); + showButton(User3, loadFileSettings); + showButton(Try, tryAction); + + resize(configDialogSize(name + TQString(" Tool Dialog"))); + + // ------------------------------------------------------------- + + d->mainLayout = new TQGridLayout( plainPage(), 2, 1); + + if (bannerFrame) + { + bannerFrame->reparent( plainPage(), TQPoint(0, 0) ); + d->mainLayout->addMultiCellWidget(bannerFrame, 0, 0, 0, 1); + } + + // ------------------------------------------------------------- + + TQString desc; + + if (guideVisible) + desc = i18n("

This is the the image filter effect preview. " + "If you move the mouse cursor on this area, " + "a vertical and horizontal dashed line will be draw " + "to guide you in adjusting the filter settings. " + "Press the left mouse button to freeze the dashed " + "line's position."); + else + desc = i18n("

This is the image filter effect preview."); + + d->hbox = new TQHBox(plainPage()); + d->splitter = new TQSplitter(d->hbox); + m_imagePreviewWidget = new ImageWidget(d->name, d->splitter, desc, prevModeOptions, + guideMode, guideVisible, useImageSelection); + + d->splitter->setFrameStyle( TQFrame::NoFrame ); + d->splitter->setFrameShadow( TQFrame::Plain ); + d->splitter->setFrameShape( TQFrame::NoFrame ); + d->splitter->setOpaqueResize(false); + + TQSizePolicy rightSzPolicy(TQSizePolicy::Preferred, TQSizePolicy::Expanding, 2, 1); + m_imagePreviewWidget->setSizePolicy(rightSzPolicy); + + TQString sbName(d->name + TQString(" Image Plugin Sidebar")); + d->settingsSideBar = new Sidebar(d->hbox, sbName.ascii(), Sidebar::Right); + d->settingsSideBar->setSplitter(d->splitter); + + d->mainLayout->addMultiCellWidget(d->hbox, 1, 2, 0, 1); + d->mainLayout->setColStretch(0, 10); + d->mainLayout->setRowStretch(2, 10); + + // ------------------------------------------------------------- + + d->settings = new TQWidget(plainPage()); + d->settingsLayout = new TQGridLayout( d->settings, 1, 0); + TQVBoxLayout *vLayout = new TQVBoxLayout( spacingHint() ); + + // ------------------------------------------------------------- + + TQWidget *gboxGuideSettings = new TQWidget(d->settings); + TQGridLayout* grid = new TQGridLayout( gboxGuideSettings, 2, 2, marginHint(), spacingHint()); + KSeparator *line = new KSeparator(TQt::Horizontal, gboxGuideSettings); + grid->addMultiCellWidget(line, 0, 0, 0, 2); + + TQLabel *label5 = new TQLabel(i18n("Guide color:"), gboxGuideSettings); + d->guideColorBt = new KColorButton( TQColor( TQt::red ), gboxGuideSettings ); + TQWhatsThis::add( d->guideColorBt, i18n("

Set here the color used to draw guides dashed-lines.")); + grid->addMultiCellWidget(label5, 1, 1, 0, 0); + grid->addMultiCellWidget(d->guideColorBt, 1, 1, 2, 2); + + TQLabel *label6 = new TQLabel(i18n("Guide width:"), gboxGuideSettings); + d->guideSize = new TQSpinBox( 1, 5, 1, gboxGuideSettings); + TQWhatsThis::add( d->guideSize, i18n("

Set here the width in pixels used to draw guides dashed-lines.")); + grid->addMultiCellWidget(label6, 2, 2, 0, 0); + grid->addMultiCellWidget(d->guideSize, 2, 2, 2, 2); + grid->setColStretch(1, 10); + + if (guideVisible) gboxGuideSettings->show(); + else gboxGuideSettings->hide(); + + vLayout->addWidget(gboxGuideSettings); + + TQHBox *hbox = new TQHBox(d->settings); + TQLabel *space1 = new TQLabel(hbox); + space1->setFixedWidth(spacingHint()); + d->progressBar = new KProgress(100, hbox); + d->progressBar->setMaximumHeight( fontMetrics().height() ); + TQWhatsThis::add(d->progressBar ,i18n("

This is the percentage of the task which has been completed up to this point.")); + d->progressBar->setValue(0); + setProgressVisible(false); + TQLabel *space2 = new TQLabel(hbox); + space2->setFixedWidth(spacingHint()); + + vLayout->addWidget(hbox); + vLayout->addStretch(10); + + d->settingsLayout->addMultiCellLayout(vLayout, 1, 1, 0, 0); + + d->settingsSideBar->appendTab(d->settings, SmallIcon("configure"), i18n("Settings")); + d->settingsSideBar->loadViewState(); + + // Reading splitter sizes here prevent flicker effect in dialog. + TDEConfig *config = kapp->config(); + config->setGroup(d->name + TQString(" Tool Dialog")); + if(config->hasKey("SplitterSizes")) + d->splitter->setSizes(config->readIntListEntry("SplitterSizes")); + + // ------------------------------------------------------------- + + TQTimer::singleShot(0, this, TQ_SLOT(slotInit())); + kapp->restoreOverrideCursor(); +} + +ImageGuideDlg::~ImageGuideDlg() +{ + if (d->timer) + delete d->timer; + + if (m_threadedFilter) + delete m_threadedFilter; + + if (d->aboutData) + delete d->aboutData; + + delete d->settingsSideBar; + delete d; +} + +void ImageGuideDlg::readSettings(void) +{ + TQColor defaultGuideColor(TQt::red); + TDEConfig *config = kapp->config(); + config->setGroup(d->name + TQString(" Tool Dialog")); + d->guideColorBt->setColor(config->readColorEntry("Guide Color", &defaultGuideColor)); + d->guideSize->setValue(config->readNumEntry("Guide Width", 1)); + m_imagePreviewWidget->slotChangeGuideSize(d->guideSize->value()); + m_imagePreviewWidget->slotChangeGuideColor(d->guideColorBt->color()); +} + +void ImageGuideDlg::writeSettings(void) +{ + TDEConfig *config = kapp->config(); + config->setGroup(d->name + TQString(" Tool Dialog")); + config->writeEntry( "Guide Color", d->guideColorBt->color() ); + config->writeEntry( "Guide Width", d->guideSize->value() ); + config->writeEntry( "SplitterSizes", d->splitter->sizes() ); + config->sync(); + saveDialogSize(d->name + TQString(" Tool Dialog")); +} + +void ImageGuideDlg::slotInit() +{ + readSettings(); + // Reset values to defaults. + TQTimer::singleShot(0, this, TQ_SLOT(readUserSettings())); + + if (!d->tryAction) + { + connect(m_imagePreviewWidget, TQ_SIGNAL(signalResized()), + this, TQ_SLOT(slotResized())); + } + + connect(d->guideColorBt, TQ_SIGNAL(changed(const TQColor &)), + m_imagePreviewWidget, TQ_SLOT(slotChangeGuideColor(const TQColor &))); + + connect(d->guideSize, TQ_SIGNAL(valueChanged(int)), + m_imagePreviewWidget, TQ_SLOT(slotChangeGuideSize(int))); +} + +void ImageGuideDlg::setUserAreaWidget(TQWidget *w) +{ + w->reparent( d->settings, TQPoint(0, 0) ); + TQVBoxLayout *vLayout = new TQVBoxLayout( spacingHint() ); + vLayout->addWidget(w); + d->settingsLayout->addMultiCellLayout(vLayout, 0, 0, 0, 0); +} + +void ImageGuideDlg::setAboutData(TDEAboutData *about) +{ + d->aboutData = about; + TQPushButton *helpButton = actionButton( Help ); + KHelpMenu* helpMenu = new KHelpMenu(this, d->aboutData, false); + helpMenu->menu()->removeItemAt(0); + helpMenu->menu()->insertItem(i18n("digiKam Handbook"), this, TQ_SLOT(slotHelp()), 0, -1, 0); + helpButton->setPopup( helpMenu->menu() ); +} + +void ImageGuideDlg::setProgressVisible(bool v) +{ + if (v) + d->progressBar->show(); + else + d->progressBar->hide(); +} + +void ImageGuideDlg::abortPreview() +{ + d->currentRenderingMode = ImageGuideDlgPriv::NoneRendering; + d->progressBar->setValue(0); + setProgressVisible(false); + enableButton(Ok, true); + enableButton(User1, false); + enableButton(User2, true); + enableButton(User3, true); + enableButton(Try, true); + enableButton(Default, true); + renderingFinished(); +} + +void ImageGuideDlg::slotTry() +{ + slotEffect(); +} + +void ImageGuideDlg::slotResized(void) +{ + if (d->currentRenderingMode == ImageGuideDlgPriv::FinalRendering) + { + m_imagePreviewWidget->update(); + return; + } + else if (d->currentRenderingMode == ImageGuideDlgPriv::PreviewRendering) + { + if (m_threadedFilter) + m_threadedFilter->stopComputation(); + } + + TQTimer::singleShot(0, this, TQ_SLOT(slotEffect())); +} + +void ImageGuideDlg::slotUser1() +{ + if (d->currentRenderingMode != ImageGuideDlgPriv::NoneRendering) + if (m_threadedFilter) + m_threadedFilter->stopComputation(); +} + +void ImageGuideDlg::slotDefault() +{ + resetValues(); + slotEffect(); +} + +void ImageGuideDlg::slotCancel() +{ + if (d->currentRenderingMode != ImageGuideDlgPriv::NoneRendering) + { + if (m_threadedFilter) + m_threadedFilter->stopComputation(); + + kapp->restoreOverrideCursor(); + } + + writeSettings(); + done(Cancel); +} + +void ImageGuideDlg::closeEvent(TQCloseEvent *e) +{ + if (d->currentRenderingMode != ImageGuideDlgPriv::NoneRendering) + { + if (m_threadedFilter) + m_threadedFilter->stopComputation(); + + kapp->restoreOverrideCursor(); + } + + writeSettings(); + e->accept(); +} + +void ImageGuideDlg::slotHelp() +{ + // If setAboutData() is called by plugin, well DigikamImagePlugins help is lauched, + // else digiKam help. In this case, setHelp() method must be used to set anchor and handbook name. + + if (d->aboutData) + TDEApplication::kApplication()->invokeHelp(d->name, "digikam"); + else + KDialogBase::slotHelp(); +} + +void ImageGuideDlg::slotTimer() +{ + if (d->timer) + { + d->timer->stop(); + delete d->timer; + } + + d->timer = new TQTimer( this ); + connect( d->timer, TQ_SIGNAL(timeout()), + this, TQ_SLOT(slotEffect()) ); + d->timer->start(500, true); +} + +void ImageGuideDlg::slotEffect() +{ + // Computation already in process. + if (d->currentRenderingMode != ImageGuideDlgPriv::NoneRendering) + return; + + d->currentRenderingMode = ImageGuideDlgPriv::PreviewRendering; + DDebug() << "Preview " << d->name << " started..." << endl; + + enableButton(Ok, false); + enableButton(User1, true); + enableButton(User2, false); + enableButton(User3, false); + enableButton(Default, false); + enableButton(Try, false); + d->progressBar->setValue(0); + if (d->progress) setProgressVisible(true); + + if (m_threadedFilter) + { + delete m_threadedFilter; + m_threadedFilter = 0; + } + + prepareEffect(); +} + +void ImageGuideDlg::slotOk() +{ + d->currentRenderingMode = ImageGuideDlgPriv::FinalRendering; + DDebug() << "Final " << d->name << " started..." << endl; + writeSettings(); + writeUserSettings(); + + enableButton(Ok, false); + enableButton(User1, false); + enableButton(User2, false); + enableButton(User3, false); + enableButton(Default, false); + enableButton(Try, false); + kapp->setOverrideCursor( KCursor::waitCursor() ); + d->progressBar->setValue(0); + + if (m_threadedFilter) + { + delete m_threadedFilter; + m_threadedFilter = 0; + } + + prepareFinal(); +} + +void ImageGuideDlg::customEvent(TQCustomEvent *event) +{ + if (!event) return; + + DImgThreadedFilter::EventData *ed = (DImgThreadedFilter::EventData*) event->data(); + + if (!ed) return; + + if (ed->starting) // Computation in progress ! + { + d->progressBar->setValue(ed->progress); + } + else + { + if (ed->success) // Computation Completed ! + { + switch (d->currentRenderingMode) + { + case ImageGuideDlgPriv::PreviewRendering: + { + DDebug() << "Preview " << d->name << " completed..." << endl; + putPreviewData(); + abortPreview(); + break; + } + + case ImageGuideDlgPriv::FinalRendering: + { + DDebug() << "Final" << d->name << " completed..." << endl; + putFinalData(); + kapp->restoreOverrideCursor(); + accept(); + break; + } + } + } + else // Computation Failed ! + { + switch (d->currentRenderingMode) + { + case ImageGuideDlgPriv::PreviewRendering: + { + DDebug() << "Preview " << d->name << " failed..." << endl; + // abortPreview() must be call here for set progress bar to 0 properly. + abortPreview(); + break; + } + + case ImageGuideDlgPriv::FinalRendering: + break; + } + } + } + + delete ed; +} + +// Backport KDialog::keyPressEvent() implementation from KDELibs to ignore Enter/Return Key events +// to prevent any conflicts between dialog keys events and SpinBox keys events. + +void ImageGuideDlg::keyPressEvent(TQKeyEvent *e) +{ + if ( e->state() == 0 ) + { + switch ( e->key() ) + { + case Key_Escape: + e->accept(); + reject(); + break; + case Key_Enter: + case Key_Return: + e->ignore(); + break; + default: + e->ignore(); + return; + } + } + else + { + // accept the dialog when Ctrl-Return is pressed + if ( e->state() == ControlButton && + (e->key() == Key_Return || e->key() == Key_Enter) ) + { + e->accept(); + accept(); + } + else + { + e->ignore(); + } + } +} + +} // NameSpace Digikam diff --git a/src/libs/dialogs/imageguidedlg.h b/src/libs/dialogs/imageguidedlg.h new file mode 100644 index 00000000..e6841c43 --- /dev/null +++ b/src/libs/dialogs/imageguidedlg.h @@ -0,0 +1,123 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-05-07 + * Description : A threaded filter plugin dialog with a preview + * image guide widget and a settings user area + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef IMAGEGUIDEDLG_H +#define IMAGEGUIDEDLG_H + +// TQt includes + +#include + +// KDE include. + +#include + +// Local includes. + +#include "imagewidget.h" +#include "imageguidewidget.h" +#include "digikam_export.h" + +class TQFrame; + +class TDEAboutData; + +namespace Digikam +{ + +class ImageGuideDlgPriv; +class DImgThreadedFilter; + +class DIGIKAM_EXPORT ImageGuideDlg : public KDialogBase +{ + TQ_OBJECT + + +public: + + ImageGuideDlg(TQWidget* parent, TQString title, TQString name, + bool loadFileSettings=false, bool progress=true, + bool guideVisible=true, + int guideMode=ImageGuideWidget::HVGuideMode, + TQFrame* bannerFrame=0, + bool prevModeOptions=false, + bool useImageSelection=false, + bool tryAction=false); + ~ImageGuideDlg(); + + void setAboutData(TDEAboutData *about); + void setUserAreaWidget(TQWidget *w); + void setProgressVisible(bool v); + +public: + + DImgThreadedFilter *m_threadedFilter; + + ImageWidget *m_imagePreviewWidget; + +public slots: + + void slotTimer(); + void slotEffect(); + void slotOk(); + void slotTry(); + +protected slots: + + virtual void slotCancel(); + virtual void slotUser1(); + virtual void slotDefault(); + virtual void slotInit(); + virtual void readUserSettings(void){ slotDefault(); }; + +private slots: + + void slotResized(); + void slotHelp(); + +protected: + + void closeEvent(TQCloseEvent *e); + void customEvent(TQCustomEvent *event); + void abortPreview(void); + void readSettings(void); + void writeSettings(void); + void keyPressEvent(TQKeyEvent *e); + + virtual void writeUserSettings(void){}; + virtual void resetValues(void){}; + virtual void prepareEffect(void){}; + virtual void prepareFinal(void){}; + virtual void putPreviewData(void){}; + virtual void putFinalData(void){}; + virtual void renderingFinished(void){}; + +private: + + ImageGuideDlgPriv* d; +}; + +} // NameSpace Digikam + +#endif /* IMAGEGUIDEDLG_H */ diff --git a/src/libs/dialogs/rawcameradlg.cpp b/src/libs/dialogs/rawcameradlg.cpp new file mode 100644 index 00000000..298b47d4 --- /dev/null +++ b/src/libs/dialogs/rawcameradlg.cpp @@ -0,0 +1,178 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2008-04-07 + * Description : Raw camera list dialog + * + * Copyright (C) 2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include + +// LibKDcraw includes. + +#include +#include + +#if KDCRAW_VERSION < 0x000106 +#include +#endif + +// Local includes. + +#include "searchtextbar.h" +#include "rawcameradlg.h" +#include "rawcameradlg.moc" + +namespace Digikam +{ + +class RawCameraDlgPriv +{ +public: + + RawCameraDlgPriv() + { + listView = 0; + searchBar = 0; + } + + TQListView *listView; + + SearchTextBar *searchBar; +}; + +RawCameraDlg::RawCameraDlg(TQWidget *parent) + : KDialogBase(parent, 0, true, TQString(), Help|Ok, Ok, true) +{ + setHelp("digitalstillcamera.anchor", "digikam"); + setCaption(i18n("List of supported RAW cameras")); + + d = new RawCameraDlgPriv; + + TQWidget *page = makeMainWidget(); + TQGridLayout* grid = new TQGridLayout(page, 2, 2, 0, spacingHint()); + +#if KDCRAW_VERSION < 0x000106 + TQStringList list = KDcrawIface::DcrawBinary::instance()->supportedCamera(); + TQString dcrawVer = KDcrawIface::DcrawBinary::instance()->internalVersion(); +#else + TQStringList list = KDcrawIface::KDcraw::supportedCamera(); + TQString librawVer = KDcrawIface::KDcraw::librawVersion(); +#endif + TQString KDcrawVer = KDcrawIface::KDcraw::version(); + + // -------------------------------------------------------- + + TQLabel *logo = new TQLabel(page); + TDEIconLoader* iconLoader = TDEApplication::kApplication()->iconLoader(); + + if (TDEApplication::kApplication()->aboutData()->appName() == TQString("digikam")) + logo->setPixmap(iconLoader->loadIcon("digikam", TDEIcon::NoGroup, 96, TDEIcon::DefaultState, 0, true)); + else + logo->setPixmap(iconLoader->loadIcon("showfoto", TDEIcon::NoGroup, 96, TDEIcon::DefaultState, 0, true)); + + // -------------------------------------------------------- + + TQLabel *header = new TQLabel(page); +#if KDCRAW_VERSION < 0x000106 + header->setText(i18n("

Using KDcraw library version %1" + "

Using Dcraw program version %2" + "

%3 models in the list") + .arg(KDcrawVer).arg(dcrawVer).arg(list.count())); +#else + header->setText(i18n("

Using KDcraw library version %1" + "

Using LibRaw version %2" + "

%3 models in the list") + .arg(KDcrawVer).arg(librawVer).arg(list.count())); +#endif + + // -------------------------------------------------------- + + d->searchBar = new SearchTextBar(page, "RawCameraDlgSearchBar"); + d->listView = new TQListView(page); + d->listView->addColumn("Camera Model"); // Header is hiden. No i18n here. + d->listView->setSorting(1); + d->listView->header()->hide(); + d->listView->setResizeMode(TQListView::LastColumn); + + for (TQStringList::Iterator it = list.begin() ; it != list.end() ; ++it) + new TQListViewItem(d->listView, *it); + + // -------------------------------------------------------- + + + grid->addMultiCellWidget(logo, 0, 0, 0, 0); + grid->addMultiCellWidget(header, 0, 0, 1, 2); + grid->addMultiCellWidget(d->listView, 1, 1, 0, 2); + grid->addMultiCellWidget(d->searchBar, 2, 2, 0, 2); + grid->setColStretch(1, 10); + grid->setRowStretch(1, 10); + + // -------------------------------------------------------- + + connect(d->searchBar, TQ_SIGNAL(signalTextChanged(const TQString&)), + this, TQ_SLOT(slotSearchTextChanged(const TQString&))); + + resize(500, 500); +} + +RawCameraDlg::~RawCameraDlg() +{ + delete d; +} + +void RawCameraDlg::slotSearchTextChanged(const TQString& filter) +{ + bool query = false; + TQString search = filter.lower(); + + TQListViewItemIterator it(d->listView); + + for ( ; it.current(); ++it ) + { + TQListViewItem *item = it.current(); + + if (item->text(0).lower().contains(search)) + { + query = true; + item->setVisible(true); + } + else + { + item->setVisible(false); + } + } + + d->searchBar->slotSearchResult(query); +} + +} // NameSpace Digikam diff --git a/src/libs/dialogs/rawcameradlg.h b/src/libs/dialogs/rawcameradlg.h new file mode 100644 index 00000000..41d4a7de --- /dev/null +++ b/src/libs/dialogs/rawcameradlg.h @@ -0,0 +1,61 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2008-04-07 + * Description : Raw camera list dialog + * + * Copyright (C) 2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef RAWCAMERADLG_H +#define RAWCAMERADLG_H + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class RawCameraDlgPriv; + +class DIGIKAM_EXPORT RawCameraDlg : public KDialogBase +{ + TQ_OBJECT + + +public: + + RawCameraDlg(TQWidget* parent); + ~RawCameraDlg(); + +private slots: + + void slotSearchTextChanged(const TQString&); + +private: + + RawCameraDlgPriv *d; +}; + +} // NameSpace Digikam + +#endif // RAWCAMERADLG_H diff --git a/src/libs/dimg/Makefile.am b/src/libs/dimg/Makefile.am new file mode 100644 index 00000000..23e746a2 --- /dev/null +++ b/src/libs/dimg/Makefile.am @@ -0,0 +1,27 @@ +SUBDIRS = loaders filters +METASOURCES = AUTO + +noinst_LTLIBRARIES = libdimg.la + +libdimg_la_SOURCES = dimg.cpp dimgscale.cpp dcolor.cpp dcolorcomposer.cpp ddebug.cpp + +libdimg_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +libdimg_la_LIBADD = $(top_builddir)/src/libs/histogram/libhistogram.la \ + $(top_builddir)/src/libs/levels/liblevels.la \ + $(top_builddir)/src/libs/curves/libcurves.la \ + $(top_builddir)/src/libs/whitebalance/libwhitebalance.la \ + $(top_builddir)/src/libs/dimg/loaders/libdimgloaders.la \ + $(top_builddir)/src/libs/dimg/filters/libdimgfilters.la \ + $(top_builddir)/src/libs/dmetadata/libdmetadata.la \ + $(LIBKDCRAW_LIBS) $(LCMS_LIBS) + +INCLUDES = $(all_includes) $(LIBKDCRAW_CFLAGS) \ + -I$(top_srcdir)/src/libs/dimg/loaders \ + -I$(top_srcdir)/src/libs/dimg/filters \ + -I$(top_srcdir)/src/libs/dmetadata \ + -I$(top_srcdir)/src/digikam + +digikaminclude_HEADERS = dimg.h dcolor.h dcolorpixelaccess.h dcolorcomposer.h \ + dcolorblend.h ddebug.h +digikamincludedir = $(includedir)/digikam diff --git a/src/libs/dimg/README b/src/libs/dimg/README new file mode 100644 index 00000000..4b13f3fe --- /dev/null +++ b/src/libs/dimg/README @@ -0,0 +1,95 @@ + +--------------------------------------------------------------------------- +WHAT'S DIMG FRAMEWORK ? + + +Original post from Renchi Raju on digiKam mailing list : + +[Digikam-devel] Imaging Library +Renchi Raju renchi at pooh.tam.uiuc.edu +Sun Jun 19 23:15:20 CEST 2005 + +as you all know, we have been using imlib2 library for the 0.7 series. we +have had a fairly large number of bugreports because of imlib2 (not +imlib2's fault, but because of tdelibs's handling of ltdl). In addition, +some imlib2 bugreports I reported to upstream have gone unfixed for long +time now. + +Also, we need to think about 16bit imaging support. this won't come from +imlib2 and neither from tqt. with qt4 there was hope of 16bit image +support, but trolltech has made it clear that imaging apps forms only 0.1% +of their customer base and they are not interested in providing custom +support for them. so the only solution I see (without depending on +imagemagick) is to roll our own library. + +i have been working on a imaging library for digikam, its called DImg. +it doesn't aim to be a complete imaging library; it uses TQImage for +rendering and for loading files which are not supported natively by it. +some of the working/planned features: + +* Native Image Loaders, for some imageformats which are of interest to +us: JPEG (complete), TIFF (a rudimentary one currently), PNG (planned), PPM +(planned). for the rest tqt's qimage is used. + +* Metadata preservation: when a file is loaded, its metadata like XMP, +IPTC, EXIF, JFIF are read and held in memory. now when you save back the +file to the original file or to a different file, the metadata is +automatically written (How, we have been handling it currently with +imlib2 is on saving a file: we save the file to a temporary file, reread +the exif info from the original file and then write to a second temporary +file.) + +* Explicitly Shared Container format (see tqt docs): this is necessary for +performance reasons. + +* 8bit and 16bit support: if the file format is 16 bit, it will load up +the image in 16bit format (currently only 16bit tiff support) and all +operations are done in 16 bit format, except when the rendering to screen +is done, when its converted on the fly to a temporary 8bit image and then +rendered. + +* Basic image manipulation: rotate, flip, color modifications, crop, +scale (this has been ported from Imlib2 - originally ported by Mosfet, I +added 16 bit scaling support and support for scaling of only a section of +the image) + +* Rendering to Pixmap: using TQImage/QPixmap. (see above for rendering of +16bit images). + +* Pixel format: the pixel format is different from TQImage/Imlib2 pixel +format. In TQImage/Imlib2 the pixel data is stored as unsigned ints and to +access the individual colors you need to use bit-shifting to ensure +endian correctness. in DImg, the pixel data is stored as unsigned char. +the color layout is B,G,R,A (blue, green, red, alpha) + +for 8bit images: you can access individual color components like this: + +uchar* pixels = image.bits(); +for (int i=0; i + * + * RGB<->HLS transformation algorithms are inspired from methods + * describe at this url : + * http://www.paris-pc-gis.com/MI_Enviro/Colors/color_models.htm + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// C++ includes. + +#include + +// Local includes. + +#include "ddebug.h" +#include "dcolor.h" + +namespace Digikam +{ + +/* +DColor::DColor(const DColor& color) +{ + m_red = color.m_red; + m_green = color.m_green; + m_blue = color.m_blue; + m_alpha = color.m_alpha; + m_sixteenBit = color.m_sixteenBit; +} +*/ + +DColor::DColor(const TQColor& color, bool sixteenBit) +{ + // initialize as eight bit + m_red = color.red(); + m_green = color.green(); + m_blue = color.blue(); + m_alpha = 255; + m_sixteenBit = false; + + // convert to sixteen bit if requested + if (sixteenBit) + convertToSixteenBit(); +} + +/* +DColor& DColor::operator=(const DColor& color) +{ + m_red = color.m_red; + m_green = color.m_green; + m_blue = color.m_blue; + m_alpha = color.m_alpha; + m_sixteenBit = color.m_sixteenBit; + return *this; +} +*/ + +TQColor DColor::getTQColor() const +{ + if (m_sixteenBit) + { + DColor eightBit(*this); + eightBit.convertToEightBit(); + return eightBit.getTQColor(); + } + + return (TQColor(m_red, m_green, m_blue)); +} + +void DColor::convertToSixteenBit() +{ + if (m_sixteenBit) + return; + + m_red = (m_red + 1) * 256 - 1; + m_green = (m_green + 1) * 256 - 1; + m_blue = (m_blue + 1) * 256 - 1; + m_alpha = (m_alpha + 1) * 256 - 1; + m_sixteenBit = true; +} + +void DColor::convertToEightBit() +{ + if (!m_sixteenBit) + return; + + m_red = (m_red + 1) / 256 - 1; + m_green = (m_green + 1) / 256 - 1; + m_blue = (m_blue + 1) / 256 - 1; + m_alpha = (m_alpha + 1) / 256 - 1; + m_sixteenBit = false; +} + + +void DColor::getHSL(int* h, int* s, int* l) const +{ + double min; + double max; + double red; + double green; + double blue; + double delta; + double sum; + double hue, sat, lig; + + double range = m_sixteenBit ? 65535.0 : 255.0; + + red = m_red / range; + green = m_green / range; + blue = m_blue / range; + + if (red > green) + { + if (red > blue) + max = red; + else + max = blue; + + if (green < blue) + min = green; + else + min = blue; + } + else + { + if (green > blue) + max = green; + else + max = blue; + + if (red < blue) + min = red; + else + min = blue; + } + + sum = max + min; + + lig = sum / 2; + sat = 0; + hue = 0; + + if (max != min) + { + delta = max - min; + + if (lig <= 0.5) + sat = delta / sum; + else + sat = delta / (2 - sum); + + if (red == max) + hue = (green - blue) / delta; + else if (green == max) + hue = 2 + (blue - red) / delta; + else if (blue == max) + hue = 4 + (red - green) / delta; + + if (hue < 0) + hue += 6; + if (hue > 6) + hue -= 6; + + hue *= 60; + } + + *h = lround(hue * range / 360.0); + *s = lround(sat * range); + *l = lround(lig * range); +} + +void DColor::setRGB(int h, int s, int l, bool sixteenBit) +{ + double hue; + double lightness; + double saturation; + double m1, m2; + double r, g, b; + + double range = m_sixteenBit ? 65535.0 : 255.0; + + if (s == 0) + { + m_red = l; + m_green = l; + m_blue = l; + } + else + { + hue = (double)(h * 360.0 / range); + lightness = (double)(l / range); + saturation = (double)(s / range); + + if (lightness <= 0.5) + m2 = lightness * (1 + saturation); + else + m2 = lightness + saturation - lightness * saturation; + + m1 = 2 * lightness - m2; + + double mh; + + mh = hue + 120; + while (mh > 360) + mh -= 360; + while (mh < 0) + mh += 360; + + if (mh < 60) + r = m1 + (m2 - m1) * mh / 60; + else if (mh < 180) + r = m2; + else if (mh < 240) + r = m1 + (m2 - m1) * (240 - mh) / 60; + else + r = m1; + + mh = hue; + while (mh > 360) + mh -= 360; + while (mh < 0) + mh += 360; + + if (mh < 60) + g = m1 + (m2 - m1) * mh / 60; + else if (mh < 180) + g = m2; + else if (mh < 240) + g = m1 + (m2 - m1) * (240 - mh) / 60; + else + g = m1; + + mh = hue - 120; + while (mh > 360) + mh -= 360; + while (mh < 0) + mh += 360; + + if (mh < 60) + b = m1 + (m2 - m1) * mh / 60; + else if (mh < 180) + b = m2; + else if (mh < 240) + b = m1 + (m2 - m1) * (240 - mh) / 60; + else + b = m1; + + m_red = lround(r * range); + m_green = lround(g * range); + m_blue = lround(b * range); + } + + m_sixteenBit = sixteenBit; + + // Fully opaque color. + if (m_sixteenBit) + m_alpha = 65535; + else + m_alpha = 255; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/dcolor.h b/src/libs/dimg/dcolor.h new file mode 100644 index 00000000..16a34f55 --- /dev/null +++ b/src/libs/dimg/dcolor.h @@ -0,0 +1,152 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-12-02 + * Description : 8-16 bits color container. + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DCOLOR_H +#define DCOLOR_H + +// TQt includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT DColor +{ +public: + + /** Initialize with default value, fully transparent eight bit black */ + DColor() + : m_red(0), m_green(0), m_blue(0), m_alpha(0), m_sixteenBit(false) + {}; + + /** Read value from data. Equivalent to setColor() */ + DColor(const uchar *data, bool sixteenBit = false) + { setColor(data, sixteenBit); } + + /** Initialize with given RGBA values */ + DColor(int red, int green, int blue, int alpha, bool sixteenBit) + : m_red(red), m_green(green), m_blue(blue), m_alpha(alpha), m_sixteenBit(sixteenBit) + {}; + + /** Read values from TQColor, convert to sixteenBit of sixteenBit is true */ + DColor(const TQColor& color, bool sixteenBit=false); + + // Use default copy constructor, assignment operator and destructor + + /** Read color values as RGBA from the given memory location. + If sixteenBit is false, 4 bytes are read. + If sixteenBit is true, 8 bytes are read. + Inline method. + */ + void setColor(const uchar *data, bool sixteenBit = false); + + /** Write the values of this color to the given memory location. + If sixteenBit is false, 4 bytes are written. + If sixteenBit is true, 8 bytes are written. + Inline method. + */ + void setPixel(uchar *data) const; + + int red () const { return m_red; } + int green() const { return m_green; } + int blue () const { return m_blue; } + int alpha() const { return m_alpha; } + bool sixteenBit() const { return m_sixteenBit; } + + void setRed (int red) { m_red = red; } + void setGreen(int green) { m_green = green; } + void setBlue (int blue) { m_blue = blue; } + void setAlpha(int alpha) { m_alpha = alpha; } + void setSixteenBit(bool sixteenBit) { m_sixteenBit = sixteenBit; } + + TQColor getTQColor() const; + + /** Convert the color values of this color to and from sixteen bit + and set the sixteenBit value accordingly + */ + void convertToSixteenBit(); + void convertToEightBit(); + + /** Premultiply and demultiply this color. + DImg stores the color non-premultiplied. + Inline methods. + */ + void premultiply(); + void demultiply(); + + /** Return the current RGB color values of this color + in the HSL color space. + Alpha is ignored for the conversion. + */ + void getHSL(int* h, int* s, int* l) const; + + /** Set the RGB color values of this color + to the given HSL values converted to RGB. + Alpha is set to be fully opaque. + sixteenBit determines both how the HSL values are interpreted + and the sixteenBit value of this color after this operation. + */ + void setRGB(int h, int s, int l, bool sixteenBit); + +private: + + int m_red; + int m_green; + int m_blue; + int m_alpha; + + bool m_sixteenBit; + +public: + + // Inline alpha blending helper functions. + // These functions are used by DColorComposer. + // Look at that code to learn how to use them for + // composition if you want to use them in optimized code. + void blendZero(); + void blendAlpha8(int alpha); + void blendInvAlpha8(int alpha); + void blendAlpha16(int alpha); + void blendInvAlpha16(int alpha); + void premultiply16(int alpha); + void premultiply8(int alpha); + void demultiply16(int alpha); + void demultiply8(int alpha); + void blendAdd(const DColor &src); + void blendClamp8(); + void blendClamp16(); +}; + +} // NameSpace Digikam + + +// Inline methods +#include "dcolorpixelaccess.h" +#include "dcolorblend.h" + +#endif /* DCOLOR_H */ diff --git a/src/libs/dimg/dcolorblend.h b/src/libs/dimg/dcolorblend.h new file mode 100644 index 00000000..b68322ba --- /dev/null +++ b/src/libs/dimg/dcolorblend.h @@ -0,0 +1,179 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-03-01 + * Description : DColor methods for blending + * + * Copyright (C) 2006-2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// Inspired by DirectFB, src/gfx/generic/generic.c: + +/* + (c) Copyright 2000-2002 convergence integrated media GmbH + (c) Copyright 2002-2005 convergence GmbH. + + All rights reserved. + + Written by Denis Oliver Kropp , + Andreas Hundt , + Sven Neumann , + Ville Syrjälä and + Claudio Ciccani . + +*/ + +#ifndef DCOLORBLEND_H +#define DCOLORBLEND_H + +namespace Digikam +{ + +inline void DColor::premultiply() +{ + if (sixteenBit()) + premultiply16(alpha()); + else + premultiply8(alpha()); +} + +inline void DColor::demultiply() +{ + if (sixteenBit()) + { + demultiply16(alpha()); + blendClamp16(); + } + else + { + demultiply8(alpha()); + blendClamp8(); + } +} + +inline void DColor::blendZero() +{ + setAlpha(0); + setRed(0); + setGreen(0); + setBlue(0); +} + +inline void DColor::blendAlpha16(int alphaValue) +{ + uint Sa = alphaValue + 1; + + setRed ((Sa * (uint)red()) >> 16); + setGreen((Sa * (uint)green()) >> 16); + setBlue ((Sa * (uint)blue()) >> 16); + setAlpha((Sa * (uint)alpha()) >> 16); +} + +inline void DColor::blendAlpha8(int alphaValue) +{ + uint Sa = alphaValue + 1; + + setRed ((Sa * red()) >> 8); + setGreen((Sa * green()) >> 8); + setBlue ((Sa * blue()) >> 8); + setAlpha((Sa * alpha()) >> 8); +} + +inline void DColor::blendInvAlpha16(int alphaValue) +{ + uint Sa = 65536 - alphaValue; + + setRed ((Sa * (uint)red()) >> 16); + setGreen((Sa * (uint)green()) >> 16); + setBlue ((Sa * (uint)blue()) >> 16); + setAlpha((Sa * (uint)alpha()) >> 16); +} + +inline void DColor::blendInvAlpha8(int alphaValue) +{ + uint Sa = 256 - alphaValue; + + setRed ((Sa * red()) >> 8); + setGreen((Sa * green()) >> 8); + setBlue ((Sa * blue()) >> 8); + setAlpha((Sa * alpha()) >> 8); +} + +inline void DColor::premultiply16(int alphaValue) +{ + uint Da = alphaValue + 1; + + setRed ((Da * (uint)red()) >> 16); + setGreen((Da * (uint)green()) >> 16); + setBlue ((Da * (uint)blue()) >> 16); +} + +inline void DColor::premultiply8(int alphaValue) +{ + uint Da = alphaValue + 1; + + setRed ((Da * red()) >> 8); + setGreen((Da * green()) >> 8); + setBlue ((Da * blue()) >> 8); +} + +inline void DColor::demultiply16(int alphaValue) +{ + uint Da = alphaValue + 1; + + setRed (((uint)red() << 16) / Da); + setGreen(((uint)green() << 16) / Da); + setBlue (((uint)blue() << 16) / Da); +} + +inline void DColor::demultiply8(int alphaValue) +{ + uint Da = alphaValue + 1; + + setRed ((red() << 8) / Da); + setGreen((green() << 8) / Da); + setBlue ((blue() << 8) / Da); +} + +inline void DColor::blendAdd(const DColor &src) +{ + setRed (red() + src.red()); + setGreen(green() + src.green()); + setBlue (blue() + src.blue()); + setAlpha(alpha() + src.alpha()); +} + +inline void DColor::blendClamp16() +{ + if (0xFFFF0000 & red()) setRed(65535); + if (0xFFFF0000 & green()) setGreen(65535); + if (0xFFFF0000 & blue()) setBlue(65535); + if (0xFFFF0000 & alpha()) setAlpha(65535); +} + +inline void DColor::blendClamp8() +{ + if (0xFF00 & red()) setRed(255); + if (0xFF00 & green()) setGreen(255); + if (0xFF00 & blue()) setBlue(255); + if (0xFF00 & alpha()) setAlpha(255); +} + +} // namespace Digikam + +#endif // DCOLORBLEND_H + diff --git a/src/libs/dimg/dcolorcomposer.cpp b/src/libs/dimg/dcolorcomposer.cpp new file mode 100644 index 00000000..653dbab3 --- /dev/null +++ b/src/libs/dimg/dcolorcomposer.cpp @@ -0,0 +1,436 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-03-02 + * Description : DColor methods for composing + * + * Copyright (C) 2006-2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// Integer arithmetic inspired by DirectFB, +// src/gfx/generic/generic.c and src/display/idirectfbsurface.c: + +/* + (c) Copyright 2000-2002 convergence integrated media GmbH + (c) Copyright 2002-2005 convergence GmbH. + + All rights reserved. + + Written by Denis Oliver Kropp , + Andreas Hundt , + Sven Neumann , + Ville Syrj�l� and + Claudio Ciccani . + +*/ + +// C++ includes. + +#include + +// Local includes. + +#include "dcolorcomposer.h" + +namespace Digikam +{ + +class DColorComposerPorterDuffNone : public DColorComposer +{ +public: + virtual void compose(DColor &dest, DColor src); +}; + +class DColorComposerPorterDuffClear : public DColorComposer +{ +public: + virtual void compose(DColor &dest, DColor src); + virtual void compose(DColor &dest, DColor src, MultiplicationFlags multiplicationFlags); +}; + +class DColorComposerPorterDuffSrc : public DColorComposer +{ +public: + virtual void compose(DColor &dest, DColor src); + virtual void compose(DColor &dest, DColor src, MultiplicationFlags multiplicationFlags); +}; + +class DColorComposerPorterDuffSrcOver : public DColorComposer +{ +public: + virtual void compose(DColor &dest, DColor src); +}; + +class DColorComposerPorterDuffDstOver : public DColorComposer +{ +public: + virtual void compose(DColor &dest, DColor src); +}; + +class DColorComposerPorterDuffSrcIn : public DColorComposer +{ +public: + virtual void compose(DColor &dest, DColor src); +}; + +class DColorComposerPorterDuffDstIn : public DColorComposer +{ +public: + virtual void compose(DColor &dest, DColor src); +}; + +class DColorComposerPorterDuffSrcOut : public DColorComposer +{ +public: + virtual void compose(DColor &dest, DColor src); +}; + +class DColorComposerPorterDuffDstOut : public DColorComposer +{ +public: + virtual void compose(DColor &dest, DColor src); +}; + +class DColorComposerPorterDuffSrcAtop : public DColorComposer +{ + public: + virtual void compose(DColor &dest, DColor src); +}; + +class DColorComposerPorterDuffDstAtop : public DColorComposer +{ + public: + virtual void compose(DColor &dest, DColor src); +}; + +class DColorComposerPorterDuffXor : public DColorComposer +{ + public: + virtual void compose(DColor &dest, DColor src); +}; + +// Porter-Duff None +// component = (source * sa + destination * (1-sa)) +// Src blending function Src Alpha +// Dst blending function Inv Src Alpha +void DColorComposerPorterDuffNone::compose(DColor &dest, DColor src) +{ + // preserve src alpha value for dest blending, + // src.alpha() will be changed after blending src + int sa = src.alpha(); + if (dest.sixteenBit()) + { + src.blendAlpha16(sa); + dest.blendInvAlpha16(sa); + dest.blendAdd(src); + dest.blendClamp16(); + } + else + { + src.blendAlpha8(sa); + dest.blendInvAlpha8(sa); + dest.blendAdd(src); + dest.blendClamp8(); + } +} + +// Porter-Duff Clear +// component = (source * 0 + destination * 0) +// Src blending function Zero +// Dst blending function Zero +void DColorComposerPorterDuffClear::compose(DColor &dest, DColor src) +{ + src.blendZero(); + dest.blendZero(); + dest.blendAdd(src); +} + +void DColorComposerPorterDuffClear::compose(DColor &dest, DColor src, MultiplicationFlags) +{ + // skip pre- and demultiplication + compose(dest, src); +} + +// Porter-Duff Src +// Normal Painter's algorithm +// component = (source * 1 + destination * 0) +// Src blending function One +// Dst blending function Zero +void DColorComposerPorterDuffSrc::compose(DColor &dest, DColor src) +{ + // src: no-op + dest.blendZero(); + dest.blendAdd(src); +} + +void DColorComposerPorterDuffSrc::compose(DColor &dest, DColor src, MultiplicationFlags) +{ + // skip pre- and demultiplication + compose(dest, src); +} + +// Porter-Duff Src Over +// component = (source * 1 + destination * (1-sa)) +// Src blending function One +// Dst blending function Inv Src Alpha +void DColorComposerPorterDuffSrcOver::compose(DColor &dest, DColor src) +{ + if (dest.sixteenBit()) + { + // src: no-op + dest.blendInvAlpha16(src.alpha()); + dest.blendAdd(src); + dest.blendClamp16(); + } + else + { + // src: no-op + dest.blendInvAlpha8(src.alpha()); + dest.blendAdd(src); + dest.blendClamp8(); + } +} + +// Porter-Duff Dst over +// component = (source * (1.0-da) + destination * 1) +// Src blending function Inv Dst Alpha +// Dst blending function One +void DColorComposerPorterDuffDstOver::compose(DColor &dest, DColor src) +{ + if (dest.sixteenBit()) + { + src.blendInvAlpha16(dest.alpha()); + // dest: no-op + dest.blendAdd(src); + dest.blendClamp16(); + } + else + { + src.blendInvAlpha8(dest.alpha()); + // dest: no-op + dest.blendAdd(src); + dest.blendClamp8(); + } +} + +// Porter-Duff Src In +// component = (source * da + destination * 0) +// Src blending function Dst Alpha +// Dst blending function Zero +void DColorComposerPorterDuffSrcIn::compose(DColor &dest, DColor src) +{ + if (dest.sixteenBit()) + { + src.blendAlpha16(dest.alpha()); + dest.blendZero(); + dest.blendAdd(src); + dest.blendClamp16(); + } + else + { + src.blendAlpha8(dest.alpha()); + dest.blendZero(); + dest.blendAdd(src); + dest.blendClamp8(); + } +} + +// Porter-Duff Dst In +// component = (source * 0 + destination * sa) +// Src blending function Zero +// Dst blending function Src Alpha +void DColorComposerPorterDuffDstIn::compose(DColor &dest, DColor src) +{ + int sa = src.alpha(); + if (dest.sixteenBit()) + { + src.blendZero(); + dest.blendAlpha16(sa); + dest.blendAdd(src); + dest.blendClamp16(); + } + else + { + src.blendZero(); + dest.blendAlpha8(sa); + dest.blendAdd(src); + dest.blendClamp8(); + } +} + +// Porter-Duff Src Out +// component = (source * (1-da) + destination * 0) +// Src blending function Inv Dst Alpha +// Dst blending function Zero +void DColorComposerPorterDuffSrcOut::compose(DColor &dest, DColor src) +{ + if (dest.sixteenBit()) + { + src.blendInvAlpha16(dest.alpha()); + dest.blendZero(); + dest.blendAdd(src); + dest.blendClamp16(); + } + else + { + src.blendInvAlpha8(dest.alpha()); + dest.blendZero(); + dest.blendAdd(src); + dest.blendClamp8(); + } +} + +// Porter-Duff Dst Out +// component = (source * 0 + destination * (1-sa)) +// Src blending function Zero +// Dst blending function Inv Src Alpha +void DColorComposerPorterDuffDstOut::compose(DColor &dest, DColor src) +{ + int sa = src.alpha(); + if (dest.sixteenBit()) + { + src.blendZero(); + dest.blendInvAlpha16(sa); + dest.blendAdd(src); + dest.blendClamp16(); + } + else + { + src.blendZero(); + dest.blendInvAlpha8(sa); + dest.blendAdd(src); + dest.blendClamp8(); + } +} + +// Porter-Duff Src Atop +// component = (source * da + destination * (1-sa)) +// Src blending function Dst Alpha +// Dst blending function Inv Src Alpha +void DColorComposerPorterDuffSrcAtop::compose(DColor &dest, DColor src) +{ + int sa = src.alpha(); + if (dest.sixteenBit()) + { + src.blendAlpha16(dest.alpha()); + dest.blendInvAlpha16(sa); + dest.blendAdd(src); + dest.blendClamp16(); + } + else + { + src.blendAlpha8(dest.alpha()); + dest.blendInvAlpha8(sa); + dest.blendAdd(src); + dest.blendClamp8(); + } +} + +// Porter-Duff Dst Atop +// component = (source * (1-da) + destination * sa) +// Src blending function Inv Dest Alpha +// Dst blending function Src Alpha +void DColorComposerPorterDuffDstAtop::compose(DColor &dest, DColor src) +{ + int sa = src.alpha(); + if (dest.sixteenBit()) + { + src.blendInvAlpha16(dest.alpha()); + dest.blendAlpha16(sa); + dest.blendAdd(src); + dest.blendClamp16(); + } + else + { + src.blendInvAlpha8(dest.alpha()); + dest.blendInvAlpha8(sa); + dest.blendAdd(src); + dest.blendClamp8(); + } +} + +// Porter-Duff Xor +// component = (source * (1-da) + destination * (1-sa)) +// Src blending function Inv Dst Alpha +// Dst blending function Inv Src Alpha +void DColorComposerPorterDuffXor::compose(DColor &dest, DColor src) +{ + int sa = src.alpha(); + if (dest.sixteenBit()) + { + src.blendInvAlpha16(dest.alpha()); + dest.blendInvAlpha16(sa); + dest.blendAdd(src); + dest.blendClamp16(); + } + else + { + src.blendInvAlpha8(dest.alpha()); + dest.blendInvAlpha8(sa); + dest.blendAdd(src); + dest.blendClamp8(); + } +} + +// ----------------------------------------------------------------------- + +void DColorComposer::compose(DColor &dest, DColor src, DColorComposer::MultiplicationFlags multiplicationFlags) +{ + if (multiplicationFlags & PremultiplySrc) + src.premultiply(); + if (multiplicationFlags & PremultiplyDst) + dest.premultiply(); + + compose(dest, src); + + if (multiplicationFlags & DemultiplyDst) + dest.demultiply(); +} + +DColorComposer *DColorComposer::getComposer(DColorComposer::CompositingOperation rule) +{ + switch(rule) + { + case PorterDuffNone: + return new DColorComposerPorterDuffNone; + case PorterDuffClear: + return new DColorComposerPorterDuffClear; + case PorterDuffSrc: + return new DColorComposerPorterDuffSrc; + case PorterDuffSrcOver: + return new DColorComposerPorterDuffSrcOver; + case PorterDuffDstOver: + return new DColorComposerPorterDuffDstOver; + case PorterDuffSrcIn: + return new DColorComposerPorterDuffSrcIn; + case PorterDuffDstIn: + return new DColorComposerPorterDuffDstIn; + case PorterDuffSrcOut: + return new DColorComposerPorterDuffSrcOut; + case PorterDuffDstOut: + return new DColorComposerPorterDuffDstOut; + case PorterDuffSrcAtop: + return new DColorComposerPorterDuffDstOut; + case PorterDuffDstAtop: + return new DColorComposerPorterDuffDstOut; + case PorterDuffXor: + return new DColorComposerPorterDuffDstOut; + } + return 0; +} + +} // namespace DigiKam diff --git a/src/libs/dimg/dcolorcomposer.h b/src/libs/dimg/dcolorcomposer.h new file mode 100644 index 00000000..3e885ca8 --- /dev/null +++ b/src/libs/dimg/dcolorcomposer.h @@ -0,0 +1,133 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-03-02 + * Description : DColor methods for composing + * + * Copyright (C) 2006-2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DCOLORCOMPOSER_H +#define DCOLORCOMPOSER_H + +// Local includes. + +#include "dcolor.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT DColorComposer +{ +public: + /** The available rules to combine src and destination color. + + For the Porter-Duff rules, the formula is + component = (source * fs + destination * fd) + where + fs, fd according to the following table with + sa = source alpha, + da = destination alpha: + + None fs: sa fd: 1.0-sa + Clear fs: 0.0 fd: 0.0 + Src fs: 1.0 fd: 0.0 + Src Over fs: 1.0 fd: 1.0-sa + Dst Over fs: 1.0-da fd: 1.0 + Src In fs: da fd: 0.0 + Dst In fs: 0.0 fd: sa + Src Out fs: 1.0-da fd: 0.0 + Dst Out fs: 0.0 fd: 1.0-sa + + Src Atop fs: da fd: 1.0-sa + Dst Atop fs: 1.0-da fd: sa + Xor fs: 1.0-da fd: 1.0-sa + + None is the default, classical blending mode, a "Src over" simplification: + Blend non-premultiplied RGBA data "src over" a fully opaque background. + Src is the painter's algorithm. + All other operations require premultiplied colors. + The documentation of java.awt.AlphaComposite (Java 1.5) + provides a good introduction and documentation on Porter Duff. + */ + + enum CompositingOperation + { + PorterDuffNone, + PorterDuffClear, + PorterDuffSrc, + PorterDuffSrcOver, + PorterDuffDstOver, + PorterDuffSrcIn, + PorterDuffDstIn, + PorterDuffSrcOut, + PorterDuffDstOut, + PorterDuffSrcAtop, + PorterDuffDstAtop, + PorterDuffXor + }; + + enum MultiplicationFlags + { + NoMultiplication = 0x00, + PremultiplySrc = 0x01, + PremultiplyDst = 0x02, + DemultiplyDst = 0x04, + + MultiplicationFlagsDImg = PremultiplySrc | PremultiplyDst | DemultiplyDst, + MultiplicationFlagsPremultipliedColorOnDImg = PremultiplyDst | DemultiplyDst + }; + + /** + Retrieve a DColorComposer object for one of the predefined rules. + The object needs to be deleted by the caller. + */ + static DColorComposer *getComposer(CompositingOperation rule); + + /** + Carry out the actual composition process. + Src and Dest are composed and the result is written to dest. + No pre-/demultiplication is done by this method, use the other overloaded + methods, which call this method, if you need pre- or demultiplication + (you need it if any of the colors are read from or written to a DImg). + + If you just pass the object to a DImg method, you do not need to call this. + Call this function if you want to compose two colors. + Implement this function if you create a custom DColorComposer. + + The bit depth of source and destination color must be identical. + */ + virtual void compose(DColor &dest, DColor src) = 0; + + /** + Compose the two colors by calling compose(dest, src). + Pre- and demultiplication operations are done as specified. + For PorterDuff operations except PorterDuffNone, you need + + - PremultiplySrc if src is not premultiplied (read from a DImg) + - PremultiplyDst if dst is not premultiplied (read from a DImg) + - DemultiplyDst if dst will be written to non-premultiplied data (a DImg) + */ + virtual void compose(DColor &dest, DColor src, MultiplicationFlags multiplicationFlags); + + virtual ~DColorComposer(){}; +}; + +} // namespace Digikam + +#endif // DCOLORCOMPOSER_H diff --git a/src/libs/dimg/dcolorpixelaccess.h b/src/libs/dimg/dcolorpixelaccess.h new file mode 100644 index 00000000..30695c3e --- /dev/null +++ b/src/libs/dimg/dcolorpixelaccess.h @@ -0,0 +1,78 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-03-02 + * Description : methods to access on pixels color + * + * Copyright (C) 2005-2007 by Gilles Caulier + * Copyright (C) 2006-2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DCOLORPIXELACCESS_H +#define DCOLORPIXELACCESS_H + +namespace Digikam +{ + +// These methods are used in quite a few image effects, +// typically in loops iterating the data. +// Providing them as inline methods allows the compiler to optimize better. + +inline void DColor::setColor(const uchar *data, bool sixteenBit) +{ + m_sixteenBit = sixteenBit; + + if (!sixteenBit) // 8 bits image + { + setBlue (data[0]); + setGreen(data[1]); + setRed (data[2]); + setAlpha(data[3]); + } + else // 16 bits image + { + unsigned short* data16 = (unsigned short*)data; + setBlue (data16[0]); + setGreen(data16[1]); + setRed (data16[2]); + setAlpha(data16[3]); + } +} + +inline void DColor::setPixel(uchar *data) const +{ + if (sixteenBit()) // 16 bits image. + { + unsigned short *data16 = (unsigned short *)data; + data16[0] = (unsigned short)blue(); + data16[1] = (unsigned short)green(); + data16[2] = (unsigned short)red(); + data16[3] = (unsigned short)alpha(); + } + else // 8 bits image. + { + data[0] = (uchar)blue(); + data[1] = (uchar)green(); + data[2] = (uchar)red(); + data[3] = (uchar)alpha(); + } +} + + +} // namespace Digikam + +#endif // DCOLORPIXELACCESS_H diff --git a/src/libs/dimg/ddebug.cpp b/src/libs/dimg/ddebug.cpp new file mode 100644 index 00000000..3f8f07c4 --- /dev/null +++ b/src/libs/dimg/ddebug.cpp @@ -0,0 +1,92 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-06-11 + * Description : thread safe debugging. + * + * See B.K.O #133026: because kdDebug() is not thread-safe + * we need to use a dedicaced debug statements in threaded + * implementation to prevent crash. + * + * Copyright (C) 2006-2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include + +// Local includes. + +#include "ddebug.h" + +#undef DDebug +#undef kdDebug + +namespace Digikam +{ + +//static KStaticDeleter deleter; +static TQMutex *_ddebug_mutex_ = 0; + +Ddbgstream::Ddbgstream(kdbgstream stream) + : kdbgstream(stream) +{ + // using a static variable here - we can safely assume that kdDebug + // is called at least once from the main thread before threads start. + if (!_ddebug_mutex_) + { + // leak the mutex object for simplicity + _ddebug_mutex_ = new TQMutex; + //deleter.setObject(mutex, new TQMutex); + //TDEGlobal::unregisterStaticDeleter(&deleter); + } + _ddebug_mutex_->lock(); +} + +Ddbgstream::~Ddbgstream() +{ + _ddebug_mutex_->unlock(); +} + +Dndbgstream::Dndbgstream(kndbgstream stream) + : kndbgstream(stream) +{ + // using a static variable here - we can safely assume that kdDebug + // is called at least once from the main thread before threads start. + if (!_ddebug_mutex_) + { + // leak the mutex object for simplicity + _ddebug_mutex_ = new TQMutex; + //deleter.setObject(mutex, new TQMutex); + //TDEGlobal::unregisterStaticDeleter(&deleter); + } + _ddebug_mutex_->lock(); +} + +Dndbgstream::~Dndbgstream() +{ + _ddebug_mutex_->unlock(); +} + +} // namespace Digikam + +Digikam::Ddbgstream DDebug(int area) { return Digikam::Ddbgstream(kdDebug(area)); } +Digikam::Ddbgstream DError(int area) { return Digikam::Ddbgstream(kdError(area)); } +Digikam::Ddbgstream DWarning(int area) { return Digikam::Ddbgstream(kdWarning(area)); } + +Digikam::Dndbgstream DnDebug(int area) { return Digikam::Dndbgstream(kndDebug(area)); } + diff --git a/src/libs/dimg/ddebug.h b/src/libs/dimg/ddebug.h new file mode 100644 index 00000000..1ea337eb --- /dev/null +++ b/src/libs/dimg/ddebug.h @@ -0,0 +1,73 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-06-11 + * Description : thread safe debugging. + * + * See B.K.O #133026: because kdDebug() is not thread-safe + * we need to use a dedicaced debug statements in threaded + * implementation to prevent crash. + * + * Copyright (C) 2006 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef _DDEBUG_H_ +#define _DDEBUG_H_ + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT Ddbgstream : public kdbgstream +{ + +public: + + Ddbgstream(kdbgstream stream); + ~Ddbgstream(); +}; + +class DIGIKAM_EXPORT Dndbgstream : public kndbgstream +{ + +public: + + Dndbgstream(kndbgstream stream); + ~Dndbgstream(); +}; + +} // namespace Digikam + +DIGIKAM_EXPORT Digikam::Ddbgstream DDebug(int area = 0); +DIGIKAM_EXPORT Digikam::Ddbgstream DWarning(int area = 0); +DIGIKAM_EXPORT Digikam::Ddbgstream DError(int area = 0); + +DIGIKAM_EXPORT Digikam::Dndbgstream DnDebug(int area = 0); + +#ifdef NDEBUG +#define DDebug DnDebug +#endif + +#endif // _DDEBUG_H_ + diff --git a/src/libs/dimg/dimg.cpp b/src/libs/dimg/dimg.cpp new file mode 100644 index 00000000..d61f2dd0 --- /dev/null +++ b/src/libs/dimg/dimg.cpp @@ -0,0 +1,1700 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-14 + * Description : digiKam 8/16 bits image management API + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2005-2007 by Gilles Caulier + * Copyright (C) 2006-2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// C ANSI includes. + +extern "C" +{ +#if !defined(__STDC_LIMIT_MACROS) +#define __STDC_LIMIT_MACROS +#endif +#include +} + +// C++ includes. + +#include + +// TQt includes. + +#include +#include +#include + +// LibKDcraw includes. + +#include +#include + +#if KDCRAW_VERSION < 0x000106 +#include +#endif + +// Local includes. + +#include "pngloader.h" +#include "jpegloader.h" +#include "tiffloader.h" +#include "ppmloader.h" +#include "rawloader.h" +#include "jp2kloader.h" +#include "qimageloader.h" +#include "icctransform.h" +#include "exposurecontainer.h" +#include "ddebug.h" +#include "dimgprivate.h" +#include "dimgloaderobserver.h" +#include "dimg.h" + +typedef uint64_t ullong; +typedef int64_t llong; + +namespace Digikam +{ + +DImg::DImg() + : m_priv(new DImgPrivate) +{ +} + +DImg::DImg(const TQCString& filePath, DImgLoaderObserver *observer, + DRawDecoding rawDecodingSettings) + : m_priv(new DImgPrivate) +{ + load(filePath, observer, rawDecodingSettings); +} + +DImg::DImg(const TQString& filePath, DImgLoaderObserver *observer, + DRawDecoding rawDecodingSettings) + : m_priv(new DImgPrivate) +{ + load(filePath, observer, rawDecodingSettings); +} + +DImg::DImg(const DImg& image) +{ + m_priv = image.m_priv; + m_priv->ref(); +} + +DImg::DImg(uint width, uint height, bool sixteenBit, bool alpha, uchar* data, bool copyData) + : m_priv(new DImgPrivate) +{ + putImageData(width, height, sixteenBit, alpha, data, copyData); +} + +DImg::DImg(const DImg &image, int w, int h) + : m_priv(new DImgPrivate) +{ + // This private constructor creates a copy of everything except the data. + // The image size is set to the given values and a buffer corresponding to these values is allocated. + // This is used by copy and scale. + copyImageData(image.m_priv); + copyMetaData(image.m_priv); + setImageDimension(w, h); + allocateData(); +} + +DImg::DImg(const TQImage& image) + : m_priv(new DImgPrivate) +{ + if (!image.isNull()) + { + TQImage target = image.convertDepth(32); + + uint w = target.width(); + uint h = target.height(); + uchar* data = new uchar[w*h*4]; + uint* sptr = (uint*)target.bits(); + uchar* dptr = data; + + for (uint i = 0 ; i < w*h ; i++) + { + dptr[0] = tqBlue(*sptr); + dptr[1] = tqGreen(*sptr); + dptr[2] = tqRed(*sptr); + dptr[3] = tqAlpha(*sptr); + + dptr += 4; + sptr++; + } + + putImageData(w, h, false, image.hasAlphaBuffer(), data, false); + } +} + +DImg::~DImg() +{ + if (m_priv->deref()) + delete m_priv; +} + + +//--------------------------------------------------------------------------------------------------- +// data management + + +DImg& DImg::operator=(const DImg& image) +{ + if (m_priv == image.m_priv) + return *this; + + if (m_priv->deref()) + { + delete m_priv; + m_priv = 0; + } + + m_priv = image.m_priv; + m_priv->ref(); + + return *this; +} + +bool DImg::operator==(const DImg& image) const +{ + return m_priv == image.m_priv; +} + +void DImg::reset(void) +{ + if (m_priv->deref()) + delete m_priv; + + m_priv = new DImgPrivate; +} + +void DImg::detach() +{ + // are we being shared? + if (m_priv->count <= 1) + { + return; + } + + DImgPrivate* old = m_priv; + + m_priv = new DImgPrivate; + copyImageData(old); + copyMetaData(old); + + if (old->data) + { + int size = allocateData(); + memcpy(m_priv->data, old->data, size); + } + + old->deref(); +} + +void DImg::putImageData(uint width, uint height, bool sixteenBit, bool alpha, uchar *data, bool copyData) +{ + // set image data, metadata is untouched + + bool null = (width == 0) || (height == 0); + // allocateData, or code below will set null to false + setImageData(true, width, height, sixteenBit, alpha); + + // replace data + delete [] m_priv->data; + if (null) + { + // image is null - no data + m_priv->data = 0; + } + else if (copyData) + { + int size = allocateData(); + if (data) + memcpy(m_priv->data, data, size); + } + else + { + if (data) + { + m_priv->data = data; + m_priv->null = false; + } + else + allocateData(); + } +} + +void DImg::putImageData(uchar *data, bool copyData) +{ + if (!data) + { + delete [] m_priv->data; + m_priv->data = 0; + m_priv->null = true; + } + else if (copyData) + { + memcpy(m_priv->data, data, numBytes()); + } + else + { + m_priv->data = data; + } +} + +void DImg::resetMetaData() +{ + m_priv->attributes.clear(); + m_priv->embeddedText.clear(); + m_priv->metaData.clear(); +} + +uchar *DImg::stripImageData() +{ + uchar *data = m_priv->data; + m_priv->data = 0; + m_priv->null = true; + return data; +} + +void DImg::copyMetaData(const DImgPrivate *src) +{ + m_priv->isReadOnly = src->isReadOnly; + m_priv->attributes = src->attributes; + m_priv->embeddedText = src->embeddedText; + + // since qbytearrays are explicitly shared, we need to make sure that they are + // detached from any shared references + + for (TQMap::const_iterator it = src->metaData.begin(); + it != src->metaData.end(); ++it) + { + m_priv->metaData.insert(it.key(), it.data().copy()); + } +} + +void DImg::copyImageData(const DImgPrivate *src) +{ + setImageData(src->null, src->width, src->height, src->sixteenBit, src->alpha); +} + +int DImg::allocateData() +{ + int size = m_priv->width * m_priv->height * (m_priv->sixteenBit ? 8 : 4); + m_priv->data = new uchar[size]; + m_priv->null = false; + return size; +} + +void DImg::setImageDimension(uint width, uint height) +{ + m_priv->width = width; + m_priv->height = height; +} + +void DImg::setImageData(bool null, uint width, uint height, bool sixteenBit, bool alpha) +{ + m_priv->null = null; + m_priv->width = width; + m_priv->height = height; + m_priv->alpha = alpha; + m_priv->sixteenBit = sixteenBit; +} + + +//--------------------------------------------------------------------------------------------------- +// load and save + + +bool DImg::load(const TQString& filePath, DImgLoaderObserver *observer, + DRawDecoding rawDecodingSettings) +{ + FORMAT format = fileFormat(filePath); + + switch (format) + { + case(NONE): + { + DDebug() << filePath << " : Unknown image format !!!" << endl; + return false; + break; + } + case(JPEG): + { + DDebug() << filePath << " : JPEG file identified" << endl; + JPEGLoader loader(this); + if (loader.load(filePath, observer)) + { + m_priv->null = false; + m_priv->alpha = loader.hasAlpha(); + m_priv->sixteenBit = loader.sixteenBit(); + m_priv->isReadOnly = loader.isReadOnly(); + return true; + } + break; + } + case(TIFF): + { + DDebug() << filePath << " : TIFF file identified" << endl; + TIFFLoader loader(this); + if (loader.load(filePath, observer)) + { + m_priv->null = false; + m_priv->alpha = loader.hasAlpha(); + m_priv->sixteenBit = loader.sixteenBit(); + m_priv->isReadOnly = loader.isReadOnly(); + return true; + } + break; + } + case(PNG): + { + DDebug() << filePath << " : PNG file identified" << endl; + PNGLoader loader(this); + if (loader.load(filePath, observer)) + { + m_priv->null = false; + m_priv->alpha = loader.hasAlpha(); + m_priv->sixteenBit = loader.sixteenBit(); + m_priv->isReadOnly = loader.isReadOnly(); + return true; + } + break; + } + case(PPM): + { + DDebug() << filePath << " : PPM file identified" << endl; + PPMLoader loader(this); + if (loader.load(filePath, observer)) + { + m_priv->null = false; + m_priv->alpha = loader.hasAlpha(); + m_priv->sixteenBit = loader.sixteenBit(); + m_priv->isReadOnly = loader.isReadOnly(); + return true; + } + break; + } + case(RAW): + { + DDebug() << filePath << " : RAW file identified" << endl; + RAWLoader loader(this, rawDecodingSettings); + if (loader.load(filePath, observer)) + { + m_priv->null = false; + m_priv->alpha = loader.hasAlpha(); + m_priv->sixteenBit = loader.sixteenBit(); + m_priv->isReadOnly = loader.isReadOnly(); + return true; + } + break; + } + case(JP2K): + { + DDebug() << filePath << " : JPEG2000 file identified" << endl; + JP2KLoader loader(this); + if (loader.load(filePath, observer)) + { + m_priv->null = false; + m_priv->alpha = loader.hasAlpha(); + m_priv->sixteenBit = loader.sixteenBit(); + m_priv->isReadOnly = loader.isReadOnly(); + return true; + } + break; + } + default: + { + DDebug() << filePath << " : TQIMAGE file identified" << endl; + TQImageLoader loader(this); + if (loader.load(filePath, observer)) + { + m_priv->null = false; + m_priv->alpha = loader.hasAlpha(); + m_priv->sixteenBit = loader.sixteenBit(); + m_priv->isReadOnly = loader.isReadOnly(); + return true; + } + break; + } + } + + return false; +} + +bool DImg::save(const TQString& filePath, const TQString& format, DImgLoaderObserver *observer) +{ + if (isNull()) + return false; + + if (format.isEmpty()) + return false; + + TQString frm = format.upper(); + + if (frm == "JPEG" || frm == "JPG" || frm == "JPE") + { + JPEGLoader loader(this); + return loader.save(filePath, observer); + } + else if (frm == "PNG") + { + PNGLoader loader(this); + return loader.save(filePath, observer); + } + else if (frm == "TIFF" || frm == "TIF") + { + TIFFLoader loader(this); + return loader.save(filePath, observer); + } + else if (frm == "PPM") + { + PPMLoader loader(this); + return loader.save(filePath, observer); + } + if (frm == "JP2" || frm == "JPX" || frm == "JPC" || frm == "PGX") + { + JP2KLoader loader(this); + return loader.save(filePath, observer); + } + else + { + setAttribute("format", format); + TQImageLoader loader(this); + return loader.save(filePath, observer); + } + + return false; +} + +DImg::FORMAT DImg::fileFormat(const TQString& filePath) +{ + if ( filePath.isNull() ) + return NONE; + + // In first we trying to check the file extension. This is mandatory because + // some tiff files are detected like RAW files by dcraw::identify method. + + TQFileInfo fileInfo(filePath); + if (!fileInfo.exists()) + { + DDebug() << k_funcinfo << "File \"" << filePath << "\" does not exist" << endl; + return NONE; + } + +#if KDCRAW_VERSION < 0x000106 + TQString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles()); +#else + TQString rawFilesExt(KDcrawIface::KDcraw::rawFiles()); +#endif + TQString ext = fileInfo.extension(false).upper(); + + if (!ext.isEmpty()) + { + if (ext == TQString("JPEG") || ext == TQString("JPG") || ext == TQString("JPE")) + return JPEG; + else if (ext == TQString("PNG")) + return PNG; + else if (ext == TQString("TIFF") || ext == TQString("TIF")) + return TIFF; + else if (rawFilesExt.upper().contains(ext)) + return RAW; + if (ext == TQString("JP2") || ext == TQString("JPX") || // JPEG2000 file format + ext == TQString("JPC") || // JPEG2000 code stream + ext == TQString("PGX")) // JPEG2000 WM format + return JP2K; + } + + // In second, we trying to parse file header. + + FILE* f = fopen(TQFile::encodeName(filePath), "rb"); + + if (!f) + { + DDebug() << k_funcinfo << "Failed to open file \"" << filePath << "\"" << endl; + return NONE; + } + + const int headerLen = 9; + unsigned char header[headerLen]; + + if (fread(&header, headerLen, 1, f) != 1) + { + DDebug() << k_funcinfo << "Failed to read header of file \"" << filePath << "\"" << endl; + fclose(f); + return NONE; + } + + fclose(f); + + KDcrawIface::DcrawInfoContainer dcrawIdentify; + KDcrawIface::KDcraw::rawFileIdentify(dcrawIdentify, filePath); + uchar jpegID[2] = { 0xFF, 0xD8 }; + uchar tiffBigID[2] = { 0x4D, 0x4D }; + uchar tiffLilID[2] = { 0x49, 0x49 }; + uchar pngID[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; + uchar jp2ID[5] = { 0x6A, 0x50, 0x20, 0x20, 0x0D, }; + uchar jpcID[2] = { 0xFF, 0x4F }; + + if (memcmp(&header, &jpegID, 2) == 0) // JPEG file ? + { + return JPEG; + } + else if (memcmp(&header, &pngID, 8) == 0) // PNG file ? + { + return PNG; + } + else if (memcmp(&header[0], "P", 1) == 0 && + memcmp(&header[2], "\n", 1) == 0) // PPM 16 bits file ? + { + int width, height, rgbmax; + char nl; + FILE *file = fopen(TQFile::encodeName(filePath), "rb"); + + if (fscanf (file, "P6 %d %d %d%c", &width, &height, &rgbmax, &nl) == 4) + { + if (rgbmax > 255) + { + pclose (file); + return PPM; + } + } + + pclose (file); + } + else if (dcrawIdentify.isDecodable) + { + // RAW File test using dcraw::identify method. + // Need to test it before TIFF because any RAW file + // formats using TIFF header. + return RAW; + } + else if (memcmp(&header, &tiffBigID, 2) == 0 || // TIFF file ? + memcmp(&header, &tiffLilID, 2) == 0) + { + return TIFF; + } + else if (memcmp(&header[4], &jp2ID, 5) == 0 || // JPEG2000 file ? + memcmp(&header, &jpcID, 2) == 0) + { + return JP2K; + } + + // In others cases, TQImage will be used to try to open file. + return TQIMAGE; +} + + +//--------------------------------------------------------------------------------------------------- +// accessing properties + + +bool DImg::isNull() const +{ + return m_priv->null; +} + +uint DImg::width() const +{ + return m_priv->width; +} + +uint DImg::height() const +{ + return m_priv->height; +} + +TQSize DImg::size() const +{ + return TQSize(m_priv->width, m_priv->height); +} + +uchar* DImg::bits() const +{ + return m_priv->data; +} + +uchar* DImg::scanLine(uint i) const +{ + if ( i >= height() ) + return 0; + + uchar *data = bits() + (width() * bytesDepth() * i); + return data; +} + +bool DImg::hasAlpha() const +{ + return m_priv->alpha; +} + +bool DImg::sixteenBit() const +{ + return m_priv->sixteenBit; +} + +bool DImg::isReadOnly() const +{ + return m_priv->isReadOnly; +} + +bool DImg::getICCProfilFromFile(const TQString& filePath) +{ + TQFile file(filePath); + if ( !file.open(IO_ReadOnly) ) + return false; + + TQByteArray data(file.size()); + TQDataStream stream( &file ); + stream.readRawBytes(data.data(), data.size()); + setICCProfil(data); + file.close(); + return true; +} + +bool DImg::setICCProfilToFile(const TQString& filePath) +{ + TQFile file(filePath); + if ( !file.open(IO_WriteOnly) ) + return false; + + TQByteArray data(getICCProfil()); + TQDataStream stream( &file ); + stream.writeRawBytes(data.data(), data.size()); + file.close(); + return true; +} + +TQByteArray DImg::getComments() const +{ + return metadata(COM); +} + +TQByteArray DImg::getExif() const +{ + return metadata(EXIF); +} + +TQByteArray DImg::getIptc() const +{ + return metadata(IPTC); +} + +TQByteArray DImg::getICCProfil() const +{ + return metadata(ICC); +} + +void DImg::setComments(const TQByteArray& commentsData) +{ + m_priv->metaData.replace(COM, commentsData); +} + +void DImg::setExif(const TQByteArray& exifData) +{ + m_priv->metaData.replace(EXIF, exifData); +} + +void DImg::setIptc(const TQByteArray& iptcData) +{ + m_priv->metaData.replace(IPTC, iptcData); +} + +void DImg::setICCProfil(const TQByteArray& profile) +{ + m_priv->metaData.replace(ICC, profile); +} + +TQByteArray DImg::metadata(DImg::METADATA key) const +{ + typedef TQMap MetaDataMap; + + for (MetaDataMap::iterator it = m_priv->metaData.begin(); it != m_priv->metaData.end(); ++it) + { + if (it.key() == key) + return it.data(); + } + + return TQByteArray(); +} + +uint DImg::numBytes() const +{ + return (width() * height() * bytesDepth()); +} + +uint DImg::numPixels() const +{ + return (width() * height()); +} + +int DImg::bytesDepth() const +{ + if (sixteenBit()) + return 8; + + return 4; +} + +int DImg::bitsDepth() const +{ + if (sixteenBit()) + return 16; + + return 8; +} + +void DImg::setAttribute(const TQString& key, const TQVariant& value) +{ + m_priv->attributes.insert(key, value); +} + +TQVariant DImg::attribute(const TQString& key) const +{ + if (m_priv->attributes.contains(key)) + return m_priv->attributes[key]; + + return TQVariant(); +} + +void DImg::setEmbeddedText(const TQString& key, const TQString& text) +{ + m_priv->embeddedText.insert(key, text); +} + +TQString DImg::embeddedText(const TQString& key) const +{ + if (m_priv->embeddedText.contains(key)) + return m_priv->embeddedText[key]; + + return TQString(); +} + +DColor DImg::getPixelColor(uint x, uint y) const +{ + if (isNull() || x > width() || y > height()) + { + DDebug() << k_funcinfo << " : wrong pixel position!" << endl; + return DColor(); + } + + uchar *data = bits() + x*bytesDepth() + (width()*y*bytesDepth()); + + return( DColor(data, sixteenBit()) ); +} + +void DImg::setPixelColor(uint x, uint y, DColor color) +{ + if (isNull() || x > width() || y > height()) + { + DDebug() << k_funcinfo << " : wrong pixel position!" << endl; + return; + } + + if (color.sixteenBit() != sixteenBit()) + { + DDebug() << k_funcinfo << " : wrong color depth!" << endl; + return; + } + + uchar *data = bits() + x*bytesDepth() + (width()*y*bytesDepth()); + color.setPixel(data); +} + + +//--------------------------------------------------------------------------------------------------- +// copying operations + + +DImg DImg::copy() +{ + DImg img(*this); + img.detach(); + return img; +} + +DImg DImg::copyImageData() +{ + DImg img(width(), height(), sixteenBit(), hasAlpha(), bits(), true); + return img; +} + +DImg DImg::copyMetaData() +{ + DImg img; + // copy width, height, alpha, sixteenBit, null + img.copyImageData(m_priv); + // deeply copy metadata + img.copyMetaData(m_priv); + // set image to null + img.m_priv->null = true; + return img; +} + +DImg DImg::copy(TQRect rect) +{ + return copy(rect.x(), rect.y(), rect.width(), rect.height()); +} + +DImg DImg::copy(int x, int y, int w, int h) +{ + if ( isNull() || w <= 0 || h <= 0) + { + DDebug() << k_funcinfo << " : return null image!" << endl; + return DImg(); + } + + DImg image(*this, w, h); + image.bitBltImage(this, x, y, w, h, 0, 0); + + return image; +} + + +//--------------------------------------------------------------------------------------------------- +// bitwise operations + + +void DImg::bitBltImage(const DImg* src, int dx, int dy) +{ + bitBltImage(src, 0, 0, src->width(), src->height(), dx, dy); +} + +void DImg::bitBltImage(const DImg* src, int sx, int sy, int dx, int dy) +{ + bitBltImage(src, sx, sy, src->width() - sx, src->height() - sy, dx, dy); +} + +void DImg::bitBltImage(const DImg* src, int sx, int sy, int w, int h, int dx, int dy) +{ + if (isNull()) + return; + + if (src->sixteenBit() != sixteenBit()) + { + DWarning() << "Blitting from 8-bit to 16-bit or vice versa is not supported" << endl; + return; + } + + if (w == -1 && h == -1) + { + w = src->width(); + h = src->height(); + } + + bitBlt(src->bits(), bits(), sx, sy, w, h, dx, dy, + src->width(), src->height(), width(), height(), sixteenBit(), src->bytesDepth(), bytesDepth()); +} + +void DImg::bitBltImage(const uchar* src, int sx, int sy, int w, int h, int dx, int dy, + uint swidth, uint sheight, int sdepth) +{ + if (isNull()) + return; + + if (bytesDepth() != sdepth) + { + DWarning() << "Blitting from 8-bit to 16-bit or vice versa is not supported" << endl; + return; + } + + if (w == -1 && h == -1) + { + w = swidth; + h = sheight; + } + + bitBlt(src, bits(), sx, sy, w, h, dx, dy, swidth, sheight, width(), height(), sixteenBit(), sdepth, bytesDepth()); +} + +bool DImg::normalizeRegionArguments(int &sx, int &sy, int &w, int &h, int &dx, int &dy, + uint swidth, uint sheight, uint dwidth, uint dheight) +{ + if (sx < 0) + { + // sx is negative, so + is - and - is + + dx -= sx; + w += sx; + sx = 0; + } + + if (sy < 0) + { + dy -= sy; + h += sy; + sy = 0; + } + + if (dx < 0) + { + sx -= dx; + w += dx; + dx = 0; + } + + if (dy < 0) + { + sy -= dy; + h += dy; + dy = 0; + } + + if (sx + w > (int)swidth) + { + w = swidth - sx; + } + + if (sy + h > (int)sheight) + { + h = sheight - sy; + } + + if (dx + w > (int)dwidth) + { + w = dwidth - dx; + } + + if (dy + h > (int)dheight) + { + h = dheight - dy; + } + + // Nothing left to copy + if (w <= 0 || h <= 0) + return false; + + return true; +} + +void DImg::bitBlt (const uchar *src, uchar *dest, + int sx, int sy, int w, int h, int dx, int dy, + uint swidth, uint sheight, uint dwidth, uint dheight, + bool /*sixteenBit*/, int sdepth, int ddepth) +{ + // Normalize + if (!normalizeRegionArguments(sx, sy, w, h, dx, dy, swidth, sheight, dwidth, dheight)) + return; + + // Same pixels + if (src == dest && dx==sx && dy==sy) + return; + + const uchar *sptr; + uchar *dptr; + uint slinelength = swidth * sdepth; + uint dlinelength = dwidth * ddepth; + + int scurY = sy; + int dcurY = dy; + for (int j = 0 ; j < h ; j++, scurY++, dcurY++) + { + sptr = &src [ scurY * slinelength ] + sx * sdepth; + dptr = &dest[ dcurY * dlinelength ] + dx * ddepth; + + // plain and simple bitBlt + for (int i = 0; i < w * sdepth ; i++, sptr++, dptr++) + { + *dptr = *sptr; + } + } +} + +void DImg::bitBlendImage(DColorComposer *composer, const DImg* src, + int sx, int sy, int w, int h, int dx, int dy, + DColorComposer::MultiplicationFlags multiplicationFlags) +{ + if (isNull()) + return; + + if (src->sixteenBit() != sixteenBit()) + { + DWarning() << "Blending from 8-bit to 16-bit or vice versa is not supported" << endl; + return; + } + + bitBlend(composer, src->bits(), bits(), sx, sy, w, h, dx, dy, + src->width(), src->height(), width(), height(), sixteenBit(), + src->bytesDepth(), bytesDepth(), multiplicationFlags); +} + +void DImg::bitBlend (DColorComposer *composer, const uchar *src, uchar *dest, + int sx, int sy, int w, int h, int dx, int dy, + uint swidth, uint sheight, uint dwidth, uint dheight, + bool sixteenBit, int sdepth, int ddepth, + DColorComposer::MultiplicationFlags multiplicationFlags) +{ + // Normalize + if (!normalizeRegionArguments(sx, sy, w, h, dx, dy, swidth, sheight, dwidth, dheight)) + return; + + const uchar *sptr; + uchar *dptr; + uint slinelength = swidth * sdepth; + uint dlinelength = dwidth * ddepth; + + int scurY = sy; + int dcurY = dy; + for (int j = 0 ; j < h ; j++, scurY++, dcurY++) + { + sptr = &src [ scurY * slinelength ] + sx * sdepth; + dptr = &dest[ dcurY * dlinelength ] + dx * ddepth; + + // blend src and destination + for (int i = 0 ; i < w ; i++, sptr+=sdepth, dptr+=ddepth) + { + DColor src(sptr, sixteenBit); + DColor dst(dptr, sixteenBit); + + // blend colors + composer->compose(dst, src, multiplicationFlags); + + dst.setPixel(dptr); + } + } +} + + +//--------------------------------------------------------------------------------------------------- +// TQImage / TQPixmap access + + +TQImage DImg::copyTQImage() +{ + if (isNull()) + return TQImage(); + + if (sixteenBit()) + { + DImg img(*this); + img.detach(); + img.convertDepth(32); + return img.copyTQImage(); + } + + TQImage img(width(), height(), 32); + + uchar* sptr = bits(); + uint* dptr = (uint*)img.bits(); + + for (uint i=0; i < width()*height(); i++) + { + *dptr++ = tqRgba(sptr[2], sptr[1], sptr[0], sptr[3]); + sptr += 4; + } + + if (hasAlpha()) + { + img.setAlphaBuffer(true); + } + + return img; +} + +TQImage DImg::copyTQImage(TQRect rect) +{ + return (copyTQImage(rect.x(), rect.y(), rect.width(), rect.height())); +} + +TQImage DImg::copyTQImage(int x, int y, int w, int h) +{ + if (isNull()) + return TQImage(); + + DImg img = copy(x, y, w, h); + + if (img.sixteenBit()) + img.convertDepth(32); + + return img.copyTQImage(); +} + +TQPixmap DImg::convertToPixmap() +{ + if (isNull()) + return TQPixmap(); + + if (sixteenBit()) + { + // make fastaaaa.. + return TQPixmap(copyTQImage(0, 0, width(), height())); + } + + if (TQImage::systemByteOrder() == TQImage::BigEndian) + { + TQImage img(width(), height(), 32); + + uchar* sptr = bits(); + uint* dptr = (uint*)img.bits(); + + for (uint i=0; ihasOutputProfile()) + { + DDebug() << k_funcinfo << " : no monitor ICC profile available!" << endl; + return convertToPixmap(); + } + + DImg img = copy(); + + // Without embedded profile + if (img.getICCProfil().isNull()) + { + TQByteArray fakeProfile; + monitorICCtrans->apply(img, fakeProfile, monitorICCtrans->getRenderingIntent(), + monitorICCtrans->getUseBPC(), false, + monitorICCtrans->inputProfile().isNull()); + } + // With embedded profile. + else + { + monitorICCtrans->getEmbeddedProfile( img ); + monitorICCtrans->apply( img ); + } + + return (img.convertToPixmap()); +} + +TQImage DImg::pureColorMask(ExposureSettingsContainer *expoSettings) +{ + if (isNull() || (!expoSettings->underExposureIndicator && !expoSettings->overExposureIndicator)) + return TQImage(); + + TQImage img(size(), 32); + img.fill(0x00000000); // Full transparent. + img.setAlphaBuffer(true); + + uchar *bits = img.bits(); + int max = sixteenBit() ? 65535 : 255; + int index; + DColor pix; + + for (uint x=0 ; x < width() ; x++) + { + for (uint y=0 ; yunderExposureIndicator && + pix.red() == 0 && pix.green() == 0 && pix.blue() == 0) + { + bits[index ] = expoSettings->underExposureColor.blue(); + bits[index + 1] = expoSettings->underExposureColor.green(); + bits[index + 2] = expoSettings->underExposureColor.red(); + bits[index + 3] = 0xFF; + } + + if (expoSettings->overExposureIndicator && + pix.red() == max && pix.green() == max && pix.blue() == max) + { + bits[index ] = expoSettings->overExposureColor.blue(); + bits[index + 1] = expoSettings->overExposureColor.green(); + bits[index + 2] = expoSettings->overExposureColor.red(); + bits[index + 3] = 0xFF; + } + } + } + + return img; +} + + +//--------------------------------------------------------------------------------------------------- +// basic imaging operations + + +void DImg::crop(TQRect rect) +{ + crop(rect.x(), rect.y(), rect.width(), rect.height()); +} + +void DImg::crop(int x, int y, int w, int h) +{ + if ( isNull() || w <= 0 || h <= 0) + return; + + uint oldw = width(); + uint oldh = height(); + uchar *old = stripImageData(); + + // set new image data, bits(), width(), height() change + setImageDimension(w, h); + allocateData(); + + // copy image region (x|y), wxh, from old data to point (0|0) of new data + bitBlt(old, bits(), x, y, w, h, 0, 0, oldw, oldh, width(), height(), sixteenBit(), bytesDepth(), bytesDepth()); + delete [] old; +} + +void DImg::resize(int w, int h) +{ + if ( w <= 0 || h <= 0) + return; + + DImg image = smoothScale(w, h); + + delete [] m_priv->data; + m_priv->data = image.stripImageData(); + setImageDimension(w, h); +} + +void DImg::rotate(ANGLE angle) +{ + if (isNull()) + return; + + switch (angle) + { + case(ROT90): + { + uint w = height(); + uint h = width(); + + if (sixteenBit()) + { + ullong* newData = new ullong[w*h]; + + ullong *from = (ullong*) m_priv->data; + ullong *to; + + for (int y = w-1; y >=0; y--) + { + to = newData + y; + + for (uint x=0; x < h; x++) + { + *to = *from++; + to += w; + } + } + + setImageDimension(w, h); + + delete [] m_priv->data; + m_priv->data = (uchar*)newData; + } + else + { + uint* newData = new uint[w*h]; + + uint *from = (uint*) m_priv->data; + uint *to; + + for (int y = w-1; y >=0; y--) + { + to = newData + y; + + for (uint x=0; x < h; x++) + { + *to = *from++; + to += w; + } + } + + setImageDimension(w, h); + + delete [] m_priv->data; + m_priv->data = (uchar*)newData; + } + + break; + } + case(ROT180): + { + uint w = width(); + uint h = height(); + + int middle_line = -1; + if (h % 2) + middle_line = h / 2; + + if (sixteenBit()) + { + ullong *line1; + ullong *line2; + + ullong* data = (ullong*) bits(); + ullong tmp; + + // can be done inplace + for (uint y = 0; y < (h+1)/2; y++) + { + line1 = data + y * w; + line2 = data + (h-y) * w; + for (uint x=0; x < w; x++) + { + tmp = *line1; + *line1 = *line2; + *line2 = tmp; + + line1++; + line2--; + if ((int)y == middle_line && x * 2 >= w) + break; + } + } + } + else + { + uint *line1; + uint *line2; + + uint* data = (uint*) bits(); + uint tmp; + + // can be done inplace + for (uint y = 0; y < (h+1)/2; y++) + { + line1 = data + y * w; + line2 = data + (h-y) * w; + + for (uint x=0; x < w; x++) + { + tmp = *line1; + *line1 = *line2; + *line2 = tmp; + + line1++; + line2--; + if ((int)y == middle_line && x * 2 >= w) + break; + } + } + } + + break; + } + case(ROT270): + { + uint w = height(); + uint h = width(); + + if (sixteenBit()) + { + ullong* newData = new ullong[w*h]; + + ullong *from = (ullong*) m_priv->data; + ullong *to; + + for (uint y = 0; y < w; y++) + { + to = newData + y + w*(h-1); + + for (uint x=0; x < h; x++) + { + *to = *from++; + to -= w; + } + } + + setImageDimension(w, h); + + delete [] m_priv->data; + m_priv->data = (uchar*)newData; + } + else + { + uint* newData = new uint[w*h]; + + uint *from = (uint*) m_priv->data; + uint *to; + + for (uint y = 0; y < w; y++) + { + to = newData + y + w*(h-1); + + for (uint x=0; x < h; x++) + { + *to = *from++; + to -= w; + } + } + + setImageDimension(w, h); + + delete [] m_priv->data; + m_priv->data = (uchar*)newData; + } + + break; + } + default: + break; + } +} + +// 15-11-2005: This method have been tested indeep with valgrind by Gilles. + +void DImg::flip(FLIP direction) +{ + if (isNull()) + return; + + switch (direction) + { + case(HORIZONTAL): + { + uint w = width(); + uint h = height(); + + if (sixteenBit()) + { + unsigned short tmp[4]; + unsigned short *beg; + unsigned short *end; + + unsigned short * data = (unsigned short *)bits(); + + // can be done inplace + for (uint y = 0 ; y < h ; y++) + { + beg = data + y * w * 4; + end = beg + (w-1) * 4; + + for (uint x=0 ; x < (w/2) ; x++) + { + memcpy(&tmp, beg, 8); + memcpy(beg, end, 8); + memcpy(end, &tmp, 8); + + beg+=4; + end-=4; + } + } + } + else + { + uchar tmp[4]; + uchar *beg; + uchar *end; + + uchar* data = bits(); + + // can be done inplace + for (uint y = 0 ; y < h ; y++) + { + beg = data + y * w * 4; + end = beg + (w-1) * 4; + + for (uint x=0 ; x < (w/2) ; x++) + { + memcpy(&tmp, beg, 4); + memcpy(beg, end, 4); + memcpy(end, &tmp, 4); + + beg+=4; + end-=4; + } + } + } + + break; + } + case(VERTICAL): + { + uint w = width(); + uint h = height(); + + if (sixteenBit()) + { + unsigned short tmp[4]; + unsigned short *line1; + unsigned short *line2; + + unsigned short* data = (unsigned short*) bits(); + + // can be done inplace + for (uint y = 0 ; y < (h/2) ; y++) + { + line1 = data + y * w * 4; + line2 = data + (h-y-1) * w * 4; + + for (uint x=0 ; x < w ; x++) + { + memcpy(&tmp, line1, 8); + memcpy(line1, line2, 8); + memcpy(line2, &tmp, 8); + + line1+=4; + line2+=4; + } + } + } + else + { + uchar tmp[4]; + uchar *line1; + uchar *line2; + + uchar* data = bits(); + + // can be done inplace + for (uint y = 0 ; y < (h/2) ; y++) + { + line1 = data + y * w * 4; + line2 = data + (h-y-1) * w * 4; + + for (uint x=0 ; x < w ; x++) + { + memcpy(&tmp, line1, 4); + memcpy(line1, line2, 4); + memcpy(line2, &tmp, 4); + + line1+=4; + line2+=4; + } + } + } + + break; + } + default: + break; + } +} + +void DImg::convertToSixteenBit() +{ + convertDepth(64); +} + +void DImg::convertToEightBit() +{ + convertDepth(32); +} + +void DImg::convertToDepthOfImage(const DImg *otherImage) +{ + if (otherImage->sixteenBit()) + convertToSixteenBit(); + else + convertToEightBit(); +} + +void DImg::convertDepth(int depth) +{ + if (isNull()) + return; + + if (depth != 32 && depth != 64) + { + DDebug() << k_funcinfo << " : wrong color depth!" << endl; + return; + } + + if (((depth == 32) && !sixteenBit()) || + ((depth == 64) && sixteenBit())) + return; + + if (depth == 32) + { + // downgrading from 16 bit to 8 bit + + uchar* data = new uchar[width()*height()*4]; + uchar* dptr = data; + ushort* sptr = (ushort*)bits(); + + for (uint i=0; idata; + m_priv->data = data; + m_priv->sixteenBit = false; + } + else if (depth == 64) + { + // upgrading from 8 bit to 16 bit + + uchar* data = new uchar[width()*height()*8]; + ushort* dptr = (ushort*)data; + uchar* sptr = bits(); + + for (uint i=0; idata; + m_priv->data = data; + m_priv->sixteenBit = true; + } +} + +void DImg::fill(DColor color) +{ + if (sixteenBit()) + { + unsigned short *imgData16 = (unsigned short *)m_priv->data; + + for (uint i = 0 ; i < width()*height()*4 ; i+=4) + { + imgData16[ i ] = (unsigned short)color.blue(); + imgData16[i+1] = (unsigned short)color.green(); + imgData16[i+2] = (unsigned short)color.red(); + imgData16[i+3] = (unsigned short)color.alpha(); + } + } + else + { + uchar *imgData = m_priv->data; + + for (uint i = 0 ; i < width()*height()*4 ; i+=4) + { + imgData[ i ] = (uchar)color.blue(); + imgData[i+1] = (uchar)color.green(); + imgData[i+2] = (uchar)color.red(); + imgData[i+3] = (uchar)color.alpha(); + } + } +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/dimg.h b/src/libs/dimg/dimg.h new file mode 100644 index 00000000..48973e47 --- /dev/null +++ b/src/libs/dimg/dimg.h @@ -0,0 +1,353 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-14 + * Description : digiKam 8/16 bits image management API + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2005-2007 by Gilles Caulier + * Copyright (C) 2006-2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DIMG_H +#define DIMG_H + +// TQt includes. + +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "digikam_export.h" +#include "drawdecoding.h" +#include "dcolor.h" +#include "dcolorcomposer.h" + +class TQString; + +namespace Digikam +{ + +class ExposureSettingsContainer; +class DImgPrivate; +class IccTransform; +class DImgLoaderObserver; + +class DIGIKAM_EXPORT DImg +{ +public: + + enum FORMAT + { + NONE = 0, + JPEG, + PNG, + TIFF, + RAW, + PPM, + JP2K, + TQIMAGE + }; + + enum METADATA + { + COM, // JFIF comments section data. + EXIF, // EXIF meta-data. + IPTC, // IPTC meta-data. + ICC // ICC color profile. + }; + + enum ANGLE + { + ROT90, + ROT180, + ROT270 + }; + + enum FLIP + { + HORIZONTAL, + VERTICAL + }; + + /** Identify file format */ + static FORMAT fileFormat(const TQString& filePath); + + /** Create null image */ + DImg(); + + /** Load image using TQCString as file path */ + DImg(const TQCString& filePath, DImgLoaderObserver *observer = 0, + DRawDecoding rawDecodingSettings=DRawDecoding()); + + /** Load image using TQString as file path */ + DImg(const TQString& filePath, DImgLoaderObserver *observer = 0, + DRawDecoding rawDecodingSettings=DRawDecoding()); + + /** Copy image: Creates a shallow copy that refers to the same shared data. + The two images will be equal. Call detach() or copy() to create deep copies. + */ + DImg(const DImg& image); + + /** Copy image: Creates a copy of a TQImage object. If the TQImage is null, a + null DImg will be created. + */ + DImg(const TQImage& image); + + /** Create image from data. + If data is 0, a new buffer will be allocated, otherwise the given data will be used: + If copydata is true, the data will be copied to a newly allocated buffer. + If copyData is false, this DImg object will take ownership of the data pointer. + + If there is an alpha channel, the data shall be in non-premultiplied form (unassociated alpha). + */ + DImg(uint width, uint height, bool sixteenBit, bool alpha=false, uchar* data = 0, bool copyData = true); + + ~DImg(); + + /** Equivalent to the copy constructor */ + DImg& operator=(const DImg& image); + + /** Detaches from shared data and makes sure that this image + is the only one referring to the data. + If multiple images share common data, this image makes a copy + of the data and detaches itself from the sharing mechanism. + Nothing is done if there is just a single reference. + */ + void detach(); + + /** Returns whether two images are equal. + Two images are equal if and only if they refer to the same shared data. + (Thus, DImg() == DImg() is not true, both instances refer two their + own shared data. image == DImg(image) is true.) + If two or more images refer to the same data, they have the same + image data, bits() returns the same data, they have the same metadata, + and a change to one image also affects the others. + Call detach() to split one image from the group of equal images. + */ + bool operator==(const DImg& image) const; + + + /** Replaces image data of this object. Metadata is unchanged. Parameters like constructor above. */ + void putImageData(uint width, uint height, bool sixteenBit, bool alpha, uchar *data, bool copyData = true); + + /** Overloaded function, provided for convenience, behaves essentially + like the function above if data is not 0. + Uses current width, height, sixteenBit, and alpha values. + If data is 0, the current data is deleted and the image is set to null + (But metadata unchanged). + */ + void putImageData(uchar *data, bool copyData = true); + + /** Reset metadata and image data to null image */ + void reset(void); + + /** Reset metadata, but do not change image data */ + void resetMetaData(void); + + /** Returns the data of this image. + Ownership of the buffer is passed to the caller, this image will be null afterwards. + */ + uchar* stripImageData(); + + + + bool load(const TQString& filePath, DImgLoaderObserver *observer = 0, + DRawDecoding rawDecodingSettings=DRawDecoding()); + + bool save(const TQString& filePath, const TQString& format, DImgLoaderObserver *observer = 0); + + bool isNull() const; + uint width() const; + uint height() const; + TQSize size() const; + uchar* bits() const; + uchar* scanLine(uint i) const; + bool hasAlpha() const; + bool sixteenBit() const; + uint numBytes() const; + uint numPixels() const; + + /** Return the number of bytes depth of one pixel : 4 (non sixteenBit) or 8 (sixteen) */ + int bytesDepth() const; + + /** Return the number of bits depth of one color component for one pixel : 8 (non sixteenBit) or 16 (sixteen) */ + int bitsDepth() const; + + /** Access a single pixel of the image. + These functions add some safety checks and then use the methods from DColor. + In optimized code working directly on the data, + better use the inline methods from DColor. + */ + DColor getPixelColor(uint x, uint y) const; + void setPixelColor(uint x, uint y, DColor color); + + /** + Return true if the original image file format cannot be saved. + This is depending of DImgLoader::save() implementation. For example + RAW file formats are supported by DImg uing dcraw than cannot support + writing operations. + */ + bool isReadOnly() const; + + /** Metadata manipulation methods */ + TQByteArray getComments() const; + TQByteArray getExif() const; + TQByteArray getIptc() const; + TQByteArray getICCProfil() const; + void setComments(const TQByteArray& commentsData); + void setExif(const TQByteArray& exifData); + void setIptc(const TQByteArray& iptcData); + void setICCProfil(const TQByteArray& profile); + + TQByteArray metadata(METADATA key) const; + + bool getICCProfilFromFile(const TQString& filePath); + bool setICCProfilToFile(const TQString& filePath); + + void setAttribute(const TQString& key, const TQVariant& value); + TQVariant attribute(const TQString& key) const; + + void setEmbeddedText(const TQString& key, const TQString& text); + TQString embeddedText(const TQString& key) const; + + + /** Return a deep copy of full image */ + DImg copy(); + + /** Return a deep copy of the image, but do not include metadata. */ + DImg copyImageData(); + + /** Return an image that containes a deep copy of + this image's metadata and the information associated + with the image data (width, height, hasAlpha, sixteenBit), + but no image data, i.e. isNull() is true. + */ + DImg copyMetaData(); + + /** Return a region of image */ + DImg copy(TQRect rect); + DImg copy(int x, int y, int w, int h); + + /** Copy a region of pixels from a source image to this image. + Parameters: + sx|sy Coordinates in the source image of the rectangle to be copied + w h Width and height of the rectangle (Default, or when both are -1: whole source image) + dx|dy Coordinates in this image of the rectangle in which the region will be copied + (Default: 0|0) + The bit depth of source and destination must be identical. + */ + void bitBltImage(const DImg* src, int dx, int dy); + void bitBltImage(const DImg* src, int sx, int sy, int dx, int dy); + void bitBltImage(const DImg* src, int sx, int sy, int w, int h, int dx, int dy); + void bitBltImage(const uchar* src, int sx, int sy, int w, int h, int dx, int dy, + uint swidth, uint sheight, int sdepth); + + /** Blend src image on this image (this is dest) with the specified composer + and multiplication flags. See documentation of DColorComposer for more info. + For the other arguments, see documentation of bitBltImage above. + */ + void bitBlendImage(DColorComposer *composer, const DImg* src, + int sx, int sy, int w, int h, int dx, int dy, + DColorComposer::MultiplicationFlags multiplicationFlags = + DColorComposer::NoMultiplication); + + /** TQImage wrapper methods */ + TQImage copyTQImage(); + TQImage copyTQImage(TQRect rect); + TQImage copyTQImage(int x, int y, int w, int h); + + /** Crop image to the specified region */ + void crop(TQRect rect); + void crop(int x, int y, int w, int h); + + /** Set width and height of this image, smoothScale it to the given size */ + void resize(int w, int h); + + /** Return a version of this image scaled to the specified size with the specified mode. + See TQSize documentation for information on available modes + */ + DImg smoothScale(int width, int height, TQSize::ScaleMode scaleMode=TQSize::ScaleFree); + + /** Take the region specified by the rectangle sx|sy, width and height sw * sh, + and scale it to an image with size dw * dh + */ + DImg smoothScaleSection(int sx, int sy, int sw, int sh, + int dw, int dh); + + void rotate(ANGLE angle); + void flip(FLIP direction); + + TQPixmap convertToPixmap(); + TQPixmap convertToPixmap(IccTransform* monitorICCtrans); + + /** Return a mask image where pure white and pure black pixels are over-colored. + This way is used to identify over and under exposed pixels. + */ + TQImage pureColorMask(ExposureSettingsContainer *expoSettings); + + /** Convert depth of image. Depth is bytesDepth * bitsDepth. + If depth is 32, converts to 8 bits, + if depth is 64, converts to 16 bits. + */ + void convertDepth(int depth); + + /** Wrapper methods for convertDepth */ + void convertToSixteenBit(); + void convertToEightBit(); + void convertToDepthOfImage(const DImg *otherImage); + + /** Fill whole image with specified color. + The bit depth of the color must be identical to the depth of this image. + */ + void fill(DColor color); + +private: + + DImgPrivate *m_priv; + +private: + + void copyMetaData(const DImgPrivate *src); + void copyImageData(const DImgPrivate *src); + void setImageData(bool null, uint width, uint height, bool sixteenBit, bool alpha); + void setImageDimension(uint width, uint height); + int allocateData(); + DImg(const DImg &image, int w, int h); + static void bitBlt(const uchar *src, uchar *dest, + int sx, int sy, int w, int h, int dx, int dy, + uint swidth, uint sheight, uint dwidth, uint dheight, + bool sixteenBit, int sdepth, int ddepth); + static void bitBlend(DColorComposer *composer, const uchar *src, uchar *dest, + int sx, int sy, int w, int h, int dx, int dy, + uint swidth, uint sheight, uint dwidth, uint dheight, + bool sixteenBit, int sdepth, int ddepth, + DColorComposer::MultiplicationFlags multiplicationFlags); + static bool normalizeRegionArguments(int &sx, int &sy, int &w, int &h, int &dx, int &dy, + uint swidth, uint sheight, uint dwidth, uint dheight); + + friend class DImgLoader; +}; + +} // NameSpace Digikam + +#endif /* DIMG_H */ diff --git a/src/libs/dimg/dimgprivate.h b/src/libs/dimg/dimgprivate.h new file mode 100644 index 00000000..021208d3 --- /dev/null +++ b/src/libs/dimg/dimgprivate.h @@ -0,0 +1,81 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-15 + * Description : DImg private data members + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DIMGPRIVATE_H +#define DIMGPRIVATE_H + +// TQt includes. + +#include +#include +#include +#include +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT DImgPrivate : public TQShared +{ +public: + + DImgPrivate() + { + null = true; + width = 0; + height = 0; + data = 0; + alpha = false; + sixteenBit = false; + isReadOnly = false; + } + + ~DImgPrivate() + { + delete [] data; + } + + bool null; + bool alpha; + bool sixteenBit; + bool isReadOnly; + + unsigned int width; + unsigned int height; + + unsigned char *data; + + TQMap metaData; + TQStringVariantMap attributes; + TQMap embeddedText; + +}; + +} // NameSpace Digikam + +#endif /* DIMGPRIVATE_H */ diff --git a/src/libs/dimg/dimgscale.cpp b/src/libs/dimg/dimgscale.cpp new file mode 100644 index 00000000..850e36de --- /dev/null +++ b/src/libs/dimg/dimgscale.cpp @@ -0,0 +1,2127 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-14 + * Description : This is the normal smoothscale method, + * based on Imlib2's smoothscale. Added + * smoothScaleSection - Scaling only of a + * section of a image. Added 16bit image support + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2006-2008 by Gilles Caulier + * Copyright (C) 2006-2008 by Marcel Wiesweg + * + * Ported to C++/TQImage by Daniel M. Duley + * Following modification are (C) Daniel M. Duley + * Changes include formatting, namespaces and other C++'ings, removal of old + * #ifdef'ed code, and removal of unneeded border calculation code. + * + * Imlib2 is (C) Carsten Haitzler and various contributors. The MMX code + * is by Willem Monsuwe . + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// C ansi includes. + +extern "C" +{ +#include +} + +// C++ includes. + +#include +#include +#include + +// Local includes. + +#include "dimgprivate.h" +#include "dimg.h" + +typedef uint64_t ullong; +typedef int64_t llong; + +namespace Digikam +{ + +namespace DImgScale +{ + typedef struct __dimg_scale_info + { + int *xpoints; + uint **ypoints; + ullong **ypoints16; + int *xapoints; + int *yapoints; + int xup_yup; + } DImgScaleInfo; + + uint** dimgCalcYPoints(uint *src, int sw, int sh, int dh); + ullong** dimgCalcYPoints16(ullong *src, int sw, int sh, int dh); + int* dimgCalcXPoints(int sw, int dw); + int* dimgCalcApoints(int s, int d, int up); + + DImgScaleInfo* dimgFreeScaleInfo(DImgScaleInfo *isi); + DImgScaleInfo *dimgCalcScaleInfo(const DImg &img, + int sw, int sh, + int dw, int dh, + bool sixteenBit, + bool aa); + + void dimgSampleRGBA(DImgScaleInfo *isi, unsigned int *dest, int dxx, + int dyy, int dx, int dy, int dw, int dh, int dow); + void dimgScaleAARGBA(DImgScaleInfo *isi, unsigned int *dest, int dxx, + int dyy, int dx, int dy, int dw, int dh, int dow, + int sow); + void dimgScaleAARGB(DImgScaleInfo *isi, unsigned int *dest, int dxx, + int dyy, int dx, int dy, int dw, int dh, int dow, int + sow); + + void dimgScaleAARGBA16(DImgScaleInfo *isi, ullong *dest, + int dxx, int dyy, int dw, int dh, + int dow, int sow); + void dimgScaleAARGB16(DImgScaleInfo *isi, ullong *dest, + int dxx, int dyy, int dw, int dh, + int dow, int sow); +}; + +using namespace DImgScale; + +DImg DImg::smoothScale(int dw, int dh, TQSize::ScaleMode scaleMode) +{ + if (dw < 0 || dh < 0 || isNull()) + return DImg(); + + uint w = width(); + uint h = height(); + + if (w <= 0 || h <= 0) + return DImg(); + + TQSize newSize(w, h); + newSize.scale( TQSize(dw, dh), scaleMode ); + if (!newSize.isValid()) + return DImg(); + + dw = newSize.width(); + dh = newSize.height(); + + // do we actually need to scale? + if ((w == (uint)dw) && (h == (uint)dh)) + { + return copy(); + } + + DImgScale::DImgScaleInfo *scaleinfo = dimgCalcScaleInfo(*this, w, h, dw, dh, sixteenBit(), true); + if (!scaleinfo) + return *this; + + DImg buffer(*this, dw, dh); + + if (sixteenBit()) + { + if (hasAlpha()) + { + dimgScaleAARGBA16(scaleinfo, (ullong*) buffer.bits(), + 0, 0, dw, dh, dw, w); + } + else + { + dimgScaleAARGB16(scaleinfo, (ullong*) buffer.bits(), + 0, 0, dw, dh, dw, w); + } + } + else + { + if (hasAlpha()) + { + dimgScaleAARGBA(scaleinfo, (unsigned int *)buffer.bits(), + 0, 0, 0, 0, dw, dh, dw, w); + } + else + { + dimgScaleAARGB(scaleinfo, (unsigned int *)buffer.bits(), + 0, 0, 0, 0, dw, dh, dw, w); + } + } + + dimgFreeScaleInfo(scaleinfo); + + return buffer; +} + +#define CLIP(x, y, w, h, xx, yy, ww, hh) \ +if (x < (xx)) {w += (x - (xx)); x = (xx);} \ +if (y < (yy)) {h += (y - (yy)); y = (yy);} \ +if ((x + w) > ((xx) + (ww))) {w = (ww) - (x - xx);} \ +if ((y + h) > ((yy) + (hh))) {h = (hh) - (y - yy);} + +DImg DImg::smoothScaleSection(int sx, int sy, + int sw, int sh, + int dw, int dh) +{ + uint w = width(); + uint h = height(); + + // sanity checks + if ((dw <= 0) || (dh <= 0)) + return DImg(); + + if ((sw <= 0) || (sh <= 0)) + return DImg(); + + // clip the source rect to be within the actual image + int psx, psy, psw, psh; + psx = sx; + psy = sy; + psw = sw; + psh = sh; + CLIP(sx, sy, sw, sh, 0, 0, (int)w, (int)h); + + // clip output coords to clipped input coords + if (psw != sw) + dw = (dw * sw) / psw; + if (psh != sh) + dh = (dh * sh) / psh; + + // do a second check to see if we now have invalid coords + // do not do anything if we have a 0 widht or height image to render + if ((dw <= 0) || (dh <= 0)) + return DImg(); + + // if the input rect size < 0 do not render either + if ((sw <= 0) || (sh <= 0)) + return DImg(); + + // do we actually need to scale? + if ((sw == dw) && (sh == dh)) + { + return copy(sx, sy, sw, sh); + } + + // calculate scaleinfo + DImgScaleInfo *scaleinfo = dimgCalcScaleInfo(*this, sw, sh, dw, dh, sixteenBit(), true); + if (!scaleinfo) + return DImg(); + + DImg buffer(*this, dw, dh); + + if (sixteenBit()) + { + if (hasAlpha()) + { + dimgScaleAARGBA16(scaleinfo, (ullong*) buffer.bits(), + ((sx * dw) / sw), + ((sy * dh) / sh), + dw, dh, + dw, w); + } + else + { + dimgScaleAARGB16(scaleinfo, (ullong*) buffer.bits(), + ((sx * dw) / sw), + ((sy * dh) / sh), + dw, dh, + dw, w); + } + } + else + { + if (hasAlpha()) + { + dimgScaleAARGBA(scaleinfo, + (uint *)buffer.bits(), + ((sx * dw) / sw), + ((sy * dh) / sh), + 0, 0, + dw, dh, + dw, w); + } + else + { + dimgScaleAARGB(scaleinfo, + (uint *)buffer.bits(), + ((sx * dw) / sw), + ((sy * dh) / sh), + 0, 0, + dw, dh, + dw, w); + } + } + + dimgFreeScaleInfo(scaleinfo); + + return buffer; +} + + +// +// Code ported from Imlib2... +// + +// FIXME: replace with mRed, etc... These work on pointers to pixels, not +// pixel values +#define A_VAL(p) ((unsigned char *)(p))[3] +#define R_VAL(p) ((unsigned char *)(p))[2] +#define G_VAL(p) ((unsigned char *)(p))[1] +#define B_VAL(p) ((unsigned char *)(p))[0] + +#define INV_XAP (256 - xapoints[x]) +#define XAP (xapoints[x]) +#define INV_YAP (256 - yapoints[dyy + y]) +#define YAP (yapoints[dyy + y]) + +unsigned int** DImgScale::dimgCalcYPoints(unsigned int *src, int sw, int sh, int dh) +{ + unsigned int **p; + int i, j = 0; + int val, inc; + + p = new unsigned int* [dh+1]; + + val = 0; + inc = (sh << 16) / dh; + for(i = 0; i < dh; i++) + { + p[j++] = src + ((val >> 16) * sw); + val += inc; + } + + return(p); +} + +ullong** DImgScale::dimgCalcYPoints16(ullong* src, int sw, int sh, int dh) +{ + ullong** p; + int i, j = 0; + int val, inc; + + p = new ullong*[(dh+1)]; + + val = 0; + inc = (sh << 16) / dh; + for(i = 0; i < dh; i++) + { + p[j++] = src + ((val >> 16) * sw); + val += inc; + } + + return p; +} + +int* DImgScale::dimgCalcXPoints(int sw, int dw) +{ + int *p, i, j = 0; + int val, inc; + + p = new int[dw+1]; + + val = 0; + inc = (sw << 16) / dw; + for(i = 0; i < dw; i++) + { + p[j++] = (val >> 16); + val += inc; + } + + return(p); +} + +int* DImgScale::dimgCalcApoints(int s, int d, int up) +{ + int *p, i, j = 0; + + p = new int[d]; + + /* scaling up */ + if(up) + { + int val, inc; + + val = 0; + inc = (s << 16) / d; + for(i = 0; i < d; i++) + { + p[j++] = (val >> 8) - ((val >> 8) & 0xffffff00); + if((val >> 16) >= (s - 1)) + p[j - 1] = 0; + val += inc; + } + } + /* scaling down */ + else + { + int val, inc, ap, Cp; + val = 0; + inc = (s << 16) / d; + Cp = ((d << 14) / s) + 1; + + for(i = 0; i < d; i++) + { + ap = ((0x100 - ((val >> 8) & 0xff)) * Cp) >> 8; + p[j] = ap | (Cp << 16); + j++; + val += inc; + } + } + + return(p); +} + +DImgScaleInfo* DImgScale::dimgCalcScaleInfo(const DImg &img, + int sw, int sh, + int dw, int dh, + bool /*sixteenBit*/, + bool aa) +{ + DImgScaleInfo *isi; + int scw, sch; + + scw = dw * img.width() / sw; + sch = dh * img.height() / sh; + + isi = new DImgScaleInfo; + if(!isi) + return(NULL); + + memset(isi, 0, sizeof(DImgScaleInfo)); + + isi->xup_yup = (abs(dw) >= sw) + ((abs(dh) >= sh) << 1); + + isi->xpoints = dimgCalcXPoints(img.width(), scw); + if(!isi->xpoints) + return(dimgFreeScaleInfo(isi)); + + if (img.sixteenBit()) + { + isi->ypoints = 0; + isi->ypoints16 = dimgCalcYPoints16((ullong*)img.bits(), img.width(), img.height(), sch); + if (!isi->ypoints16) return(dimgFreeScaleInfo(isi)); + } + else + { + isi->ypoints16 = 0; + isi->ypoints = dimgCalcYPoints((uint*)img.bits(), img.width(), img.height(), sch); + if (!isi->ypoints) return(dimgFreeScaleInfo(isi)); + } + + if (aa) + { + isi->xapoints = dimgCalcApoints(img.width(), scw, isi->xup_yup & 1); + if(!isi->xapoints) return(dimgFreeScaleInfo(isi)); + + isi->yapoints = dimgCalcApoints(img.height(), sch, isi->xup_yup & 2); + if(!isi->yapoints) return(dimgFreeScaleInfo(isi)); + } +/* It doesn't work... + else + { + isi->xapoints = new int[scw]; + if(!isi->xapoints) return(dimgFreeScaleInfo(isi)); + for(int i = 0; i < scw; i++) isi->xapoints[i] = 0; + + isi->yapoints = new int[sch]; + if(!isi->yapoints) return(dimgFreeScaleInfo(isi)); + for(int i = 0; i < sch; i++) isi->yapoints[i] = 0; + }*/ + + return(isi); +} + +DImgScaleInfo* DImgScale::dimgFreeScaleInfo(DImgScaleInfo *isi) +{ + if(isi) + { + delete [] isi->xpoints; + delete [] isi->ypoints; + delete [] isi->ypoints16; + delete [] isi->xapoints; + delete [] isi->yapoints; + delete isi; + } + + return 0; +} + +/** scale by pixel sampling only */ +void DImgScale::dimgSampleRGBA(DImgScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow) +{ + unsigned int *sptr, *dptr; + int x, y, end; + unsigned int **ypoints = isi->ypoints; + int *xpoints = isi->xpoints; + + /* whats the last pixel ont he line so we stop there */ + end = dxx + dw; + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++) + { + /* get the pointer to the start of the destination scanline */ + dptr = dest + dx + ((y + dy) * dow); + /* calculate the source line we'll scan from */ + sptr = ypoints[dyy + y]; + /* go thru the scanline and copy across */ + for(x = dxx; x < end; x++) + *dptr++ = sptr[xpoints[x]]; + } +} + +/* FIXME: NEED to optimise ScaleAARGBA - currently its "ok" but needs work*/ + +/** scale by area sampling */ +void DImgScale::dimgScaleAARGBA(DImgScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow) +{ + unsigned int *sptr, *dptr; + int x, y, end; + unsigned int **ypoints = isi->ypoints; + int *xpoints = isi->xpoints; + int *xapoints = isi->xapoints; + int *yapoints = isi->yapoints; + + end = dxx + dw; + /* scaling up both ways */ + if(isi->xup_yup == 3) + { + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++) + { + /* calculate the source line we'll scan from */ + dptr = dest + dx + ((y + dy) * dow); + sptr = ypoints[dyy + y]; + if(YAP > 0) + { + for(x = dxx; x < end; x++) + { + int r, g, b, a; + int rr, gg, bb, aa; + unsigned int *pix; + + if(XAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + a = A_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + a += A_VAL(pix) * XAP; + pix += sow; + rr = R_VAL(pix) * XAP; + gg = G_VAL(pix) * XAP; + bb = B_VAL(pix) * XAP; + aa = A_VAL(pix) * XAP; + pix--; + rr += R_VAL(pix) * INV_XAP; + gg += G_VAL(pix) * INV_XAP; + bb += B_VAL(pix) * INV_XAP; + aa += A_VAL(pix) * INV_XAP; + r = ((rr * YAP) + (r * INV_YAP)) >> 16; + g = ((gg * YAP) + (g * INV_YAP)) >> 16; + b = ((bb * YAP) + (b * INV_YAP)) >> 16; + a = ((aa * YAP) + (a * INV_YAP)) >> 16; + + A_VAL(dptr) = a; + R_VAL(dptr) = r; + G_VAL(dptr) = g; + B_VAL(dptr) = b; + + dptr++; + } + else + { + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_YAP; + g = G_VAL(pix) * INV_YAP; + b = B_VAL(pix) * INV_YAP; + a = A_VAL(pix) * INV_YAP; + pix += sow; + r += R_VAL(pix) * YAP; + g += G_VAL(pix) * YAP; + b += B_VAL(pix) * YAP; + a += A_VAL(pix) * YAP; + r >>= 8; + g >>= 8; + b >>= 8; + a >>= 8; + + A_VAL(dptr) = a; + R_VAL(dptr) = r; + G_VAL(dptr) = g; + B_VAL(dptr) = b; + + dptr++; + } + } + } + else + { + for(x = dxx; x < end; x++) + { + int r, g, b, a; + unsigned int *pix; + + if(XAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + a = A_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + a += A_VAL(pix) * XAP; + r >>= 8; + g >>= 8; + b >>= 8; + a >>= 8; + + A_VAL(dptr) = a; + R_VAL(dptr) = r; + G_VAL(dptr) = g; + B_VAL(dptr) = b; + + dptr++; + } + else + *dptr++ = sptr[xpoints[x] ]; + } + } + } + } + /* if we're scaling down vertically */ + else if(isi->xup_yup == 1) + { + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cy, j; + unsigned int *pix; + int r, g, b, a, rr, gg, bb, aa; + int yap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++) + { + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++) + { + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL(pix) * yap) >> 10; + g = (G_VAL(pix) * yap) >> 10; + b = (B_VAL(pix) * yap) >> 10; + a = (A_VAL(pix) * yap) >> 10; + for(j = (1 << 14) - yap; j > Cy; j -= Cy) + { + pix += sow; + r += (R_VAL(pix) * Cy) >> 10; + g += (G_VAL(pix) * Cy) >> 10; + b += (B_VAL(pix) * Cy) >> 10; + a += (A_VAL(pix) * Cy) >> 10; + } + if(j > 0) + { + pix += sow; + r += (R_VAL(pix) * j) >> 10; + g += (G_VAL(pix) * j) >> 10; + b += (B_VAL(pix) * j) >> 10; + a += (A_VAL(pix) * j) >> 10; + } + if(XAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x] + 1; + rr = (R_VAL(pix) * yap) >> 10; + gg = (G_VAL(pix) * yap) >> 10; + bb = (B_VAL(pix) * yap) >> 10; + aa = (A_VAL(pix) * yap) >> 10; + for(j = (1 << 14) - yap; j > Cy; j -= Cy) + { + pix += sow; + rr += (R_VAL(pix) * Cy) >> 10; + gg += (G_VAL(pix) * Cy) >> 10; + bb += (B_VAL(pix) * Cy) >> 10; + aa += (A_VAL(pix) * Cy) >> 10; + } + if(j > 0) + { + pix += sow; + rr += (R_VAL(pix) * j) >> 10; + gg += (G_VAL(pix) * j) >> 10; + bb += (B_VAL(pix) * j) >> 10; + aa += (A_VAL(pix) * j) >> 10; + } + r = r * INV_XAP; + g = g * INV_XAP; + b = b * INV_XAP; + a = a * INV_XAP; + r = (r + ((rr * XAP))) >> 12; + g = (g + ((gg * XAP))) >> 12; + b = (b + ((bb * XAP))) >> 12; + a = (a + ((aa * XAP))) >> 12; + } + else + { + r >>= 4; + g >>= 4; + b >>= 4; + a >>= 4; + } + + A_VAL(dptr) = a; + R_VAL(dptr) = r; + G_VAL(dptr) = g; + B_VAL(dptr) = b; + + dptr++; + } + } + } + /* if we're scaling down horizontally */ + else if(isi->xup_yup == 2) + { + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, j; + unsigned int *pix; + int r, g, b, a, rr, gg, bb, aa; + int xap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++) + { + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++) + { + Cx = XAP >> 16; + xap = XAP & 0xffff; + + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL(pix) * xap) >> 10; + g = (G_VAL(pix) * xap) >> 10; + b = (B_VAL(pix) * xap) >> 10; + a = (A_VAL(pix) * xap) >> 10; + for(j = (1 << 14) - xap; j > Cx; j -= Cx) + { + pix++; + r += (R_VAL(pix) * Cx) >> 10; + g += (G_VAL(pix) * Cx) >> 10; + b += (B_VAL(pix) * Cx) >> 10; + a += (A_VAL(pix) * Cx) >> 10; + } + if(j > 0) + { + pix++; + r += (R_VAL(pix) * j) >> 10; + g += (G_VAL(pix) * j) >> 10; + b += (B_VAL(pix) * j) >> 10; + a += (A_VAL(pix) * j) >> 10; + } + if(YAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x] + sow; + rr = (R_VAL(pix) * xap) >> 10; + gg = (G_VAL(pix) * xap) >> 10; + bb = (B_VAL(pix) * xap) >> 10; + aa = (A_VAL(pix) * xap) >> 10; + for(j = (1 << 14) - xap; j > Cx; j -= Cx) + { + pix++; + rr += (R_VAL(pix) * Cx) >> 10; + gg += (G_VAL(pix) * Cx) >> 10; + bb += (B_VAL(pix) * Cx) >> 10; + aa += (A_VAL(pix) * Cx) >> 10; + } + if(j > 0) + { + pix++; + rr += (R_VAL(pix) * j) >> 10; + gg += (G_VAL(pix) * j) >> 10; + bb += (B_VAL(pix) * j) >> 10; + aa += (A_VAL(pix) * j) >> 10; + } + r = r * INV_YAP; + g = g * INV_YAP; + b = b * INV_YAP; + a = a * INV_YAP; + r = (r + ((rr * YAP))) >> 12; + g = (g + ((gg * YAP))) >> 12; + b = (b + ((bb * YAP))) >> 12; + a = (a + ((aa * YAP))) >> 12; + } + else + { + r >>= 4; + g >>= 4; + b >>= 4; + a >>= 4; + } + + A_VAL(dptr) = a; + R_VAL(dptr) = r; + G_VAL(dptr) = g; + B_VAL(dptr) = b; + + dptr++; + } + } + } + /* if we're scaling down horizontally & vertically */ + else + { + /*\ 'Correct' version, with math units prepared for MMXification: + |*| The operation 'b = (b * c) >> 16' translates to pmulhw, + |*| so the operation 'b = (b * c) >> d' would translate to + |*| psllw (16 - d), %mmb; pmulh %mmc, %mmb + \*/ + int Cx, Cy, i, j; + unsigned int *pix; + int a, r, g, b, ax, rx, gx, bx; + int xap, yap; + + for(y = 0; y < dh; y++) + { + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++) + { + Cx = XAP >> 16; + xap = XAP & 0xffff; + + sptr = ypoints[dyy + y] + xpoints[x]; + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + ax = (A_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx) + { + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + ax += (A_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0) + { + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + ax += (A_VAL(pix) * i) >> 9; + } + + r = (rx * yap) >> 14; + g = (gx * yap) >> 14; + b = (bx * yap) >> 14; + a = (ax * yap) >> 14; + + for(j = (1 << 14) - yap; j > Cy; j -= Cy) + { + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + ax = (A_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx) + { + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + ax += (A_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0) + { + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + ax += (A_VAL(pix) * i) >> 9; + } + + r += (rx * Cy) >> 14; + g += (gx * Cy) >> 14; + b += (bx * Cy) >> 14; + a += (ax * Cy) >> 14; + } + if(j > 0) + { + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + ax = (A_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx) + { + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + ax += (A_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0) + { + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + ax += (A_VAL(pix) * i) >> 9; + } + + r += (rx * j) >> 14; + g += (gx * j) >> 14; + b += (bx * j) >> 14; + a += (ax * j) >> 14; + } + + R_VAL(dptr) = r >> 5; + G_VAL(dptr) = g >> 5; + B_VAL(dptr) = b >> 5; + A_VAL(dptr) = a >> 5; + dptr++; + } + } + } +} + +/** scale by area sampling - IGNORE the ALPHA byte */ +void DImgScale::dimgScaleAARGB(DImgScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow) +{ + unsigned int *sptr, *dptr; + int x, y, end; + unsigned int **ypoints = isi->ypoints; + int *xpoints = isi->xpoints; + int *xapoints = isi->xapoints; + int *yapoints = isi->yapoints; + + end = dxx + dw; + /* scaling up both ways */ + if(isi->xup_yup == 3) + { + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++) + { + /* calculate the source line we'll scan from */ + dptr = dest + dx + ((y + dy) * dow); + sptr = ypoints[dyy + y]; + if(YAP > 0) + { + for(x = dxx; x < end; x++) + { + int r = 0, g = 0, b = 0; + int rr = 0, gg = 0, bb = 0; + unsigned int *pix; + + if(XAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + pix += sow; + rr = R_VAL(pix) * XAP; + gg = G_VAL(pix) * XAP; + bb = B_VAL(pix) * XAP; + pix --; + rr += R_VAL(pix) * INV_XAP; + gg += G_VAL(pix) * INV_XAP; + bb += B_VAL(pix) * INV_XAP; + r = ((rr * YAP) + (r * INV_YAP)) >> 16; + g = ((gg * YAP) + (g * INV_YAP)) >> 16; + b = ((bb * YAP) + (b * INV_YAP)) >> 16; + + R_VAL(dptr) = r; + G_VAL(dptr) = g; + B_VAL(dptr) = b; + A_VAL(dptr) = 0xFF; + + dptr++; + } + else + { + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_YAP; + g = G_VAL(pix) * INV_YAP; + b = B_VAL(pix) * INV_YAP; + pix += sow; + r += R_VAL(pix) * YAP; + g += G_VAL(pix) * YAP; + b += B_VAL(pix) * YAP; + r >>= 8; + g >>= 8; + b >>= 8; + + R_VAL(dptr) = r; + G_VAL(dptr) = g; + B_VAL(dptr) = b; + A_VAL(dptr) = 0xFF; + + dptr++; + } + } + } + else + { + for(x = dxx; x < end; x++) + { + int r = 0, g = 0, b = 0; + unsigned int *pix; + + if(XAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + r >>= 8; + g >>= 8; + b >>= 8; + + R_VAL(dptr) = r; + G_VAL(dptr) = g; + B_VAL(dptr) = b; + A_VAL(dptr) = 0xFF; + + dptr++; + } + else + *dptr++ = sptr[xpoints[x] ]; + } + } + } + } + /* if we're scaling down vertically */ + else if(isi->xup_yup == 1) + { + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cy, j; + unsigned int *pix; + int r, g, b, rr, gg, bb; + int yap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++) + { + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++) + { + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL(pix) * yap) >> 10; + g = (G_VAL(pix) * yap) >> 10; + b = (B_VAL(pix) * yap) >> 10; + pix += sow; + for(j = (1 << 14) - yap; j > Cy; j -= Cy) + { + r += (R_VAL(pix) * Cy) >> 10; + g += (G_VAL(pix) * Cy) >> 10; + b += (B_VAL(pix) * Cy) >> 10; + pix += sow; + } + if(j > 0) + { + r += (R_VAL(pix) * j) >> 10; + g += (G_VAL(pix) * j) >> 10; + b += (B_VAL(pix) * j) >> 10; + } + if(XAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x] + 1; + rr = (R_VAL(pix) * yap) >> 10; + gg = (G_VAL(pix) * yap) >> 10; + bb = (B_VAL(pix) * yap) >> 10; + pix += sow; + for(j = (1 << 14) - yap; j > Cy; j -= Cy) + { + rr += (R_VAL(pix) * Cy) >> 10; + gg += (G_VAL(pix) * Cy) >> 10; + bb += (B_VAL(pix) * Cy) >> 10; + pix += sow; + } + if(j > 0) + { + rr += (R_VAL(pix) * j) >> 10; + gg += (G_VAL(pix) * j) >> 10; + bb += (B_VAL(pix) * j) >> 10; + } + r = r * INV_XAP; + g = g * INV_XAP; + b = b * INV_XAP; + r = (r + ((rr * XAP))) >> 12; + g = (g + ((gg * XAP))) >> 12; + b = (b + ((bb * XAP))) >> 12; + } + else + { + r >>= 4; + g >>= 4; + b >>= 4; + } + + R_VAL(dptr) = r; + G_VAL(dptr) = g; + B_VAL(dptr) = b; + A_VAL(dptr) = 0xFF; + + dptr++; + } + } + } + /* if we're scaling down horizontally */ + else if(isi->xup_yup == 2) + { + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, j; + unsigned int *pix; + int r, g, b, rr, gg, bb; + int xap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++) + { + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++) + { + Cx = XAP >> 16; + xap = XAP & 0xffff; + + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL(pix) * xap) >> 10; + g = (G_VAL(pix) * xap) >> 10; + b = (B_VAL(pix) * xap) >> 10; + pix++; + for(j = (1 << 14) - xap; j > Cx; j -= Cx) + { + r += (R_VAL(pix) * Cx) >> 10; + g += (G_VAL(pix) * Cx) >> 10; + b += (B_VAL(pix) * Cx) >> 10; + pix++; + } + if(j > 0) + { + r += (R_VAL(pix) * j) >> 10; + g += (G_VAL(pix) * j) >> 10; + b += (B_VAL(pix) * j) >> 10; + } + if(YAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x] + sow; + rr = (R_VAL(pix) * xap) >> 10; + gg = (G_VAL(pix) * xap) >> 10; + bb = (B_VAL(pix) * xap) >> 10; + pix++; + for(j = (1 << 14) - xap; j > Cx; j -= Cx) + { + rr += (R_VAL(pix) * Cx) >> 10; + gg += (G_VAL(pix) * Cx) >> 10; + bb += (B_VAL(pix) * Cx) >> 10; + pix++; + } + if(j > 0) + { + rr += (R_VAL(pix) * j) >> 10; + gg += (G_VAL(pix) * j) >> 10; + bb += (B_VAL(pix) * j) >> 10; + } + r = r * INV_YAP; + g = g * INV_YAP; + b = b * INV_YAP; + r = (r + ((rr * YAP))) >> 12; + g = (g + ((gg * YAP))) >> 12; + b = (b + ((bb * YAP))) >> 12; + } + else + { + r >>= 4; + g >>= 4; + b >>= 4; + } + + R_VAL(dptr) = r; + G_VAL(dptr) = g; + B_VAL(dptr) = b; + A_VAL(dptr) = 0xFF; + + dptr++; + } + } + } + /* fully optimized (i think) - onyl change of algorithm can help */ + /* if we're scaling down horizontally & vertically */ + else + { + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, Cy, i, j; + unsigned int *pix; + int r, g, b, rx, gx, bx; + int xap, yap; + + for(y = 0; y < dh; y++) + { + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++) + { + Cx = XAP >> 16; + xap = XAP & 0xffff; + + sptr = ypoints[dyy + y] + xpoints[x]; + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx) + { + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0) + { + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + } + + r = (rx * yap) >> 14; + g = (gx * yap) >> 14; + b = (bx * yap) >> 14; + + for(j = (1 << 14) - yap; j > Cy; j -= Cy) + { + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx) + { + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0) + { + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + } + + r += (rx * Cy) >> 14; + g += (gx * Cy) >> 14; + b += (bx * Cy) >> 14; + } + if(j > 0) + { + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx) + { + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0) + { + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + } + + r += (rx * j) >> 14; + g += (gx * j) >> 14; + b += (bx * j) >> 14; + } + + R_VAL(dptr) = r >> 5; + G_VAL(dptr) = g >> 5; + B_VAL(dptr) = b >> 5; + A_VAL(dptr) = 0xFF; + dptr++; + } + } + } +} + +#define A_VAL16(p) ((ushort *)(p))[3] +#define R_VAL16(p) ((ushort *)(p))[2] +#define G_VAL16(p) ((ushort *)(p))[1] +#define B_VAL16(p) ((ushort *)(p))[0] + +/** scale by area sampling - IGNORE the ALPHA byte*/ +void DImgScale::dimgScaleAARGB16(DImgScaleInfo *isi, ullong *dest, + int dxx, int dyy, int dw, int dh, + int dow, int sow) +{ + ullong *sptr, *dptr; + int x, y, end; + ullong **ypoints = isi->ypoints16; + int *xpoints = isi->xpoints; + int *xapoints = isi->xapoints; + int *yapoints = isi->yapoints; + + end = dxx + dw; + + // scaling up both ways + if(isi->xup_yup == 3) + { + // go through every scanline in the output buffer + for(y = 0; y < dh; y++) + { + // calculate the source line we'll scan from + dptr = dest + (y * dow); + sptr = ypoints[dyy + y]; + if(YAP > 0) + { + for(x = dxx; x < end; x++) + { + llong r = 0, g = 0, b = 0; + llong rr = 0, gg = 0, bb = 0; + ullong *pix; + + if(XAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL16(pix) * INV_XAP; + g = G_VAL16(pix) * INV_XAP; + b = B_VAL16(pix) * INV_XAP; + pix++; + r += R_VAL16(pix) * XAP; + g += G_VAL16(pix) * XAP; + b += B_VAL16(pix) * XAP; + pix += sow; + rr = R_VAL16(pix) * XAP; + gg = G_VAL16(pix) * XAP; + bb = B_VAL16(pix) * XAP; + pix --; + rr += R_VAL16(pix) * INV_XAP; + gg += G_VAL16(pix) * INV_XAP; + bb += B_VAL16(pix) * INV_XAP; + r = ((rr * YAP) + (r * INV_YAP)) >> 16; + g = ((gg * YAP) + (g * INV_YAP)) >> 16; + b = ((bb * YAP) + (b * INV_YAP)) >> 16; + + R_VAL16(dptr) = r; + G_VAL16(dptr) = g; + B_VAL16(dptr) = b; + A_VAL16(dptr) = 0xFFFF; + + dptr++; + } + else + { + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL16(pix) * INV_YAP; + g = G_VAL16(pix) * INV_YAP; + b = B_VAL16(pix) * INV_YAP; + pix += sow; + r += R_VAL16(pix) * YAP; + g += G_VAL16(pix) * YAP; + b += B_VAL16(pix) * YAP; + r >>= 8; + g >>= 8; + b >>= 8; + + R_VAL16(dptr) = r; + G_VAL16(dptr) = g; + B_VAL16(dptr) = b; + A_VAL16(dptr) = 0xFFFF; + + dptr++; + } + } + } + else + { + for(x = dxx; x < end; x++) + { + llong r = 0, g = 0, b = 0; + ullong *pix; + + if(XAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL16(pix) * INV_XAP; + g = G_VAL16(pix) * INV_XAP; + b = B_VAL16(pix) * INV_XAP; + pix++; + r += R_VAL16(pix) * XAP; + g += G_VAL16(pix) * XAP; + b += B_VAL16(pix) * XAP; + r >>= 8; + g >>= 8; + b >>= 8; + + R_VAL16(dptr) = r; + G_VAL16(dptr) = g; + B_VAL16(dptr) = b; + A_VAL16(dptr) = 0xFFFF; + + dptr++; + } + else + *dptr++ = sptr[xpoints[x] ]; + } + } + } + } + // if we're scaling down vertically + else if(isi->xup_yup == 1) + { + // 'Correct' version, with math units prepared for MMXification + int Cy, j; + ullong *pix; + llong r, g, b, rr, gg, bb; + int yap; + + // go through every scanline in the output buffer + for(y = 0; y < dh; y++) + { + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + y * dow; + for(x = dxx; x < end; x++) + { + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL16(pix) * yap) >> 10; + g = (G_VAL16(pix) * yap) >> 10; + b = (B_VAL16(pix) * yap) >> 10; + pix += sow; + for(j = (1 << 14) - yap; j > Cy; j -= Cy) + { + r += (R_VAL16(pix) * Cy) >> 10; + g += (G_VAL16(pix) * Cy) >> 10; + b += (B_VAL16(pix) * Cy) >> 10; + pix += sow; + } + if(j > 0) + { + r += (R_VAL16(pix) * j) >> 10; + g += (G_VAL16(pix) * j) >> 10; + b += (B_VAL16(pix) * j) >> 10; + } + if(XAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x] + 1; + rr = (R_VAL16(pix) * yap) >> 10; + gg = (G_VAL16(pix) * yap) >> 10; + bb = (B_VAL16(pix) * yap) >> 10; + pix += sow; + for(j = (1 << 14) - yap; j > Cy; j -= Cy) + { + rr += (R_VAL16(pix) * Cy) >> 10; + gg += (G_VAL16(pix) * Cy) >> 10; + bb += (B_VAL16(pix) * Cy) >> 10; + pix += sow; + } + if(j > 0) + { + rr += (R_VAL16(pix) * j) >> 10; + gg += (G_VAL16(pix) * j) >> 10; + bb += (B_VAL16(pix) * j) >> 10; + } + r = r * INV_XAP; + g = g * INV_XAP; + b = b * INV_XAP; + r = (r + ((rr * XAP))) >> 12; + g = (g + ((gg * XAP))) >> 12; + b = (b + ((bb * XAP))) >> 12; + } + else + { + r >>= 4; + g >>= 4; + b >>= 4; + } + + R_VAL16(dptr) = r; + G_VAL16(dptr) = g; + B_VAL16(dptr) = b; + A_VAL16(dptr) = 0xFFFF; + dptr++; + } + } + } + // if we're scaling down horizontally + else if(isi->xup_yup == 2) + { + // 'Correct' version, with math units prepared for MMXification + int Cx, j; + ullong *pix; + llong r, g, b, rr, gg, bb; + int xap; + + // go through every scanline in the output buffer + for(y = 0; y < dh; y++) + { + dptr = dest + y * dow; + for(x = dxx; x < end; x++) + { + Cx = XAP >> 16; + xap = XAP & 0xffff; + + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL16(pix) * xap) >> 10; + g = (G_VAL16(pix) * xap) >> 10; + b = (B_VAL16(pix) * xap) >> 10; + pix++; + for(j = (1 << 14) - xap; j > Cx; j -= Cx) + { + r += (R_VAL16(pix) * Cx) >> 10; + g += (G_VAL16(pix) * Cx) >> 10; + b += (B_VAL16(pix) * Cx) >> 10; + pix++; + } + if(j > 0) + { + r += (R_VAL16(pix) * j) >> 10; + g += (G_VAL16(pix) * j) >> 10; + b += (B_VAL16(pix) * j) >> 10; + } + if(YAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x] + sow; + rr = (R_VAL16(pix) * xap) >> 10; + gg = (G_VAL16(pix) * xap) >> 10; + bb = (B_VAL16(pix) * xap) >> 10; + pix++; + for(j = (1 << 14) - xap; j > Cx; j -= Cx) + { + rr += (R_VAL16(pix) * Cx) >> 10; + gg += (G_VAL16(pix) * Cx) >> 10; + bb += (B_VAL16(pix) * Cx) >> 10; + pix++; + } + if(j > 0) + { + rr += (R_VAL16(pix) * j) >> 10; + gg += (G_VAL16(pix) * j) >> 10; + bb += (B_VAL16(pix) * j) >> 10; + } + r = r * INV_YAP; + g = g * INV_YAP; + b = b * INV_YAP; + r = (r + ((rr * YAP))) >> 12; + g = (g + ((gg * YAP))) >> 12; + b = (b + ((bb * YAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + } + + R_VAL16(dptr) = r; + G_VAL16(dptr) = g; + B_VAL16(dptr) = b; + A_VAL16(dptr) = 0xFFFF; + dptr++; + } + } + } + // fully optimized (i think) - onyl change of algorithm can help + // if we're scaling down horizontally & vertically + else + { + // 'Correct' version, with math units prepared for MMXification + int Cx, Cy, i, j; + ullong *pix; + llong r, g, b, rx, gx, bx; + int xap, yap; + + for(y = 0; y < dh; y++) + { + Cy = YAP >> 16; + yap = YAP & 0xffff; + dptr = dest + y * dow; + + for(x = dxx; x < end; x++) + { + Cx = XAP >> 16; + xap = XAP & 0xffff; + + sptr = ypoints[dyy + y] + xpoints[x]; + pix = sptr; + sptr += sow; + + rx = (R_VAL16(pix) * xap) >> 9; + gx = (G_VAL16(pix) * xap) >> 9; + bx = (B_VAL16(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx) + { + rx += (R_VAL16(pix) * Cx) >> 9; + gx += (G_VAL16(pix) * Cx) >> 9; + bx += (B_VAL16(pix) * Cx) >> 9; + pix++; + } + if(i > 0) + { + rx += (R_VAL16(pix) * i) >> 9; + gx += (G_VAL16(pix) * i) >> 9; + bx += (B_VAL16(pix) * i) >> 9; + } + + r = (rx * yap) >> 14; + g = (gx * yap) >> 14; + b = (bx * yap) >> 14; + + for(j = (1 << 14) - yap; j > Cy; j -= Cy) + { + pix = sptr; + sptr += sow; + rx = (R_VAL16(pix) * xap) >> 9; + gx = (G_VAL16(pix) * xap) >> 9; + bx = (B_VAL16(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx) + { + rx += (R_VAL16(pix) * Cx) >> 9; + gx += (G_VAL16(pix) * Cx) >> 9; + bx += (B_VAL16(pix) * Cx) >> 9; + pix++; + } + if(i > 0) + { + rx += (R_VAL16(pix) * i) >> 9; + gx += (G_VAL16(pix) * i) >> 9; + bx += (B_VAL16(pix) * i) >> 9; + } + + r += (rx * Cy) >> 14; + g += (gx * Cy) >> 14; + b += (bx * Cy) >> 14; + } + if(j > 0) + { + pix = sptr; + sptr += sow; + rx = (R_VAL16(pix) * xap) >> 9; + gx = (G_VAL16(pix) * xap) >> 9; + bx = (B_VAL16(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx) + { + rx += (R_VAL16(pix) * Cx) >> 9; + gx += (G_VAL16(pix) * Cx) >> 9; + bx += (B_VAL16(pix) * Cx) >> 9; + pix++; + } + if(i > 0) + { + rx += (R_VAL16(pix) * i) >> 9; + gx += (G_VAL16(pix) * i) >> 9; + bx += (B_VAL16(pix) * i) >> 9; + } + + r += (rx * j) >> 14; + g += (gx * j) >> 14; + b += (bx * j) >> 14; + } + + R_VAL16(dptr) = r >> 5; + G_VAL16(dptr) = g >> 5; + B_VAL16(dptr) = b >> 5; + A_VAL16(dptr) = 0xFFFF; + dptr++; + } + } + } +} + +/* scale by area sampling */ +void DImgScale::dimgScaleAARGBA16(DImgScaleInfo *isi, ullong *dest, + int dxx, int dyy, + int dw, int dh, + int dow, int sow) +{ + ullong *sptr, *dptr; + int x, y, end; + ullong **ypoints = isi->ypoints16; + int *xpoints = isi->xpoints; + int *xapoints = isi->xapoints; + int *yapoints = isi->yapoints; + + end = dxx + dw; + /* scaling up both ways */ + if(isi->xup_yup == 3) + { + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++) + { + /* calculate the source line we'll scan from */ + dptr = dest + (y * dow); + sptr = ypoints[dyy + y]; + if(YAP > 0) + { + for(x = dxx; x < end; x++) + { + llong r, g, b, a; + llong rr, gg, bb, aa; + ullong *pix; + + if(XAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL16(pix) * INV_XAP; + g = G_VAL16(pix) * INV_XAP; + b = B_VAL16(pix) * INV_XAP; + a = A_VAL16(pix) * INV_XAP; + pix++; + r += R_VAL16(pix) * XAP; + g += G_VAL16(pix) * XAP; + b += B_VAL16(pix) * XAP; + a += A_VAL16(pix) * XAP; + pix += sow; + rr = R_VAL16(pix) * XAP; + gg = G_VAL16(pix) * XAP; + bb = B_VAL16(pix) * XAP; + aa = A_VAL16(pix) * XAP; + pix--; + rr += R_VAL16(pix) * INV_XAP; + gg += G_VAL16(pix) * INV_XAP; + bb += B_VAL16(pix) * INV_XAP; + aa += A_VAL16(pix) * INV_XAP; + r = ((rr * YAP) + (r * INV_YAP)) >> 16; + g = ((gg * YAP) + (g * INV_YAP)) >> 16; + b = ((bb * YAP) + (b * INV_YAP)) >> 16; + a = ((aa * YAP) + (a * INV_YAP)) >> 16; + + R_VAL16(dptr) = r; + G_VAL16(dptr) = g; + B_VAL16(dptr) = b; + A_VAL16(dptr) = a; + + dptr++; + } + else + { + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL16(pix) * INV_YAP; + g = G_VAL16(pix) * INV_YAP; + b = B_VAL16(pix) * INV_YAP; + a = A_VAL16(pix) * INV_YAP; + pix += sow; + r += R_VAL16(pix) * YAP; + g += G_VAL16(pix) * YAP; + b += B_VAL16(pix) * YAP; + a += A_VAL16(pix) * YAP; + r >>= 8; + g >>= 8; + b >>= 8; + a >>= 8; + + R_VAL16(dptr) = r; + G_VAL16(dptr) = g; + B_VAL16(dptr) = b; + A_VAL16(dptr) = a; + + dptr++; + } + } + } + else + { + for(x = dxx; x < end; x++) + { + llong r, g, b, a; + ullong *pix; + + if(XAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL16(pix) * INV_XAP; + g = G_VAL16(pix) * INV_XAP; + b = B_VAL16(pix) * INV_XAP; + a = A_VAL16(pix) * INV_XAP; + pix++; + r += R_VAL16(pix) * XAP; + g += G_VAL16(pix) * XAP; + b += B_VAL16(pix) * XAP; + a += A_VAL16(pix) * XAP; + r >>= 8; + g >>= 8; + b >>= 8; + a >>= 8; + + R_VAL16(dptr) = r; + G_VAL16(dptr) = g; + B_VAL16(dptr) = b; + A_VAL16(dptr) = a; + + dptr++; + } + else + *dptr++ = sptr[xpoints[x] ]; + } + } + } + } + /* if we're scaling down vertically */ + else if(isi->xup_yup == 1) + { + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cy, j; + ullong *pix; + llong r, g, b, a, rr, gg, bb, aa; + int yap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++) + { + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + (y * dow); + for(x = dxx; x < end; x++) + { + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL16(pix) * yap) >> 10; + g = (G_VAL16(pix) * yap) >> 10; + b = (B_VAL16(pix) * yap) >> 10; + a = (A_VAL16(pix) * yap) >> 10; + for(j = (1 << 14) - yap; j > Cy; j -= Cy) + { + pix += sow; + r += (R_VAL16(pix) * Cy) >> 10; + g += (G_VAL16(pix) * Cy) >> 10; + b += (B_VAL16(pix) * Cy) >> 10; + a += (A_VAL16(pix) * Cy) >> 10; + } + if(j > 0) + { + pix += sow; + r += (R_VAL16(pix) * j) >> 10; + g += (G_VAL16(pix) * j) >> 10; + b += (B_VAL16(pix) * j) >> 10; + a += (A_VAL16(pix) * j) >> 10; + } + if(XAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x] + 1; + rr = (R_VAL16(pix) * yap) >> 10; + gg = (G_VAL16(pix) * yap) >> 10; + bb = (B_VAL16(pix) * yap) >> 10; + aa = (A_VAL16(pix) * yap) >> 10; + for(j = (1 << 14) - yap; j > Cy; j -= Cy) + { + pix += sow; + rr += (R_VAL16(pix) * Cy) >> 10; + gg += (G_VAL16(pix) * Cy) >> 10; + bb += (B_VAL16(pix) * Cy) >> 10; + aa += (A_VAL16(pix) * Cy) >> 10; + } + if(j > 0) + { + pix += sow; + rr += (R_VAL16(pix) * j) >> 10; + gg += (G_VAL16(pix) * j) >> 10; + bb += (B_VAL16(pix) * j) >> 10; + aa += (A_VAL16(pix) * j) >> 10; + } + r = r * INV_XAP; + g = g * INV_XAP; + b = b * INV_XAP; + a = a * INV_XAP; + r = (r + ((rr * XAP))) >> 12; + g = (g + ((gg * XAP))) >> 12; + b = (b + ((bb * XAP))) >> 12; + a = (a + ((aa * XAP))) >> 12; + } + else + { + r >>= 4; + g >>= 4; + b >>= 4; + a >>= 4; + } + + R_VAL16(dptr) = r; + G_VAL16(dptr) = g; + B_VAL16(dptr) = b; + A_VAL16(dptr) = a; + + dptr++; + } + } + } + /* if we're scaling down horizontally */ + else if(isi->xup_yup == 2) + { + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, j; + ullong *pix; + llong r, g, b, a, rr, gg, bb, aa; + int xap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++) + { + dptr = dest + y * dow; + for(x = dxx; x < end; x++) + { + Cx = XAP >> 16; + xap = XAP & 0xffff; + + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL16(pix) * xap) >> 10; + g = (G_VAL16(pix) * xap) >> 10; + b = (B_VAL16(pix) * xap) >> 10; + a = (A_VAL16(pix) * xap) >> 10; + for(j = (1 << 14) - xap; j > Cx; j -= Cx) + { + pix++; + r += (R_VAL16(pix) * Cx) >> 10; + g += (G_VAL16(pix) * Cx) >> 10; + b += (B_VAL16(pix) * Cx) >> 10; + a += (A_VAL16(pix) * Cx) >> 10; + } + if(j > 0) + { + pix++; + r += (R_VAL16(pix) * j) >> 10; + g += (G_VAL16(pix) * j) >> 10; + b += (B_VAL16(pix) * j) >> 10; + a += (A_VAL16(pix) * j) >> 10; + } + if(YAP > 0) + { + pix = ypoints[dyy + y] + xpoints[x] + sow; + rr = (R_VAL16(pix) * xap) >> 10; + gg = (G_VAL16(pix) * xap) >> 10; + bb = (B_VAL16(pix) * xap) >> 10; + aa = (A_VAL16(pix) * xap) >> 10; + for(j = (1 << 14) - xap; j > Cx; j -= Cx) + { + pix++; + rr += (R_VAL16(pix) * Cx) >> 10; + gg += (G_VAL16(pix) * Cx) >> 10; + bb += (B_VAL16(pix) * Cx) >> 10; + aa += (A_VAL16(pix) * Cx) >> 10; + } + if(j > 0) + { + pix++; + rr += (R_VAL16(pix) * j) >> 10; + gg += (G_VAL16(pix) * j) >> 10; + bb += (B_VAL16(pix) * j) >> 10; + aa += (A_VAL16(pix) * j) >> 10; + } + r = r * INV_YAP; + g = g * INV_YAP; + b = b * INV_YAP; + a = a * INV_YAP; + r = (r + ((rr * YAP))) >> 12; + g = (g + ((gg * YAP))) >> 12; + b = (b + ((bb * YAP))) >> 12; + a = (a + ((aa * YAP))) >> 12; + } + else + { + r >>= 4; + g >>= 4; + b >>= 4; + a >>= 4; + } + + R_VAL16(dptr) = r; + G_VAL16(dptr) = g; + B_VAL16(dptr) = b; + A_VAL16(dptr) = a; + + dptr++; + } + } + } + /* if we're scaling down horizontally & vertically */ + else{ + /*\ 'Correct' version, with math units prepared for MMXification: + |*| The operation 'b = (b * c) >> 16' translates to pmulhw, + |*| so the operation 'b = (b * c) >> d' would translate to + |*| psllw (16 - d), %mmb; pmulh %mmc, %mmb + \*/ + int Cx, Cy, i, j; + ullong *pix; + llong a, r, g, b, ax, rx, gx, bx; + int xap, yap; + + for(y = 0; y < dh; y++) + { + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + y * dow; + for(x = dxx; x < end; x++) + { + Cx = XAP >> 16; + xap = XAP & 0xffff; + + sptr = ypoints[dyy + y] + xpoints[x]; + pix = sptr; + sptr += sow; + rx = (R_VAL16(pix) * xap) >> 9; + gx = (G_VAL16(pix) * xap) >> 9; + bx = (B_VAL16(pix) * xap) >> 9; + ax = (A_VAL16(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx) + { + rx += (R_VAL16(pix) * Cx) >> 9; + gx += (G_VAL16(pix) * Cx) >> 9; + bx += (B_VAL16(pix) * Cx) >> 9; + ax += (A_VAL16(pix) * Cx) >> 9; + pix++; + } + if(i > 0) + { + rx += (R_VAL16(pix) * i) >> 9; + gx += (G_VAL16(pix) * i) >> 9; + bx += (B_VAL16(pix) * i) >> 9; + ax += (A_VAL16(pix) * i) >> 9; + } + + r = (rx * yap) >> 14; + g = (gx * yap) >> 14; + b = (bx * yap) >> 14; + a = (ax * yap) >> 14; + + + for(j = (1 << 14) - yap; j > Cy; j -= Cy) + { + pix = sptr; + sptr += sow; + rx = (R_VAL16(pix) * xap) >> 9; + gx = (G_VAL16(pix) * xap) >> 9; + bx = (B_VAL16(pix) * xap) >> 9; + ax = (A_VAL16(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx) + { + rx += (R_VAL16(pix) * Cx) >> 9; + gx += (G_VAL16(pix) * Cx) >> 9; + bx += (B_VAL16(pix) * Cx) >> 9; + ax += (A_VAL16(pix) * Cx) >> 9; + pix++; + } + if(i > 0) + { + rx += (R_VAL16(pix) * i) >> 9; + gx += (G_VAL16(pix) * i) >> 9; + bx += (B_VAL16(pix) * i) >> 9; + ax += (A_VAL16(pix) * i) >> 9; + } + + r += (rx * Cy) >> 14; + g += (gx * Cy) >> 14; + b += (bx * Cy) >> 14; + a += (ax * Cy) >> 14; + } + if(j > 0) + { + pix = sptr; + sptr += sow; + rx = (R_VAL16(pix) * xap) >> 9; + gx = (G_VAL16(pix) * xap) >> 9; + bx = (B_VAL16(pix) * xap) >> 9; + ax = (A_VAL16(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx) + { + rx += (R_VAL16(pix) * Cx) >> 9; + gx += (G_VAL16(pix) * Cx) >> 9; + bx += (B_VAL16(pix) * Cx) >> 9; + ax += (A_VAL16(pix) * Cx) >> 9; + pix++; + } + if(i > 0) + { + rx += (R_VAL16(pix) * i) >> 9; + gx += (G_VAL16(pix) * i) >> 9; + bx += (B_VAL16(pix) * i) >> 9; + ax += (A_VAL16(pix) * i) >> 9; + } + + r += (rx * j) >> 14; + g += (gx * j) >> 14; + b += (bx * j) >> 14; + a += (ax * j) >> 14; + } + + R_VAL16(dptr) = r >> 5; + G_VAL16(dptr) = g >> 5; + B_VAL16(dptr) = b >> 5; + A_VAL16(dptr) = a >> 5; + dptr++; + } + } + } +} + +/** +//Documentation of the cryptic dimgScaleAARGBA +dimgScaleAARGBA( +DImgScaleInfo *isi, // scaleinfo +unsigned int *dest, // destination img data +int dxx, // destination x location corresponding to start x of src section +int dyy, // destination y location corresponding to start y of src section +int dx, // destination x start location +int dy, // destination y start location +int dw, // destination width +int dh, // destination height +int dow, // destination scanline width +int sow); // src scanline width +*/ + +} // NameSpace Digikam diff --git a/src/libs/dimg/drawdecoding.h b/src/libs/dimg/drawdecoding.h new file mode 100644 index 00000000..e2d82fdc --- /dev/null +++ b/src/libs/dimg/drawdecoding.h @@ -0,0 +1,128 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2008-08-06 + * Description : Raw decoding settings for digiKam: + * standard libkdcraw parameters plus + * few customized for post processing. + * + * Copyright (C) 2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DRAW_DECODING_H +#define DRAW_DECODING_H + +// TQt includes. + +#include +#include + +// LibKDcraw includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT DRawDecoding : public KDcrawIface::RawDecodingSettings +{ + +public: + + /** Standard constructor with default settings + */ + DRawDecoding() + { + resetPostProcessingSettings(); + }; + + /** Standard destructor + */ + virtual ~DRawDecoding(){}; + + /** Method to use a settings to optimize time loading, for exemple to compute image histogram + */ + void optimizeTimeLoading() + { + KDcrawIface::RawDecodingSettings::optimizeTimeLoading(); + resetPostProcessingSettings(); + }; + + /** Method to reset to default values all Raw processing settings. + */ + void resetPostProcessingSettings() + { + lightness = 0.0; + contrast = 1.0; + gamma = 1.0; + saturation = 1.0; + exposureComp = 0.0; + curveAdjust = TQPointArray(); + levelsAdjust = TQValueList(); + }; + + /** Method to check is a post-processing setting have been changed + */ + bool postProcessingSettingsIsDirty() const + { + return (lightness != 0.0 || + contrast != 1.0 || + gamma != 1.0 || + saturation != 1.0 || + exposureComp != 0.0 || + !curveAdjust.isEmpty() || + !levelsAdjust.isEmpty()); + } + +public: + + /** Lightness correction value. + */ + double lightness; + + /** Contrast correction value. + */ + double contrast; + + /** Gamma correction value. + */ + double gamma; + + /** Color saturation correction value. + */ + double saturation; + + /** Exposure compensation value. + */ + double exposureComp; + + /** Luminosity curve adjustements. + */ + TQPointArray curveAdjust; + + /** Levels adjustements: 4 channels (L, R, G, B * 2 values). + */ + TQValueList levelsAdjust; +}; + +} // namespace Digikam + +#endif /* DRAW_DECODING_H */ diff --git a/src/libs/dimg/exposurecontainer.h b/src/libs/dimg/exposurecontainer.h new file mode 100644 index 00000000..62469258 --- /dev/null +++ b/src/libs/dimg/exposurecontainer.h @@ -0,0 +1,65 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-01-12 + * Description : exposure indicator settings container. + * + * Copyright (C) 2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef EXPOSURESETTINGSCONTAINER_H +#define EXPOSURESETTINGSCONTAINER_H + +// TQt includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT ExposureSettingsContainer +{ + +public: + + ExposureSettingsContainer() + { + underExposureIndicator = false; + overExposureIndicator = false; + + underExposureColor = TQt::white; + overExposureColor = TQt::black; + }; + + ~ExposureSettingsContainer(){}; + +public: + + bool underExposureIndicator; + bool overExposureIndicator; + + TQColor underExposureColor; + TQColor overExposureColor; +}; + +} // namespace Digikam + +#endif // EXPOSURESETTINGSCONTAINER_H diff --git a/src/libs/dimg/filters/Makefile.am b/src/libs/dimg/filters/Makefile.am new file mode 100644 index 00000000..25c4ee54 --- /dev/null +++ b/src/libs/dimg/filters/Makefile.am @@ -0,0 +1,21 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libdimgfilters.la + +libdimgfilters_la_SOURCES = bcgmodifier.cpp hslmodifier.cpp icctransform.cpp \ + dimgimagefilters.cpp dimgthreadedfilter.cpp \ + dimggaussianblur.cpp dimgsharpen.cpp colormodifier.cpp + +libdimgfilters_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +INCLUDES = -I$(top_srcdir)/src/libs/dimg \ + -I$(top_srcdir)/src/libs/levels \ + -I$(top_srcdir)/src/libs/histogram \ + -I$(top_srcdir)/src/digikam \ + $(LIBKDCRAW_CFLAGS) \ + $(all_includes) + + +digikaminclude_HEADERS = bcgmodifier.h hslmodifier.h dimgthreadedfilter.h dimgimagefilters.h \ + icctransform.h colormodifier.h dimgsharpen.h dimggaussianblur.h +digikamincludedir = $(includedir)/digikam diff --git a/src/libs/dimg/filters/bcgmodifier.cpp b/src/libs/dimg/filters/bcgmodifier.cpp new file mode 100644 index 00000000..b3899c20 --- /dev/null +++ b/src/libs/dimg/filters/bcgmodifier.cpp @@ -0,0 +1,208 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-03-06 + * Description : a Brightness/Contrast/Gamma image filter. + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2005-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#define CLAMP_0_255(x) TQMAX(TQMIN(x, 255), 0) +#define CLAMP_0_65535(x) TQMAX(TQMIN(x, 65535), 0) + +// C++ includes. + +#include +#include + +// Local includes. + +#include "dimg.h" +#include "bcgmodifier.h" + +namespace Digikam +{ + +class BCGModifierPriv +{ +public: + + BCGModifierPriv() + { + channel = BCGModifier::CHANNEL_ALL; + modified = false; + } + + bool modified; + + int channel; + int map16[65536]; + int map[256]; +}; + +BCGModifier::BCGModifier() +{ + d = new BCGModifierPriv; + reset(); +} + +BCGModifier::~BCGModifier() +{ + delete d; +} + +bool BCGModifier::modified() const +{ + return d->modified; +} + +void BCGModifier::reset() +{ + // initialize to linear mapping + + for (int i=0; i<65536; i++) + d->map16[i] = i; + + for (int i=0; i<256; i++) + d->map[i] = i; + + d->modified = false; +} + +void BCGModifier::applyBCG(DImg& image) +{ + if (!d->modified || image.isNull()) + return; + + applyBCG(image.bits(), image.width(), image.height(), image.sixteenBit()); +} + +void BCGModifier::applyBCG(uchar *bits, uint width, uint height, bool sixteenBits) +{ + if (!d->modified || !bits) + return; + + uint size = width*height; + + if (!sixteenBits) // 8 bits image. + { + uchar* data = bits; + + for (uint i=0; ichannel) + { + case CHANNEL_BLUE: + data[0] = CLAMP_0_255(d->map[data[0]]); + break; + + case CHANNEL_GREEN: + data[1] = CLAMP_0_255(d->map[data[1]]); + break; + + case CHANNEL_RED: + data[2] = CLAMP_0_255(d->map[data[2]]); + break; + + default: // CHANNEL_ALL + data[0] = CLAMP_0_255(d->map[data[0]]); + data[1] = CLAMP_0_255(d->map[data[1]]); + data[2] = CLAMP_0_255(d->map[data[2]]); + break; + } + + data += 4; + } + } + else // 16 bits image. + { + ushort* data = (ushort*)bits; + + for (uint i=0; ichannel) + { + case CHANNEL_BLUE: + data[0] = CLAMP_0_65535(d->map16[data[0]]); + break; + + case CHANNEL_GREEN: + data[1] = CLAMP_0_65535(d->map16[data[1]]); + break; + + case CHANNEL_RED: + data[2] = CLAMP_0_65535(d->map16[data[2]]); + break; + + default: // CHANNEL_ALL + data[0] = CLAMP_0_65535(d->map16[data[0]]); + data[1] = CLAMP_0_65535(d->map16[data[1]]); + data[2] = CLAMP_0_65535(d->map16[data[2]]); + break; + } + + data += 4; + } + } +} + +void BCGModifier::setChannel(int channel) +{ + d->channel = channel; +} + +void BCGModifier::setGamma(double val) +{ + val = (val < 0.01) ? 0.01 : val; + + for (int i=0; i<65536; i++) + d->map16[i] = lround(pow(((double)d->map16[i] / 65535.0), (1.0 / val)) * 65535.0); + + for (int i=0; i<256; i++) + d->map[i] = lround(pow(((double)d->map[i] / 255.0), (1.0 / val)) * 255.0); + + d->modified = true; +} + +void BCGModifier::setBrightness(double val) +{ + int val1 = lround(val * 65535); + + for (int i = 0; i < 65536; i++) + d->map16[i] = d->map16[i] + val1; + + val1 = lround(val * 255); + + for (int i = 0; i < 256; i++) + d->map[i] = d->map[i] + val1; + + d->modified = true; +} + +void BCGModifier::setContrast(double val) +{ + for (int i = 0; i < 65536; i++) + d->map16[i] = lround((d->map16[i] - 32767) * val) + 32767; + + for (int i = 0; i < 256; i++) + d->map[i] = lround((d->map[i] - 127) * val) + 127; + + d->modified = true; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/filters/bcgmodifier.h b/src/libs/dimg/filters/bcgmodifier.h new file mode 100644 index 00000000..b0c915d6 --- /dev/null +++ b/src/libs/dimg/filters/bcgmodifier.h @@ -0,0 +1,73 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-03-06 + * Description : a Brightness/Contrast/Gamma image filter. + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2005-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef BCGMODIFIER_H +#define BCGMODIFIER_H + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DImg; +class BCGModifierPriv; + +class DIGIKAM_EXPORT BCGModifier +{ + +public: + + enum CHANNEL + { + CHANNEL_ALL=0, + CHANNEL_RED, + CHANNEL_GREEN, + CHANNEL_BLUE + }; + +public: + + BCGModifier(); + ~BCGModifier(); + + void reset(); + bool modified() const; + + void setChannel(int channel); + void setGamma(double val); + void setBrightness(double val); + void setContrast(double val); + void applyBCG(DImg& image); + void applyBCG(uchar *bits, uint width, uint height, bool sixteenBits); + +private: + + BCGModifierPriv* d; +}; + +} // NameSpace Digikam + +#endif /* BCGMODIFIER_H */ diff --git a/src/libs/dimg/filters/colormodifier.cpp b/src/libs/dimg/filters/colormodifier.cpp new file mode 100644 index 00000000..74ddf241 --- /dev/null +++ b/src/libs/dimg/filters/colormodifier.cpp @@ -0,0 +1,287 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-18 + * Description : color modifier methods for DImg framework + * + * Copyright (C) 2006-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#define CLAMP_0_255(x) TQMAX(TQMIN(x, 255), 0) +#define CLAMP_0_65535(x) TQMAX(TQMIN(x, 65535), 0) + +// C++ includes. + +#include +#include + +// Local includes. + +#include "dimg.h" +#include "colormodifier.h" + +namespace Digikam +{ + +class ColorModifierPriv +{ +public: + + ColorModifierPriv() + { + modified = false; + } + + bool modified; + + int redMap[256]; + int greenMap[256]; + int blueMap[256]; + int alphaMap[256]; + + int redMap16[65536]; + int greenMap16[65536]; + int blueMap16[65536]; + int alphaMap16[65536]; +}; + +ColorModifier::ColorModifier() +{ + d = new ColorModifierPriv; + reset(); +} + +ColorModifier::~ColorModifier() +{ + delete d; +} + +bool ColorModifier::modified() const +{ + return d->modified; +} + +void ColorModifier::reset() +{ + // initialize to linear mapping + + for (int i=0; i<65536; i++) + { + d->redMap16[i] = i; + d->greenMap16[i] = i; + d->blueMap16[i] = i; + d->alphaMap16[i] = i; + } + + for (int i=0; i<256; i++) + { + d->redMap[i] = i; + d->greenMap[i] = i; + d->blueMap[i] = i; + d->alphaMap[i] = i; + } + + d->modified = false; +} + +void ColorModifier::setTables(int *redMap, int *greenMap, int *blueMap, int *alphaMap, bool sixteenBit) +{ + if (!sixteenBit) + { + for (int i = 0; i < 256; i++) + { + if (redMap) + d->redMap[i] = redMap[i]; + if (greenMap) + d->greenMap[i] = greenMap[i]; + if (blueMap) + d->blueMap[i] = blueMap[i]; + if (alphaMap) + d->alphaMap[i] = alphaMap[i]; + } + } + else + { + for (int i = 0; i < 65536; i++) + { + if (redMap) + d->redMap16[i] = redMap[i]; + if (greenMap) + d->greenMap16[i] = greenMap[i]; + if (blueMap) + d->blueMap16[i] = blueMap[i]; + if (alphaMap) + d->alphaMap16[i] = alphaMap[i]; + } + } + + d->modified = true; +} + +void ColorModifier::getTables(int *redMap, int *greenMap, int *blueMap, int *alphaMap, bool sixteenBit) +{ + if (!sixteenBit) + { + if (redMap) + memcpy(redMap, d->redMap, (256 * sizeof(int))); + if (greenMap) + memcpy(greenMap, d->greenMap, (256 * sizeof(int))); + if (blueMap) + memcpy(blueMap, d->blueMap, (256 * sizeof(int))); + if (alphaMap) + memcpy(alphaMap, d->alphaMap, (256 * sizeof(int))); + } + else + { + if (redMap) + memcpy(redMap, d->redMap16, (65536 * sizeof(int))); + if (greenMap) + memcpy(greenMap, d->greenMap16, (65536 * sizeof(int))); + if (blueMap) + memcpy(blueMap, d->blueMap16, (65536 * sizeof(int))); + if (alphaMap) + memcpy(alphaMap, d->alphaMap16, (65536 * sizeof(int))); + } +} + +void ColorModifier::applyColorModifier(DImg& image, double r, double g, double b, double a) +{ + if (image.isNull()) + return; + + adjustRGB(r, g, b, a, image.sixteenBit()); + + if (!image.sixteenBit()) // 8 bits image. + { + uchar* data = (uchar*) image.bits(); + + for (uint i=0; iblueMap[data[0]]; + data[1] = d->greenMap[data[1]]; + data[2] = d->redMap[data[2]]; + data[3] = d->alphaMap[data[3]]; + + data += 4; + } + } + else // 16 bits image. + { + ushort* data = (ushort*) image.bits(); + + for (uint i=0; iblueMap16[data[0]]; + data[1] = d->greenMap16[data[1]]; + data[2] = d->redMap16[data[2]]; + data[3] = d->alphaMap16[data[3]]; + + data += 4; + } + } +} + +void ColorModifier::setGamma(double val) +{ + val = (val < 0.01) ? 0.01 : val; + int val2; + + for (int i=0; i<65536; i++) + { + val2 = (int)(pow(((double)d->redMap16[i] / 65535), (1 / val)) * 65535); + d->redMap16[i] = CLAMP_0_65535(val2); + + val2 = (int)(pow(((double)d->greenMap16[i] / 65535), (1 / val)) * 65535); + d->greenMap16[i] = CLAMP_0_65535(val2); + + val2 = (int)(pow(((double)d->blueMap16[i] / 65535), (1 / val)) * 65535); + d->blueMap16[i] = CLAMP_0_65535(val2); + + val2 = (int)(pow(((double)d->alphaMap16[i] / 65535), (1 / val)) * 65535); + d->alphaMap16[i] = CLAMP_0_65535(val2); + } + + for (int i=0; i<256; i++) + { + val2 = (int)(pow(((double)d->redMap[i] / 255), (1 / val)) * 255); + d->redMap[i] = CLAMP_0_255(val2); + + val2 = (int)(pow(((double)d->greenMap[i] / 255), (1 / val)) * 255); + d->greenMap[i] = CLAMP_0_255(val2); + + val2 = (int)(pow(((double)d->blueMap[i] / 255), (1 / val)) * 255); + d->blueMap[i] = CLAMP_0_255(val2); + + val2 = (int)(pow(((double)d->alphaMap[i] / 255), (1 / val)) * 255); + d->alphaMap[i] = CLAMP_0_255(val2); + } + + d->modified = true; +} + +void ColorModifier::adjustRGB(double r, double g, double b, double a, bool sixteenBit) +{ + int r_table[65536]; + int g_table[65536]; + int b_table[65536]; + int a_table[65536]; + int dummy_table[65536]; + + if (r == 1.0 && g == 1.0 && b == 1.0 && a == 1.0) + return ; + + if (r == g && r == b && r == a) + { + setGamma(r); + } + else + { + getTables(r_table, g_table, b_table, a_table, sixteenBit); + + if(r != 1.0) + { + setGamma(r); + getTables(r_table, dummy_table, dummy_table, dummy_table, sixteenBit); + reset(); + } + + if(g != 1.0) + { + setGamma(g); + getTables(dummy_table, g_table, dummy_table, dummy_table, sixteenBit); + reset(); + } + + if(b != 1.0) + { + setGamma(b); + getTables(dummy_table, dummy_table, b_table, dummy_table, sixteenBit); + reset(); + } + + if(a != 1.0) + { + setGamma(a); + getTables(dummy_table, dummy_table, dummy_table, a_table, sixteenBit); + reset(); + } + + setTables(r_table, g_table, b_table, a_table, sixteenBit); + } +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/filters/colormodifier.h b/src/libs/dimg/filters/colormodifier.h new file mode 100644 index 00000000..9473b273 --- /dev/null +++ b/src/libs/dimg/filters/colormodifier.h @@ -0,0 +1,63 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-18 + * Description : color modifier methods for DImg framework + * + * Copyright (C) 2006-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef COLORMODIFIER_H +#define COLORMODIFIER_H + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DImg; +class ColorModifierPriv; + +class DIGIKAM_EXPORT ColorModifier +{ +public: + + ColorModifier(); + ~ColorModifier(); + + void reset(); + bool modified() const; + void applyColorModifier(DImg& image, double r, double g, double b, double a); + +private: + + void setTables(int *redMap, int *greenMap, int *blueMap, int *alphaMap, bool sixteenBit); + void getTables(int *redMap, int *greenMap, int *blueMap, int *alphaMap, bool sixteenBit); + void setGamma(double val); + void adjustRGB(double r, double g, double b, double a, bool sixteenBit); + +private: + + ColorModifierPriv* d; + +}; + +} // NameSpace Digikam + +#endif /* COLORMODIFIER_H */ diff --git a/src/libs/dimg/filters/dimggaussianblur.cpp b/src/libs/dimg/filters/dimggaussianblur.cpp new file mode 100644 index 00000000..63e19909 --- /dev/null +++ b/src/libs/dimg/filters/dimggaussianblur.cpp @@ -0,0 +1,327 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-17-07 + * Description : A Gaussian Blur threaded image filter. + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * Original Gaussian Blur algorithm copyrighted 2004 by + * Pieter Z. Voloshyn . + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// C++ includes. + +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dimgimagefilters.h" +#include "dimggaussianblur.h" + +namespace Digikam +{ + +DImgGaussianBlur::DImgGaussianBlur(DImg *orgImage, TQObject *parent, int radius) + : DImgThreadedFilter(orgImage, parent, "GaussianBlur") +{ + m_radius = radius; + initFilter(); +} + +DImgGaussianBlur::DImgGaussianBlur(DImgThreadedFilter *parentFilter, + const DImg &orgImage, const DImg &destImage, + int progressBegin, int progressEnd, int radius) + : DImgThreadedFilter(parentFilter, orgImage, destImage, progressBegin, progressEnd, + parentFilter->filterName() + ": GaussianBlur") +{ + m_radius = radius; + filterImage(); +} + + +void DImgGaussianBlur::filterImage(void) +{ + gaussianBlurImage(m_orgImage.bits(), m_orgImage.width(), m_orgImage.height(), + m_orgImage.sixteenBit(), m_radius); +} + +/** Function to apply the Gaussian Blur on an image*/ + +void DImgGaussianBlur::gaussianBlurImage(uchar *data, int width, int height, bool sixteenBit, int radius) +{ + if (!data || !width || !height) + { + DWarning() << ("DImgGaussianBlur::gaussianBlurImage: no image data available!") + << endl; + return; + } + + if (radius > 100) radius = 100; + if (radius <= 0) + { + m_destImage = m_orgImage; + return; + } + + // Gaussian kernel computation using the Radius parameter. + + int nKSize, nCenter; + double x, sd, factor, lnsd, lnfactor; + int i, j, n, h, w; + + nKSize = 2 * radius + 1; + nCenter = nKSize / 2; + int *Kernel = new int[nKSize]; + + lnfactor = (4.2485 - 2.7081) / 10 * nKSize + 2.7081; + lnsd = (0.5878 + 0.5447) / 10 * nKSize - 0.5447; + factor = exp (lnfactor); + sd = exp (lnsd); + + for (i = 0; !m_cancel && (i < nKSize); i++) + { + x = sqrt ((i - nCenter) * (i - nCenter)); + Kernel[i] = (int)(factor * exp (-0.5 * pow ((x / sd), 2)) / (sd * sqrt (2.0 * M_PI))); + } + + // Now, we need to convolve the image descriptor. + // I've worked hard here, but I think this is a very smart + // way to convolve an array, its very hard to explain how I reach + // this, but the trick here its to store the sum used by the + // previous pixel, so we sum with the other pixels that wasn't get. + + int nSumA, nSumR, nSumG, nSumB, nCount, progress; + int nKernelWidth = radius * 2 + 1; + + // We need to alloc a 2d array to help us to store the values + + int** arrMult = Alloc2DArray (nKernelWidth, sixteenBit ? 65536 : 256); + + for (i = 0; !m_cancel && (i < nKernelWidth); i++) + for (j = 0; !m_cancel && (j < (sixteenBit ? 65536 : 256)); j++) + arrMult[i][j] = j * Kernel[i]; + + // We need to copy our bits to blur bits + + uchar* pOutBits = m_destImage.bits(); + uchar* pBlur = new uchar[m_destImage.numBytes()]; + + memcpy (pBlur, data, m_destImage.numBytes()); + + // We need to initialize all the loop and iterator variables + + nSumA = nSumR = nSumG = nSumB = nCount = i = j = 0; + unsigned short* data16 = (unsigned short*)data; + unsigned short* pBlur16 = (unsigned short*)pBlur; + unsigned short* pOutBits16 = (unsigned short*)pOutBits; + + // Now, we enter in the main loop + + for (h = 0; !m_cancel && (h < height); h++) + { + for (w = 0; !m_cancel && (w < width); w++, i+=4) + { + if (!sixteenBit) // 8 bits image. + { + uchar *org, *dst; + + // first of all, we need to blur the horizontal lines + + for (n = -radius; !m_cancel && (n <= radius); n++) + { + // if is inside... + if (IsInside (width, height, w + n, h)) + { + // we points to the pixel + j = i + 4*n; + + // finally, we sum the pixels using a method similar to assigntables + + org = &data[j]; + nSumA += arrMult[n + radius][org[3]]; + nSumR += arrMult[n + radius][org[2]]; + nSumG += arrMult[n + radius][org[1]]; + nSumB += arrMult[n + radius][org[0]]; + + // we need to add to the counter, the kernel value + nCount += Kernel[n + radius]; + } + } + + if (nCount == 0) nCount = 1; + + // now, we return to blur bits the horizontal blur values + dst = &pBlur[i]; + dst[3] = (uchar)CLAMP (nSumA / nCount, 0, 255); + dst[2] = (uchar)CLAMP (nSumR / nCount, 0, 255); + dst[1] = (uchar)CLAMP (nSumG / nCount, 0, 255); + dst[0] = (uchar)CLAMP (nSumB / nCount, 0, 255); + + // ok, now we reinitialize the variables + nSumA = nSumR = nSumG = nSumB = nCount = 0; + } + else // 16 bits image. + { + unsigned short *org, *dst; + + // first of all, we need to blur the horizontal lines + + for (n = -radius; !m_cancel && (n <= radius); n++) + { + // if is inside... + if (IsInside (width, height, w + n, h)) + { + // we points to the pixel + j = i + 4*n; + + // finally, we sum the pixels using a method similar to assigntables + + org = &data16[j]; + nSumA += arrMult[n + radius][org[3]]; + nSumR += arrMult[n + radius][org[2]]; + nSumG += arrMult[n + radius][org[1]]; + nSumB += arrMult[n + radius][org[0]]; + + // we need to add to the counter, the kernel value + nCount += Kernel[n + radius]; + } + } + + if (nCount == 0) nCount = 1; + + // now, we return to blur bits the horizontal blur values + dst = &pBlur16[i]; + dst[3] = (unsigned short)CLAMP (nSumA / nCount, 0, 65535); + dst[2] = (unsigned short)CLAMP (nSumR / nCount, 0, 65535); + dst[1] = (unsigned short)CLAMP (nSumG / nCount, 0, 65535); + dst[0] = (unsigned short)CLAMP (nSumB / nCount, 0, 65535); + + // ok, now we reinitialize the variables + nSumA = nSumR = nSumG = nSumB = nCount = 0; + } + } + + progress = (int) (((double)h * 50.0) / height); + if ( progress%5 == 0 ) + postProgress( progress ); + } + + // getting the blur bits, we initialize position variables + i = j = 0; + + // We enter in the second main loop + for (w = 0; !m_cancel && (w < width); w++, i = w*4) + { + for (h = 0; !m_cancel && (h < height); h++, i += width*4) + { + if (!sixteenBit) // 8 bits image. + { + uchar *org, *dst; + + // first of all, we need to blur the vertical lines + for (n = -radius; !m_cancel && (n <= radius); n++) + { + // if is inside... + if (IsInside(width, height, w, h + n)) + { + // we points to the pixel + j = i + n * 4 * width; + + // finally, we sum the pixels using a method similar to assigntables + org = &pBlur[j]; + nSumA += arrMult[n + radius][org[3]]; + nSumR += arrMult[n + radius][org[2]]; + nSumG += arrMult[n + radius][org[1]]; + nSumB += arrMult[n + radius][org[0]]; + + // we need to add to the counter, the kernel value + nCount += Kernel[n + radius]; + } + } + + if (nCount == 0) nCount = 1; + + // To preserve Alpha channel. + memcpy (&pOutBits[i], &data[i], 4); + + // now, we return to bits the vertical blur values + dst = &pOutBits[i]; + dst[3] = (uchar)CLAMP (nSumA / nCount, 0, 255); + dst[2] = (uchar)CLAMP (nSumR / nCount, 0, 255); + dst[1] = (uchar)CLAMP (nSumG / nCount, 0, 255); + dst[0] = (uchar)CLAMP (nSumB / nCount, 0, 255); + + // ok, now we reinitialize the variables + nSumA = nSumR = nSumG = nSumB = nCount = 0; + } + else // 16 bits image. + { + unsigned short *org, *dst; + + // first of all, we need to blur the vertical lines + for (n = -radius; !m_cancel && (n <= radius); n++) + { + // if is inside... + if (IsInside(width, height, w, h + n)) + { + // we points to the pixel + j = i + n * 4 * width; + + // finally, we sum the pixels using a method similar to assigntables + org = &pBlur16[j]; + nSumA += arrMult[n + radius][org[3]]; + nSumR += arrMult[n + radius][org[2]]; + nSumG += arrMult[n + radius][org[1]]; + nSumB += arrMult[n + radius][org[0]]; + + // we need to add to the counter, the kernel value + nCount += Kernel[n + radius]; + } + } + + if (nCount == 0) nCount = 1; + + // To preserve Alpha channel. + memcpy (&pOutBits16[i], &data16[i], 8); + + // now, we return to bits the vertical blur values + dst = &pOutBits16[i]; + dst[3] = (unsigned short)CLAMP (nSumA / nCount, 0, 65535); + dst[2] = (unsigned short)CLAMP (nSumR / nCount, 0, 65535); + dst[1] = (unsigned short)CLAMP (nSumG / nCount, 0, 65535); + dst[0] = (unsigned short)CLAMP (nSumB / nCount, 0, 65535); + + // ok, now we reinitialize the variables + nSumA = nSumR = nSumG = nSumB = nCount = 0; + } + } + + progress = (int) (50.0 + ((double)w * 50.0) / width); + if ( progress%5 == 0 ) + postProgress( progress ); + } + + // now, we must free memory + Free2DArray (arrMult, nKernelWidth); + delete [] pBlur; + delete [] Kernel; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/filters/dimggaussianblur.h b/src/libs/dimg/filters/dimggaussianblur.h new file mode 100644 index 00000000..e88944bc --- /dev/null +++ b/src/libs/dimg/filters/dimggaussianblur.h @@ -0,0 +1,97 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-17-07 + * Description : A Gaussian Blur threaded image filter. + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DIMGGAUSSIAN_BLUR_H +#define DIMGGAUSSIAN_BLUR_H + +// Digikam includes. + +#include "digikam_export.h" + +// Local includes. + +#include "dimgthreadedfilter.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT DImgGaussianBlur : public DImgThreadedFilter +{ + +public: + + DImgGaussianBlur(DImg *orgImage, TQObject *parent=0, int radius=3); + + // Constructor for slave mode: execute immediately in current thread with specified master filter + DImgGaussianBlur(DImgThreadedFilter *parentFilter, const DImg &orgImage, const DImg &destImage, + int progressBegin=0, int progressEnd=100, int radius=3); + + ~DImgGaussianBlur(){}; + +private: // Gaussian blur filter data. + + int m_radius; + +private: // Gaussian blur filter methods. + + virtual void filterImage(void); + + void gaussianBlurImage(uchar *data, int width, int height, bool sixteenBit, int radius); + + // function to allocate a 2d array + int** Alloc2DArray (int Columns, int Rows) + { + // First, we declare our future 2d array to be returned + int** lpcArray = 0L; + + // Now, we alloc the main pointer with Columns + lpcArray = new int*[Columns]; + + for (int i = 0; i < Columns; i++) + lpcArray[i] = new int[Rows]; + + return (lpcArray); + }; + + // Function to deallocates the 2d array previously created + void Free2DArray (int** lpcArray, int Columns) + { + // loop to dealocate the columns + for (int i = 0; i < Columns; i++) + delete [] lpcArray[i]; + + // now, we delete the main pointer + delete [] lpcArray; + }; + + inline bool IsInside (int Width, int Height, int X, int Y) + { + bool bIsWOk = ((X < 0) ? false : (X >= Width ) ? false : true); + bool bIsHOk = ((Y < 0) ? false : (Y >= Height) ? false : true); + return (bIsWOk && bIsHOk); + }; +}; + +} // NameSpace Digikam + +#endif /* DIMGGAUSSIAN_BLUR_H */ diff --git a/src/libs/dimg/filters/dimgimagefilters.cpp b/src/libs/dimg/filters/dimgimagefilters.cpp new file mode 100644 index 00000000..b964ed4d --- /dev/null +++ b/src/libs/dimg/filters/dimgimagefilters.cpp @@ -0,0 +1,977 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-24-01 + * Description : misc image filters + * + * Copyright (C) 2004-2007 by Gilles Caulier + * + * Original Equalise and StretchContrast Algorithms copyright 2002 + * by Daniel M. Duley from KImageEffect API. + * + * Original Normalize Image algorithm copyrighted 1997 by + * Adam D. Moss from Gimp 2.0 implementation. + * + * Original channel mixer algorithm copyrighted 2002 by + * Martin Guldahl from Gimp 2.2 + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// C++ includes. + +#include +#include + +// Local includes. + +#include "imagehistogram.h" +#include "imagelevels.h" +#include "dcolor.h" +#include "ddebug.h" +#include "dimggaussianblur.h" +#include "dimgsharpen.h" +#include "dimgimagefilters.h" + +namespace Digikam +{ + +/** Performs an histogram equalisation of the image. + this method adjusts the brightness of colors across the + active image so that the histogram for the value channel + is as nearly as possible flat, that is, so that each possible + brightness value appears at about the same number of pixels + as each other value. Sometimes Equalize works wonderfully at + enhancing the contrasts in an image. Other times it gives + garbage. It is a very powerful operation, which can either work + miracles on an image or destroy it.*/ +void DImgImageFilters::equalizeImage(uchar *data, int w, int h, bool sixteenBit) +{ + if (!data || !w || !h) + { + DWarning() << ("DImgImageFilters::equalizeImage: no image data available!") << endl; + return; + } + + struct double_packet high, low, intensity; + struct double_packet *map; + struct int_packet *equalize_map; + long i; + + // Create an histogram of the current image. + ImageHistogram *histogram = new ImageHistogram(data, w, h, sixteenBit); + + // Memory allocation. + map = new double_packet[histogram->getHistogramSegment()]; + equalize_map = new int_packet[histogram->getHistogramSegment()]; + + if( !histogram || !map || !equalize_map ) + { + if(histogram) + delete histogram; + + if(map) + delete [] map; + + if(equalize_map) + delete [] equalize_map; + + DWarning() << ("DImgImageFilters::equalizeImage: Unable to allocate memory!") << endl; + return; + } + + // Integrate the histogram to get the equalization map. + + memset(&intensity, 0, sizeof(struct double_packet)); + memset(&high, 0, sizeof(struct double_packet)); + memset(&low, 0, sizeof(struct double_packet)); + + for(i = 0 ; i < histogram->getHistogramSegment() ; i++) + { + intensity.red += histogram->getValue(ImageHistogram::RedChannel, i); + intensity.green += histogram->getValue(ImageHistogram::GreenChannel, i); + intensity.blue += histogram->getValue(ImageHistogram::BlueChannel, i); + intensity.alpha += histogram->getValue(ImageHistogram::AlphaChannel, i); + map[i] = intensity; + } + + // Stretch the histogram. + + low = map[0]; + high = map[histogram->getHistogramSegment()-1]; + memset(equalize_map, 0, histogram->getHistogramSegment()*sizeof(int_packet)); + + for(i = 0 ; i < histogram->getHistogramSegment() ; i++) + { + if(high.red != low.red) + equalize_map[i].red = (uint)(((256*histogram->getHistogramSegment() -1) * + (map[i].red-low.red))/(high.red-low.red)); + + if(high.green != low.green) + equalize_map[i].green = (uint)(((256*histogram->getHistogramSegment() -1) * + (map[i].green-low.green))/(high.green-low.green)); + + if(high.blue != low.blue) + equalize_map[i].blue = (uint)(((256*histogram->getHistogramSegment() -1) * + (map[i].blue-low.blue))/(high.blue-low.blue)); + + if(high.alpha != low.alpha) + equalize_map[i].alpha = (uint)(((256*histogram->getHistogramSegment() -1) * + (map[i].alpha-low.alpha))/(high.alpha-low.alpha)); + } + + delete histogram; + delete [] map; + + // Apply results to image. + + if (!sixteenBit) // 8 bits image. + { + uchar red, green, blue, alpha; + uchar *ptr = data; + + for (i = 0 ; i < w*h ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + alpha = ptr[3]; + + if(low.red != high.red) + red = (equalize_map[red].red)/257; + + if(low.green != high.green) + green = (equalize_map[green].green)/257; + + if(low.blue != high.blue) + blue = (equalize_map[blue].blue)/257; + + if(low.alpha != high.alpha) + alpha = (equalize_map[alpha].alpha)/257; + + ptr[0] = blue; + ptr[1] = green; + ptr[2] = red; + ptr[3] = alpha; + ptr += 4; + } + } + else // 16 bits image. + { + unsigned short red, green, blue, alpha; + unsigned short *ptr = (unsigned short *)data; + + for (i = 0 ; i < w*h ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + alpha = ptr[3]; + + if(low.red != high.red) + red = (equalize_map[red].red)/257; + + if(low.green != high.green) + green = (equalize_map[green].green)/257; + + if(low.blue != high.blue) + blue = (equalize_map[blue].blue)/257; + + if(low.alpha != high.alpha) + alpha = (equalize_map[alpha].alpha)/257; + + ptr[0] = blue; + ptr[1] = green; + ptr[2] = red; + ptr[3] = alpha; + ptr += 4; + } + } + + delete [] equalize_map; +} + +/** Performs histogram normalization of the image. The algorithm normalizes + the pixel values from an image for to span the full range + of color values. This is a contrast enhancement technique.*/ +void DImgImageFilters::stretchContrastImage(uchar *data, int w, int h, bool sixteenBit) +{ + if (!data || !w || !h) + { + DWarning() << ("DImgImageFilters::stretchContrastImage: no image data available!") << endl; + return; + } + + struct double_packet high, low, intensity; + struct int_packet *normalize_map; + long long number_pixels; + long i; + unsigned long threshold_intensity; + + // Create an histogram of the current image. + ImageHistogram *histogram = new ImageHistogram(data, w, h, sixteenBit); + + // Memory allocation. + normalize_map = new int_packet[histogram->getHistogramSegment()]; + + if( !histogram || !normalize_map ) + { + if(histogram) + delete histogram; + + if(normalize_map) + delete [] normalize_map; + + DWarning() << ("DImgImageFilters::stretchContrastImage: Unable to allocate memory!") << endl; + return; + } + + // Find the histogram boundaries by locating the 0.1 percent levels. + + number_pixels = (long long)(w*h); + threshold_intensity = number_pixels / 1000; + + memset(&high, 0, sizeof(struct double_packet)); + memset(&low, 0, sizeof(struct double_packet)); + + // Red. + + memset(&intensity, 0, sizeof(struct double_packet)); + + for(high.red = histogram->getHistogramSegment()-1 ; high.red != 0 ; high.red--) + { + intensity.red += histogram->getValue(ImageHistogram::RedChannel, (int)high.red); + + if( intensity.red > threshold_intensity ) + break; + } + + if( low.red == high.red ) + { + threshold_intensity = 0; + memset(&intensity, 0, sizeof(struct double_packet)); + + for(low.red = 0 ; low.red < histogram->getHistogramSegment()-1 ; low.red++) + { + intensity.red += histogram->getValue(ImageHistogram::RedChannel, (int)low.red); + + if( intensity.red > threshold_intensity ) + break; + } + + memset(&intensity, 0, sizeof(struct double_packet)); + + for(high.red = histogram->getHistogramSegment()-1 ; high.red != 0 ; high.red--) + { + intensity.red += histogram->getValue(ImageHistogram::RedChannel, (int)high.red); + + if( intensity.red > threshold_intensity ) + break; + } + } + + // Green. + + memset(&intensity, 0, sizeof(struct double_packet)); + + for(high.green = histogram->getHistogramSegment()-1 ; high.green != 0 ; high.green--) + { + intensity.green += histogram->getValue(ImageHistogram::GreenChannel, (int)high.green); + + if( intensity.green > threshold_intensity ) + break; + } + + if( low.green == high.green ) + { + threshold_intensity = 0; + memset(&intensity, 0, sizeof(struct double_packet)); + + for(low.green = 0 ; low.green < histogram->getHistogramSegment()-1 ; low.green++) + { + intensity.green += histogram->getValue(ImageHistogram::GreenChannel, (int)low.green); + + if( intensity.green > threshold_intensity ) + break; + } + + memset(&intensity, 0, sizeof(struct double_packet)); + + for(high.green = histogram->getHistogramSegment()-1 ; high.green != 0 ; high.green--) + { + intensity.green += histogram->getValue(ImageHistogram::GreenChannel, (int)high.green); + + if( intensity.green > threshold_intensity ) + break; + } + } + + // Blue. + + memset(&intensity, 0, sizeof(struct double_packet)); + + for(high.blue = histogram->getHistogramSegment()-1 ; high.blue != 0 ; high.blue--) + { + intensity.blue += histogram->getValue(ImageHistogram::BlueChannel, (int)high.blue); + + if( intensity.blue > threshold_intensity ) + break; + } + + if( low.blue == high.blue ) + { + threshold_intensity = 0; + memset(&intensity, 0, sizeof(struct double_packet)); + + for(low.blue = 0 ; low.blue < histogram->getHistogramSegment()-1 ; low.blue++) + { + intensity.blue += histogram->getValue(ImageHistogram::BlueChannel, (int)low.blue); + + if( intensity.blue > threshold_intensity ) + break; + } + + memset(&intensity, 0, sizeof(struct double_packet)); + + for(high.blue = histogram->getHistogramSegment()-1 ; high.blue != 0 ; high.blue--) + { + intensity.blue += histogram->getValue(ImageHistogram::BlueChannel, (int)high.blue); + + if( intensity.blue > threshold_intensity ) + break; + } + } + + // Alpha. + + memset(&intensity, 0, sizeof(struct double_packet)); + + for(high.alpha = histogram->getHistogramSegment()-1 ; high.alpha != 0 ; high.alpha--) + { + intensity.alpha += histogram->getValue(ImageHistogram::AlphaChannel, (int)high.alpha); + + if( intensity.alpha > threshold_intensity ) + break; + } + + if( low.alpha == high.alpha ) + { + threshold_intensity = 0; + memset(&intensity, 0, sizeof(struct double_packet)); + + for(low.alpha = 0 ; low.alpha < histogram->getHistogramSegment()-1 ; low.alpha++) + { + intensity.alpha += histogram->getValue(ImageHistogram::AlphaChannel, (int)low.alpha); + + if( intensity.alpha > threshold_intensity ) + break; + } + + memset(&intensity, 0, sizeof(struct double_packet)); + + for(high.alpha = histogram->getHistogramSegment()-1 ; high.alpha != 0 ; high.alpha--) + { + intensity.alpha += histogram->getValue(ImageHistogram::AlphaChannel, (int)high.alpha); + + if( intensity.alpha > threshold_intensity ) + break; + } + } + + delete histogram; + + // Stretch the histogram to create the normalized image mapping. + + memset(normalize_map, 0, histogram->getHistogramSegment()*sizeof(struct int_packet)); + + for(i = 0 ; i <= (long)histogram->getHistogramSegment()-1 ; i++) + { + if(i < (long) low.red) + normalize_map[i].red = 0; + else if (i > (long) high.red) + normalize_map[i].red = (256*histogram->getHistogramSegment() -1); + else if (low.red != high.red) + normalize_map[i].red = (int)(((256*histogram->getHistogramSegment() -1)*(i-low.red))/(high.red-low.red)); + + if(i < (long) low.green) + normalize_map[i].green = 0; + else if (i > (long) high.green) + normalize_map[i].green = (256*histogram->getHistogramSegment() -1); + else if (low.green != high.green) + normalize_map[i].green = (int)(((256*histogram->getHistogramSegment() -1)*(i-low.green))/(high.green-low.green)); + + if(i < (long) low.blue) + normalize_map[i].blue = 0; + else if (i > (long) high.blue) + normalize_map[i].blue = (256*histogram->getHistogramSegment() -1); + else if (low.blue != high.blue) + normalize_map[i].blue = (int)(((256*histogram->getHistogramSegment() -1)*(i-low.blue))/(high.blue-low.blue)); + + if(i < (long) low.alpha) + normalize_map[i].alpha = 0; + else if (i > (long) high.alpha) + normalize_map[i].alpha = (256*histogram->getHistogramSegment() -1); + else if (low.alpha != high.alpha) + normalize_map[i].alpha = (int)(((256*histogram->getHistogramSegment() -1)*(i-low.alpha))/(high.alpha-low.alpha)); + } + + // Apply result to image. + + if (!sixteenBit) // 8 bits image. + { + uchar red, green, blue, alpha; + uchar *ptr = data; + + for (i = 0 ; i < w*h ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + alpha = ptr[3]; + + if(low.red != high.red) + red = (normalize_map[red].red)/257; + + if(low.green != high.green) + green = (normalize_map[green].green)/257; + + if(low.blue != high.blue) + blue = (normalize_map[blue].blue)/257; + + if(low.alpha != high.alpha) + alpha = (normalize_map[alpha].alpha)/257; + + ptr[0] = blue; + ptr[1] = green; + ptr[2] = red; + ptr[3] = alpha; + ptr += 4; + } + } + else // 16 bits image. + { + unsigned short red, green, blue, alpha; + unsigned short *ptr = (unsigned short *)data; + + for (i = 0 ; i < w*h ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + alpha = ptr[3]; + + if(low.red != high.red) + red = (normalize_map[red].red)/257; + + if(low.green != high.green) + green = (normalize_map[green].green)/257; + + if(low.blue != high.blue) + blue = (normalize_map[blue].blue)/257; + + if(low.alpha != high.alpha) + alpha = (normalize_map[alpha].alpha)/257; + + ptr[0] = blue; + ptr[1] = green; + ptr[2] = red; + ptr[3] = alpha; + ptr += 4; + } + } + + delete [] normalize_map; +} + +/** This method scales brightness values across the active + image so that the darkest point becomes black, and the + brightest point becomes as bright as possible without + altering its hue. This is often a magic fix for + images that are dim or washed out.*/ +void DImgImageFilters::normalizeImage(uchar *data, int w, int h, bool sixteenBit) +{ + NormalizeParam param; + int x, i; + unsigned short range; + + int segments = sixteenBit ? 65536 : 256; + + // Memory allocation. + + param.lut = new unsigned short[segments]; + + // Find min. and max. values. + + param.min = segments-1; + param.max = 0; + + if (!sixteenBit) // 8 bits image. + { + uchar red, green, blue; + uchar *ptr = data; + + for (i = 0 ; i < w*h ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + + if (red < param.min) param.min = red; + if (red > param.max) param.max = red; + + if (green < param.min) param.min = green; + if (green > param.max) param.max = green; + + if (blue < param.min) param.min = blue; + if (blue > param.max) param.max = blue; + + ptr += 4; + } + } + else // 16 bits image. + { + unsigned short red, green, blue; + unsigned short *ptr = (unsigned short *)data; + + for (i = 0 ; i < w*h ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + + if (red < param.min) param.min = red; + if (red > param.max) param.max = red; + + if (green < param.min) param.min = green; + if (green > param.max) param.max = green; + + if (blue < param.min) param.min = blue; + if (blue > param.max) param.max = blue; + + ptr += 4; + } + } + + // Calculate LUT. + + range = (unsigned short)(param.max - param.min); + + if (range != 0) + { + for (x = (int)param.min ; x <= (int)param.max ; x++) + param.lut[x] = (unsigned short)((segments-1) * (x - param.min) / range); + } + else + param.lut[(int)param.min] = (unsigned short)param.min; + + // Apply LUT to image. + + if (!sixteenBit) // 8 bits image. + { + uchar red, green, blue; + uchar *ptr = data; + + for (i = 0 ; i < w*h ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + + ptr[0] = param.lut[blue]; + ptr[1] = param.lut[green]; + ptr[2] = param.lut[red]; + + ptr += 4; + } + } + else // 16 bits image. + { + unsigned short red, green, blue; + unsigned short *ptr = (unsigned short *)data; + + for (i = 0 ; i < w*h ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + + ptr[0] = param.lut[blue]; + ptr[1] = param.lut[green]; + ptr[2] = param.lut[red]; + + ptr += 4; + } + } + + delete [] param.lut; +} + +/** Performs histogram auto correction of levels. + This method maximizes the tonal range in the Red, + Green, and Blue channels. It search the image shadow and highlight + limit values and adjust the Red, Green, and Blue channels + to a full histogram range.*/ +void DImgImageFilters::autoLevelsCorrectionImage(uchar *data, int w, int h, bool sixteenBit) +{ + if (!data || !w || !h) + { + DWarning() << ("DImgImageFilters::autoLevelsCorrectionImage: no image data available!") + << endl; + return; + } + uchar* desData; + + // Create the new empty destination image data space. + if (sixteenBit) + desData = new uchar[w*h*8]; + else + desData = new uchar[w*h*4]; + + // Create an histogram of the current image. + ImageHistogram *histogram = new ImageHistogram(data, w, h, sixteenBit); + + // Create an empty instance of levels to use. + ImageLevels *levels = new ImageLevels(sixteenBit); + + // Initialize an auto levels correction of the histogram. + levels->levelsAuto(histogram); + + // Calculate the LUT to apply on the image. + levels->levelsLutSetup(ImageHistogram::AlphaChannel); + + // Apply the lut to the image. + levels->levelsLutProcess(data, desData, w, h); + + if (sixteenBit) + memcpy (data, desData, w*h*8); + else + memcpy (data, desData, w*h*4); + + delete [] desData; + delete histogram; + delete levels; +} + +/** Performs image colors inversion. This tool is used for negate image + resulting of a positive film scanned.*/ +void DImgImageFilters::invertImage(uchar *data, int w, int h, bool sixteenBit) +{ + if (!data || !w || !h) + { + DWarning() << ("DImgImageFilters::invertImage: no image data available!") + << endl; + return; + } + + if (!sixteenBit) // 8 bits image. + { + uchar *ptr = data; + + for (int i = 0 ; i < w*h ; i++) + { + ptr[0] = 255 - ptr[0]; + ptr[1] = 255 - ptr[1]; + ptr[2] = 255 - ptr[2]; + ptr[3] = 255 - ptr[3]; + ptr += 4; + } + } + else // 16 bits image. + { + unsigned short *ptr = (unsigned short *)data; + + for (int i = 0 ; i < w*h ; i++) + { + ptr[0] = 65535 - ptr[0]; + ptr[1] = 65535 - ptr[1]; + ptr[2] = 65535 - ptr[2]; + ptr[3] = 65535 - ptr[3]; + ptr += 4; + } + } +} + +/** Mix RGB channel color from image*/ +void DImgImageFilters::channelMixerImage(uchar *data, int Width, int Height, bool sixteenBit, + bool bPreserveLum, bool bMonochrome, + float rrGain, float rgGain, float rbGain, + float grGain, float ggGain, float gbGain, + float brGain, float bgGain, float bbGain) +{ + if (!data || !Width || !Height) + { + DWarning() << ("DImgImageFilters::channelMixerImage: no image data available!") + << endl; + return; + } + + int i; + + double rnorm = CalculateNorm (rrGain, rgGain, rbGain, bPreserveLum); + double gnorm = CalculateNorm (grGain, ggGain, gbGain, bPreserveLum); + double bnorm = CalculateNorm (brGain, bgGain, bbGain, bPreserveLum); + + if (!sixteenBit) // 8 bits image. + { + uchar nGray, red, green, blue; + uchar *ptr = data; + + for (i = 0 ; i < Width*Height ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + + if (bMonochrome) + { + nGray = MixPixel (rrGain, rgGain, rbGain, + (unsigned short)red, (unsigned short)green, (unsigned short)blue, + sixteenBit, rnorm); + ptr[0] = ptr[1] = ptr[2] = nGray; + } + else + { + ptr[0] = (uchar)MixPixel (brGain, bgGain, bbGain, + (unsigned short)red, (unsigned short)green, (unsigned short)blue, + sixteenBit, bnorm); + ptr[1] = (uchar)MixPixel (grGain, ggGain, gbGain, + (unsigned short)red, (unsigned short)green, (unsigned short)blue, + sixteenBit, gnorm); + ptr[2] = (uchar)MixPixel (rrGain, rgGain, rbGain, + (unsigned short)red, (unsigned short)green, (unsigned short)blue, + sixteenBit, rnorm); + } + + ptr += 4; + } + } + else // 16 bits image. + { + unsigned short nGray, red, green, blue; + unsigned short *ptr = (unsigned short *)data; + + for (i = 0 ; i < Width*Height ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + + if (bMonochrome) + { + nGray = MixPixel (rrGain, rgGain, rbGain, red, green, blue, sixteenBit, rnorm); + ptr[0] = ptr[1] = ptr[2] = nGray; + } + else + { + ptr[0] = MixPixel (brGain, bgGain, bbGain, red, green, blue, sixteenBit, bnorm); + ptr[1] = MixPixel (grGain, ggGain, gbGain, red, green, blue, sixteenBit, gnorm); + ptr[2] = MixPixel (rrGain, rgGain, rbGain, red, green, blue, sixteenBit, rnorm); + } + + ptr += 4; + } + } +} + +/** Change color tonality of an image to appling a RGB color mask.*/ +void DImgImageFilters::changeTonality(uchar *data, int width, int height, bool sixteenBit, + int redMask, int greenMask, int blueMask) +{ + if (!data || !width || !height) + { + DWarning() << ("DImgImageFilters::changeTonality: no image data available!") + << endl; + return; + } + + int hue, sat, lig; + + DColor mask(redMask, greenMask, blueMask, 0, sixteenBit); + mask.getHSL(&hue, &sat, &lig); + + if (!sixteenBit) // 8 bits image. + { + uchar *ptr = data; + + for (int i = 0 ; i < width*height ; i++) + { + // Convert to grayscale using tonal mask + + lig = ROUND (0.3 * ptr[2] + 0.59 * ptr[1] + 0.11 * ptr[0]); + + mask.setRGB(hue, sat, lig, sixteenBit); + + ptr[0] = (uchar)mask.blue(); + ptr[1] = (uchar)mask.green(); + ptr[2] = (uchar)mask.red(); + ptr += 4; + } + } + else // 16 bits image. + { + unsigned short *ptr = (unsigned short *)data; + + for (int i = 0 ; i < width*height ; i++) + { + // Convert to grayscale using tonal mask + + lig = ROUND (0.3 * ptr[2] + 0.59 * ptr[1] + 0.11 * ptr[0]); + + mask.setRGB(hue, sat, lig, sixteenBit); + + ptr[0] = (unsigned short)mask.blue(); + ptr[1] = (unsigned short)mask.green(); + ptr[2] = (unsigned short)mask.red(); + ptr += 4; + } + } +} + +/** Function to apply the GaussianBlur on an image. This method do not use a + dedicaced thread.*/ +void DImgImageFilters::gaussianBlurImage(uchar *data, int width, int height, bool sixteenBit, int radius) +{ + if (!data || !width || !height) + { + DWarning() << ("DImgImageFilters::gaussianBlurImage: no image data available!") + << endl; + return; + } + + if (radius > 100) radius = 100; + if (radius <= 0) return; + + DImg orgImage(width, height, sixteenBit, true, data); + DImgGaussianBlur *filter = new DImgGaussianBlur(&orgImage, 0L, radius); + DImg imDest = filter->getTargetImage(); + memcpy( data, imDest.bits(), imDest.numBytes() ); + delete filter; +} + +/** Function to apply the sharpen filter on an image. This method do not use a + dedicaced thread.*/ +void DImgImageFilters::sharpenImage(uchar *data, int width, int height, bool sixteenBit, int radius) +{ + if (!data || !width || !height) + { + DWarning() << ("DImgImageFilters::sharpenImage: no image data available!") + << endl; + return; + } + + if (radius > 100) radius = 100; + if (radius <= 0) return; + + DImg orgImage(width, height, sixteenBit, true, data); + DImgSharpen *filter = new DImgSharpen(&orgImage, 0L, radius); + DImg imDest = filter->getTargetImage(); + memcpy( data, imDest.bits(), imDest.numBytes() ); + delete filter; +} + +/** Function to perform pixel antialiasing with 8 bits/color/pixel images. This method is used to smooth target + image in transformation method like free rotation or shear tool. */ +void DImgImageFilters::pixelAntiAliasing(uchar *data, int Width, int Height, double X, double Y, + uchar *A, uchar *R, uchar *G, uchar *B) +{ + int nX, nY, j; + double lfWeightX[2], lfWeightY[2], lfWeight; + double lfTotalR = 0.0, lfTotalG = 0.0, lfTotalB = 0.0, lfTotalA = 0.0; + + nX = (int)X; + nY = (int)Y; + + if (Y >= 0.0) + lfWeightY[0] = 1.0 - (lfWeightY[1] = Y - (double)nY); + else + lfWeightY[1] = 1.0 - (lfWeightY[0] = -(Y - (double)nY)); + + if (X >= 0.0) + lfWeightX[0] = 1.0 - (lfWeightX[1] = X - (double)nX); + else + lfWeightX[1] = 1.0 - (lfWeightX[0] = -(X - (double)nX)); + + for (int loopx = 0; loopx <= 1; loopx++) + { + for (int loopy = 0; loopy <= 1; loopy++) + { + lfWeight = lfWeightX[loopx] * lfWeightY[loopy]; + j = setPositionAdjusted (Width, Height, nX + loopx, nY + loopy); + + lfTotalB += ((double)data[j] * lfWeight); + j++; + lfTotalG += ((double)data[j] * lfWeight); + j++; + lfTotalR += ((double)data[j] * lfWeight); + j++; + lfTotalA += ((double)data[j] * lfWeight); + j++; + } + } + + *B = CLAMP0255((int)lfTotalB); + *G = CLAMP0255((int)lfTotalG); + *R = CLAMP0255((int)lfTotalR); + *A = CLAMP0255((int)lfTotalA); +} + +/** Function to perform pixel antialiasing with 16 bits/color/pixel images. This method is used to smooth target + image in transformation method like free rotation or shear tool. */ +void DImgImageFilters::pixelAntiAliasing16(unsigned short *data, int Width, int Height, double X, double Y, + unsigned short *A, unsigned short *R, unsigned short *G, + unsigned short *B) +{ + int nX, nY, j; + double lfWeightX[2], lfWeightY[2], lfWeight; + double lfTotalR = 0.0, lfTotalG = 0.0, lfTotalB = 0.0, lfTotalA = 0.0; + + nX = (int)X; + nY = (int)Y; + + if (Y >= 0.0) + lfWeightY[0] = 1.0 - (lfWeightY[1] = Y - (double)nY); + else + lfWeightY[1] = 1.0 - (lfWeightY[0] = -(Y - (double)nY)); + + if (X >= 0.0) + lfWeightX[0] = 1.0 - (lfWeightX[1] = X - (double)nX); + else + lfWeightX[1] = 1.0 - (lfWeightX[0] = -(X - (double)nX)); + + for (int loopx = 0; loopx <= 1; loopx++) + { + for (int loopy = 0; loopy <= 1; loopy++) + { + lfWeight = lfWeightX[loopx] * lfWeightY[loopy]; + j = setPositionAdjusted (Width, Height, nX + loopx, nY + loopy); + + lfTotalB += ((double)data[j] * lfWeight); + j++; + lfTotalG += ((double)data[j] * lfWeight); + j++; + lfTotalR += ((double)data[j] * lfWeight); + j++; + lfTotalA += ((double)data[j] * lfWeight); + j++; + } + } + + *B = CLAMP065535((int)lfTotalB); + *G = CLAMP065535((int)lfTotalG); + *R = CLAMP065535((int)lfTotalR); + *A = CLAMP065535((int)lfTotalA); +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/filters/dimgimagefilters.h b/src/libs/dimg/filters/dimgimagefilters.h new file mode 100644 index 00000000..009c3efb --- /dev/null +++ b/src/libs/dimg/filters/dimgimagefilters.h @@ -0,0 +1,133 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-24-01 + * Description : misc image filters + * + * Copyright (C) 2004-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DIMGIMAGE_FILTERS_H +#define DIMGIMAGE_FILTERS_H + +#define CLAMP0255(a) TQMIN(TQMAX(a,0), 255) +#define CLAMP065535(a) TQMIN(TQMAX(a,0), 65535) +#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) +#define ROUND(x) ((int) ((x) + 0.5)) + +// C++ includes. + +#include + +// Digikam includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT DImgImageFilters +{ +public: + + DImgImageFilters(){}; + ~DImgImageFilters(){}; + +private: // Private structures used internally. + + struct double_packet + { + double red; + double green; + double blue; + double alpha; + }; + + struct int_packet + { + unsigned int red; + unsigned int green; + unsigned int blue; + unsigned int alpha; + }; + + struct NormalizeParam + { + unsigned short *lut; + double min; + double max; + }; + +private: // Private methods used internally. + + // Methods for Channel Mixer. + + inline double CalculateNorm(float RedGain, float GreenGain, float BlueGain, bool bPreserveLum) + { + double lfSum = RedGain + GreenGain + BlueGain; + + if ((lfSum == 0.0) || (bPreserveLum == false)) + return (1.0); + + return( fabs (1.0 / lfSum) ); + }; + + inline unsigned short MixPixel(float RedGain, float GreenGain, float BlueGain, + unsigned short R, unsigned short G, unsigned short B, bool sixteenBit, + double Norm) + { + double lfMix = RedGain * (double)R + GreenGain * (double)G + BlueGain * (double)B; + lfMix *= Norm; + int segment = sixteenBit ? 65535 : 255; + + return( (unsigned short)CLAMP (lfMix, 0, segment) ); + }; + + inline int setPositionAdjusted (int Width, int Height, int X, int Y) + { + X = (X < 0) ? 0 : (X >= Width ) ? Width - 1 : X; + Y = (Y < 0) ? 0 : (Y >= Height) ? Height - 1 : Y; + return (Y*Width*4 + 4*X); + }; + +public: // Public methods. + + void equalizeImage(uchar *data, int w, int h, bool sixteenBit); + void stretchContrastImage(uchar *data, int w, int h, bool sixteenBit); + void normalizeImage(uchar *data, int w, int h, bool sixteenBit); + void autoLevelsCorrectionImage(uchar *data, int w, int h, bool sixteenBit); + void invertImage(uchar *data, int w, int h, bool sixteenBit); + void channelMixerImage(uchar *data, int Width, int Height, bool sixteenBit, + bool bPreserveLum, bool bMonochrome, + float rrGain, float rgGain, float rbGain, + float grGain, float ggGain, float gbGain, + float brGain, float bgGain, float bbGain); + void changeTonality(uchar *data, int width, int height, bool sixteenBit, + int redMask, int greenMask, int blueMask); + void gaussianBlurImage(uchar *data, int width, int height, bool sixteenBit, int radius); + void sharpenImage(uchar *data, int width, int height, bool sixteenBit, int radius); + + void pixelAntiAliasing(uchar *data, int Width, int Height, double X, double Y, + uchar *A, uchar *R, uchar *G, uchar *B); + + void pixelAntiAliasing16(unsigned short *data, int Width, int Height, double X, double Y, + unsigned short *A, unsigned short *R, unsigned short *G, unsigned short *B); +}; + +} // NameSpace Digikam + +#endif /* DIMGIMAGE_FILTERS_H */ diff --git a/src/libs/dimg/filters/dimgsharpen.cpp b/src/libs/dimg/filters/dimgsharpen.cpp new file mode 100644 index 00000000..a27d5b10 --- /dev/null +++ b/src/libs/dimg/filters/dimgsharpen.cpp @@ -0,0 +1,243 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-17-07 + * Description : A Sharpen threaded image filter. + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * Original Sharpen algorithm copyright 2002 + * by Daniel M. Duley from KImageEffect API. + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#define SQ2PI 2.50662827463100024161235523934010416269302368164062 +#define Epsilon 1.0e-12 + +// C++ includes. + +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dimgimagefilters.h" +#include "dimgsharpen.h" + +namespace Digikam +{ + +DImgSharpen::DImgSharpen(DImg *orgImage, TQObject *parent, double radius, double sigma) + : DImgThreadedFilter(orgImage, parent, "Sharpen") +{ + m_radius = radius; + m_sigma = sigma; + initFilter(); +} + +DImgSharpen::DImgSharpen(DImgThreadedFilter *parentFilter, + const DImg &orgImage, const DImg &destImage, + int progressBegin, int progressEnd, double radius, double sigma) + : DImgThreadedFilter(parentFilter, orgImage, destImage, progressBegin, progressEnd, + parentFilter->filterName() + ": Sharpen") +{ + m_radius = radius; + m_sigma = sigma; + // We need to provide support for orgImage == destImage. + // The algorithm does not support this out of the box, so use a temporary. + if (orgImage.bits() == destImage.bits()) + m_destImage = DImg(destImage.width(), destImage.height(), destImage.sixteenBit()); + filterImage(); + if (orgImage.bits() == destImage.bits()) + memcpy(destImage.bits(), m_destImage.bits(), m_destImage.numBytes()); +} + +void DImgSharpen::filterImage(void) +{ + sharpenImage(m_radius, m_sigma); +} + +/** Function to apply the sharpen filter on an image*/ + +void DImgSharpen::sharpenImage(double radius, double sigma) +{ + if (m_orgImage.isNull()) + { + DWarning() << k_funcinfo << "No image data available!" + << endl; + return; + } + + if (radius <= 0.0) + { + m_destImage = m_orgImage; + return; + } + + double alpha, normalize=0.0; + long i=0, u, v; + + int kernelWidth = getOptimalKernelWidth(radius, sigma); + + if((int)m_orgImage.width() < kernelWidth) + { + DWarning() << k_funcinfo << "Image is smaller than radius!" + << endl; + return; + } + + double *kernel = new double[kernelWidth*kernelWidth]; + + if(!kernel) + { + DWarning() << k_funcinfo << "Unable to allocate memory!" + << endl; + return; + } + + for(v=(-kernelWidth/2) ; v <= (kernelWidth/2) ; v++) + { + for(u=(-kernelWidth/2) ; u <= (kernelWidth/2) ; u++) + { + alpha = exp(-((double) u*u+v*v)/(2.0*sigma*sigma)); + kernel[i] = alpha/(2.0*M_PI*sigma*sigma); + normalize += kernel[i]; + i++; + } + } + + kernel[i/2] = (-2.0)*normalize; + convolveImage(kernelWidth, kernel); + + delete [] kernel; +} + +bool DImgSharpen::convolveImage(const unsigned int order, const double *kernel) +{ + uint x, y; + int mx, my, sx, sy, mcx, mcy, progress; + long kernelWidth, i; + double red, green, blue, alpha, normalize=0.0; + double *k=0; + DColor color; + + kernelWidth = order; + + if((kernelWidth % 2) == 0) + { + DWarning() << k_funcinfo << "Kernel width must be an odd number!" + << endl; + return(false); + } + + double *normal_kernel = new double[kernelWidth*kernelWidth]; + + if(!normal_kernel) + { + DWarning() << k_funcinfo << "Unable to allocate memory!" + << endl; + return(false); + } + + for(i=0 ; i < (kernelWidth*kernelWidth) ; i++) + normalize += kernel[i]; + + if(fabs(normalize) <= Epsilon) + normalize=1.0; + + normalize = 1.0/normalize; + + for(i=0 ; i < (kernelWidth*kernelWidth) ; i++) + normal_kernel[i] = normalize*kernel[i]; + + double maxClamp = m_destImage.sixteenBit() ? 16777215.0 : 65535.0; + + for(y=0 ; !m_cancel && (y < m_destImage.height()) ; y++) + { + sy = y-(kernelWidth/2); + + for(x=0 ; !m_cancel && (x < m_destImage.width()) ; x++) + { + k = normal_kernel; + red = green = blue = alpha = 0; + sy = y-(kernelWidth/2); + + for(mcy=0 ; !m_cancel && (mcy < kernelWidth) ; mcy++, sy++) + { + my = sy < 0 ? 0 : sy > (int)m_destImage.height()-1 ? m_destImage.height()-1 : sy; + sx = x+(-kernelWidth/2); + + for(mcx=0 ; !m_cancel && (mcx < kernelWidth) ; mcx++, sx++) + { + mx = sx < 0 ? 0 : sx > (int)m_destImage.width()-1 ? m_destImage.width()-1 : sx; + color = m_orgImage.getPixelColor(mx, my); + red += (*k)*(color.red() * 257.0); + green += (*k)*(color.green() * 257.0); + blue += (*k)*(color.blue() * 257.0); + alpha += (*k)*(color.alpha() * 257.0); + k++; + } + } + + red = red < 0.0 ? 0.0 : red > maxClamp ? maxClamp : red+0.5; + green = green < 0.0 ? 0.0 : green > maxClamp ? maxClamp : green+0.5; + blue = blue < 0.0 ? 0.0 : blue > maxClamp ? maxClamp : blue+0.5; + alpha = alpha < 0.0 ? 0.0 : alpha > maxClamp ? maxClamp : alpha+0.5; + + m_destImage.setPixelColor(x, y, DColor((int)(red / 257UL), (int)(green / 257UL), + (int)(blue / 257UL), (int)(alpha / 257UL), + m_destImage.sixteenBit())); + } + + progress = (int)(((double)y * 100.0) / m_destImage.height()); + if ( progress%5 == 0 ) + postProgress( progress ); + } + + delete [] normal_kernel; + return(true); +} + +int DImgSharpen::getOptimalKernelWidth(double radius, double sigma) +{ + double normalize, value; + long kernelWidth; + long u; + + if(radius > 0.0) + return((int)(2.0*ceil(radius)+1.0)); + + for(kernelWidth=5; ;) + { + normalize=0.0; + + for(u=(-kernelWidth/2) ; u <= (kernelWidth/2) ; u++) + normalize += exp(-((double) u*u)/(2.0*sigma*sigma))/(SQ2PI*sigma); + + u = kernelWidth/2; + value = exp(-((double) u*u)/(2.0*sigma*sigma))/(SQ2PI*sigma)/normalize; + + if((long)(65535*value) <= 0) + break; + + kernelWidth+=2; + } + + return((int)kernelWidth-2); +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/filters/dimgsharpen.h b/src/libs/dimg/filters/dimgsharpen.h new file mode 100644 index 00000000..5802769a --- /dev/null +++ b/src/libs/dimg/filters/dimgsharpen.h @@ -0,0 +1,69 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-17-07 + * Description : A Sharpen threaded image filter. + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DIMGSHARPEN_H +#define DIMGSHARPEN_H + +// Digikam includes. + +#include "digikam_export.h" + +// Local includes. + +#include "dimgthreadedfilter.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT DImgSharpen : public DImgThreadedFilter +{ + +public: + + DImgSharpen(DImg *orgImage, TQObject *parent=0, double radius=0.0, double sigma=1.0); + + // Constructor for slave mode: execute immediately in current thread with specified master filter + DImgSharpen(DImgThreadedFilter *parentFilter, const DImg &orgImage, const DImg &destImage, + int progressBegin=0, int progressEnd=100, double radius=0.0, double sigma=1.0); + + ~DImgSharpen(){}; + +private: // DImgSharpen filter data. + + double m_radius; + double m_sigma; + +private: // DImgSharpen filter methods. + + virtual void filterImage(void); + + void sharpenImage(double radius, double sigma); + + bool convolveImage(const unsigned int order, const double *kernel); + + int getOptimalKernelWidth(double radius, double sigma); +}; + +} // NameSpace Digikam + +#endif /* DIMGSHARPEN_H */ diff --git a/src/libs/dimg/filters/dimgthreadedfilter.cpp b/src/libs/dimg/filters/dimgthreadedfilter.cpp new file mode 100644 index 00000000..205405e8 --- /dev/null +++ b/src/libs/dimg/filters/dimgthreadedfilter.cpp @@ -0,0 +1,170 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-05-25 + * Description : threaded image filter class. + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dimgthreadedfilter.h" + +namespace Digikam +{ + +DImgThreadedFilter::DImgThreadedFilter(DImg *orgImage, TQObject *parent, + const TQString& name) + : TQThread() +{ + // remove meta data + m_orgImage = orgImage->copyImageData(); + m_parent = parent; + m_cancel = false; + + // See B.K.O #133026: make a deep copy of Qstring to prevent crash + // on Hyperthreading computer. + m_name = TQDeepCopy(name); + + m_master = 0; + m_slave = 0; + m_progressBegin = 0; + m_progressSpan = 100; +} + +DImgThreadedFilter::DImgThreadedFilter(DImgThreadedFilter *master, const DImg &orgImage, + const DImg &destImage, int progressBegin, int progressEnd, + const TQString& name) +{ + m_orgImage = orgImage; + m_destImage = destImage; + m_parent = 0; + m_cancel = false; + + // See B.K.O #133026: make a deep copy of Qstring to prevent crash + // on Hyperthreading computer. + m_name = TQDeepCopy(name); + + m_master = master; + m_slave = 0; + m_progressBegin = progressBegin; + m_progressSpan = progressEnd - progressBegin; + + m_master->setSlave(this); +} + +DImgThreadedFilter::~DImgThreadedFilter() +{ + stopComputation(); + if (m_master) + m_master->setSlave(0); +} + +void DImgThreadedFilter::initFilter(void) +{ + m_destImage.reset(); + m_destImage = DImg(m_orgImage.width(), m_orgImage.height(), + m_orgImage.sixteenBit(), m_orgImage.hasAlpha()); + + if (m_orgImage.width() && m_orgImage.height()) + { + if (m_parent) + start(); // m_parent is valide, start thread ==> run() + else + startComputation(); // no parent : no using thread. + } + else // No image data + { + if (m_parent) // If parent then send event about a problem. + { + postProgress(0, false, false); + DDebug() << m_name << "::No valid image data !!! ..." << endl; + } + } +} + +void DImgThreadedFilter::stopComputation(void) +{ + m_cancel = true; + if (m_slave) + { + m_slave->m_cancel = true; + // do not wait on slave, it is not running in its own separate thread! + //m_slave->cleanupFilter(); + } + wait(); + cleanupFilter(); +} + +void DImgThreadedFilter::postProgress(int progress, bool starting, bool success) +{ + if (m_master) + { + progress = modulateProgress(progress); + m_master->postProgress(progress, starting, success); + } + else if (m_parent) + { + EventData *eventData = new EventData(); + eventData->progress = progress; + eventData->starting = starting; + eventData->success = success; + TQApplication::postEvent(m_parent, new TQCustomEvent(TQEvent::User, eventData)); + } +} + +void DImgThreadedFilter::startComputation() +{ + // See B.K.O #133026: do not use kdDebug() statements in threaded implementation + // to prevent crash under Hyperthreaded CPU. + + if (m_parent) + postProgress(0, true, false); + + filterImage(); + + if (!m_cancel) + { + if (m_parent) + postProgress(0, false, true); + } + else + { + if (m_parent) + postProgress(0, false, false); + } +} + +void DImgThreadedFilter::setSlave(DImgThreadedFilter *slave) +{ + m_slave = slave; +} + +int DImgThreadedFilter::modulateProgress(int progress) +{ + return m_progressBegin + (int)((double)progress * (double)m_progressSpan / 100.0); +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/filters/dimgthreadedfilter.h b/src/libs/dimg/filters/dimgthreadedfilter.h new file mode 100644 index 00000000..1d4a97b8 --- /dev/null +++ b/src/libs/dimg/filters/dimgthreadedfilter.h @@ -0,0 +1,153 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-05-25 + * Description : threaded image filter class. + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DIMGTHREADEDFILTER_H +#define DIMGTHREADEDFILTER_H + +// TQt includes. + +#include +#include + +// KDE includes. + +#include + +// Local includes. + +#include "dimg.h" +#include "digikam_export.h" + +class TQObject; + +namespace Digikam +{ + +class DIGIKAM_EXPORT DImgThreadedFilter : public TQThread +{ + +public: + +/** Class used to post status of computation to parent. */ +class EventData +{ + public: + + EventData() + { + starting = false; + success = false; + } + + bool starting; + bool success; + int progress; +}; + +public: + + DImgThreadedFilter(DImg *orgImage, TQObject *parent=0, + const TQString& name=TQString()); + + ~DImgThreadedFilter(); + + DImg getTargetImage(void) { return m_destImage; }; + + virtual void startComputation(void); + virtual void stopComputation(void); + + const TQString &filterName() { return m_name; }; + +protected: + + /** Start filter operation before threaded method. Must be calls by your constructor. */ + virtual void initFilter(void); + + /** List of threaded operations by filter. */ + virtual void run(){ startComputation(); }; + + /** Main image filter method. */ + virtual void filterImage(void){}; + + /** Clean up filter data if necessary. Call by stopComputation() method. */ + virtual void cleanupFilter(void){}; + + /** Post Event to parent about progress. Warning: you need to delete + 'EventData' instance to 'customEvent' parent implementation. */ + void postProgress(int progress=0, bool starting=true, bool success=false); + +protected: + + /** + Support for chaining two filters as master and thread. + + Constructor for slave mode: + Constructs a new slave filter with the specified master. + The filter will be executed in the current thread. + orgImage and destImage will not be copied. + progressBegin and progressEnd can indicate the progress span + that the slave filter uses in the parent filter's progress. + Any derived filter class that is publicly available to other filters + should implement an additional constructor using this constructor. + */ + DImgThreadedFilter(DImgThreadedFilter *master, const DImg &orgImage, const DImg &destImage, + int progressBegin=0, int progressEnd=100, const TQString& name=TQString()); + + /** Inform the master that there is currently a slave. At destruction of the slave, call with slave=0. */ + void setSlave(DImgThreadedFilter *slave); + + /** This method modulates the progress value from the 0..100 span to the span of this slave. + Called by postProgress if master is not null. */ + virtual int modulateProgress(int progress); + +protected: + + /** Used to stop compution loop. */ + bool m_cancel; + + /** The progress span that a slave filter uses in the parent filter's progress. */ + int m_progressBegin; + int m_progressSpan; + + /** To post event from thread to parent. */ + TQObject *m_parent; + + /** Filter name.*/ + TQString m_name; + + /** Copy of original Image data. */ + DImg m_orgImage; + + /** Output image data. */ + DImg m_destImage; + + /** The current slave. Any filter might want to use another filter while processing. */ + DImgThreadedFilter *m_slave; + + /** The master of this slave filter. Progress info will be routed to this one. */ + DImgThreadedFilter *m_master; +}; + +} // NameSpace Digikam + +#endif /* DIMGTHREADEDFILTER_H */ diff --git a/src/libs/dimg/filters/hslmodifier.cpp b/src/libs/dimg/filters/hslmodifier.cpp new file mode 100644 index 00000000..4f924d76 --- /dev/null +++ b/src/libs/dimg/filters/hslmodifier.cpp @@ -0,0 +1,240 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-03-06 + * Description : Hue/Saturation/Lightness image filter. + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) +#define CLAMP_0_255(x) TQMAX(TQMIN(x, 255), 0) +#define CLAMP_0_65535(x) TQMAX(TQMIN(x, 65535), 0) + +// C++ includes. + +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dcolor.h" +#include "dimg.h" +#include "hslmodifier.h" + +namespace Digikam +{ + +class HSLModifierPriv +{ +public: + + HSLModifierPriv() + { + modified = false; + } + + bool modified; + + int htransfer[256]; + int ltransfer[256]; + int stransfer[256]; + + int htransfer16[65536]; + int ltransfer16[65536]; + int stransfer16[65536]; +}; + +HSLModifier::HSLModifier() +{ + d = new HSLModifierPriv; + reset(); +} + +HSLModifier::~HSLModifier() +{ + delete d; +} + +bool HSLModifier::modified() const +{ + return d->modified; +} + +void HSLModifier::reset() +{ + // initialize to linear mapping + + for (int i=0; i<65536; i++) + { + d->htransfer16[i] = i; + d->ltransfer16[i] = i; + d->stransfer16[i] = i; + } + + for (int i=0; i<256; i++) + { + d->htransfer[i] = i; + d->ltransfer[i] = i; + d->stransfer[i] = i; + } + + d->modified = false; +} + +void HSLModifier::applyHSL(DImg& image) +{ + if (!d->modified || image.isNull()) + return; + + bool sixteenBit = image.sixteenBit(); + uint numberOfPixels = image.numPixels(); + + if (sixteenBit) // 16 bits image. + { + unsigned short* data = (unsigned short*) image.bits(); + + for (uint i=0; ihtransfer16[hue], d->stransfer16[sat], d->ltransfer16[lig], sixteenBit); + + data[2] = color.red(); + data[1] = color.green(); + data[0] = color.blue(); + + data += 4; + } + } + else // 8 bits image. + { + uchar* data = image.bits(); + + for (uint i=0; ihtransfer[hue], d->stransfer[sat], d->ltransfer[lig], sixteenBit); + + data[2] = color.red(); + data[1] = color.green(); + data[0] = color.blue(); + + data += 4; + } + } +} + +void HSLModifier::setHue(double val) +{ + int value; + + for (int i = 0; i < 65536; i++) + { + value = lround(val * 65535.0 / 360.0); + + if ((i + value) < 0) + d->htransfer16[i] = 65535 + (i + value); + else if ((i + value) > 65535) + d->htransfer16[i] = i + value - 65535; + else + d->htransfer16[i] = i + value; + } + + for (int i = 0; i < 256; i++) + { + value = lround(val * 255.0 / 360.0); + + if ((i + value) < 0) + d->htransfer[i] = 255 + (i + value); + else if ((i + value) > 255) + d->htransfer[i] = i + value - 255; + else + d->htransfer[i] = i + value; + } + + d->modified = true; +} + +void HSLModifier::setSaturation(double val) +{ + val = CLAMP(val, -100.0, 100.0); + int value; + + for (int i = 0; i < 65536; i++) + { + value = lround( (i * (100.0 + val)) / 100.0 ); + d->stransfer16[i] = CLAMP_0_65535(value); + } + + for (int i = 0; i < 256; i++) + { + value = lround( (i * (100.0 + val)) / 100.0 ); + d->stransfer[i] = CLAMP_0_255(value); + } + + d->modified = true; +} + +void HSLModifier::setLightness(double val) +{ + // val needs to be in that range so that the result is in the range 0..65535 + val = CLAMP(val, -100.0, 100.0); + + if (val < 0) + { + for (int i = 0; i < 65536; i++) + { + d->ltransfer16[i] = lround( (i * ( val + 100.0 )) / 100.0); + } + + for (int i = 0; i < 256; i++) + { + d->ltransfer[i] = lround( (i * ( val + 100.0 )) / 100.0); + } + } + else + { + for (int i = 0; i < 65536; i++) + { + d->ltransfer16[i] = lround( i * ( 1.0 - val / 100.0 ) + 65535.0 / 100.0 * val ); + } + + for (int i = 0; i < 256; i++) + { + d->ltransfer[i] = lround( i * ( 1.0 - val / 100.0 ) + 255.0 / 100.0 * val ); + } + } + + d->modified = true; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/filters/hslmodifier.h b/src/libs/dimg/filters/hslmodifier.h new file mode 100644 index 00000000..02a1131d --- /dev/null +++ b/src/libs/dimg/filters/hslmodifier.h @@ -0,0 +1,58 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-03-06 + * Description : Hue/Saturation/Lightness image filter. + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * ============================================================ */ + +#ifndef HSLMODIFIER_H +#define HSLMODIFIER_H + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DImg; +class HSLModifierPriv; + +class DIGIKAM_EXPORT HSLModifier +{ +public: + + HSLModifier(); + ~HSLModifier(); + + void reset(); + bool modified() const; + + void setHue(double val); + void setSaturation(double val); + void setLightness(double val); + void applyHSL(DImg& image); + +private: + + HSLModifierPriv* d; +}; + +} // NameSpace Digikam + +#endif /* HSLMODIFIER_H */ diff --git a/src/libs/dimg/filters/icctransform.cpp b/src/libs/dimg/filters/icctransform.cpp new file mode 100644 index 00000000..0a1324db --- /dev/null +++ b/src/libs/dimg/filters/icctransform.cpp @@ -0,0 +1,831 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-11-18 + * Description : a class to apply ICC color correction to image. + * + * Copyright (C) 2005-2006 by F.J. Cruz + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#include + +// TQt includes. + +#include +#include +#include + +// KDE includes. + +#include +#include + +// Lcms includes. + +#include LCMS_HEADER +#if LCMS_VERSION < 114 +#define cmsTakeCopyright(profile) "Unknown" +#endif // LCMS_VERSION < 114 + +// Local includes. + +#include "ddebug.h" +#include "icctransform.h" + +namespace Digikam +{ + +class IccTransformPriv +{ +public: + + IccTransformPriv() + { + has_embedded_profile = false; + do_proof_profile = false; + } + + bool do_proof_profile; + bool has_embedded_profile; + + TQByteArray embedded_profile; + TQByteArray input_profile; + TQByteArray output_profile; + TQByteArray proof_profile; +}; + +IccTransform::IccTransform() +{ + d = new IccTransformPriv; + cmsErrorAction(LCMS_ERROR_SHOW); +} + +IccTransform::~IccTransform() +{ + delete d; +} + +bool IccTransform::hasInputProfile() +{ + return !(d->input_profile.isEmpty()); +} + +bool IccTransform::hasOutputProfile() +{ + return !(d->output_profile.isEmpty()); +} + +TQByteArray IccTransform::embeddedProfile() const +{ + return d->embedded_profile; +} + +TQByteArray IccTransform::inputProfile() const +{ + return d->input_profile; +} + +TQByteArray IccTransform::outputProfile() const +{ + return d->output_profile; +} + +TQByteArray IccTransform::proofProfile() const +{ + return d->proof_profile; +} + +void IccTransform::getTransformType(bool do_proof_profile) +{ + if (do_proof_profile) + { + d->do_proof_profile = true; + } + else + { + d->do_proof_profile = false; + } +} + +void IccTransform::getEmbeddedProfile(const DImg& image) +{ + if (!image.getICCProfil().isNull()) + { + d->embedded_profile = image.getICCProfil(); + d->has_embedded_profile = true; + } +} + +TQString IccTransform::getProfileDescription(const TQString& profile) +{ + cmsHPROFILE _profile = cmsOpenProfileFromFile(TQFile::encodeName(profile), "r"); + TQString _description = cmsTakeProductDesc(_profile); + cmsCloseProfile(_profile); + return _description; +} + +int IccTransform::getRenderingIntent() +{ + TDEConfig* config = kapp->config(); + config->setGroup("Color Management"); + return config->readNumEntry("RenderingIntent", 0); +} + +bool IccTransform::getUseBPC() +{ + TDEConfig* config = kapp->config(); + config->setGroup("Color Management"); + return config->readBoolEntry("BPCAlgorithm", false); +} + +TQByteArray IccTransform::loadICCProfilFile(const TQString& filePath) +{ + TQFile file(filePath); + if ( !file.open(IO_ReadOnly) ) + return TQByteArray(); + + TQByteArray data(file.size()); + TQDataStream stream( &file ); + stream.readRawBytes(data.data(), data.size()); + file.close(); + return data; +} + +void IccTransform::setProfiles(const TQString& input_profile, const TQString& output_profile) +{ + d->input_profile = loadICCProfilFile(input_profile); + d->output_profile = loadICCProfilFile(output_profile); +} + +void IccTransform::setProfiles(const TQString& input_profile, const TQString& output_profile, + const TQString& proof_profile) +{ + d->input_profile = loadICCProfilFile(input_profile); + d->output_profile = loadICCProfilFile(output_profile); + d->proof_profile = loadICCProfilFile(proof_profile); +} + +void IccTransform::setProfiles(const TQString& output_profile) +{ + d->output_profile = loadICCProfilFile(output_profile); +} + +void IccTransform::setProfiles(const TQString& output_profile, const TQString& proof_profile, bool forProof) +{ + if (forProof) + { + d->output_profile = loadICCProfilFile(output_profile); + d->proof_profile = loadICCProfilFile(proof_profile); + } +} + +TQString IccTransform::getEmbeddedProfileDescriptor() +{ + if (d->embedded_profile.isEmpty()) + return TQString(); + + cmsHPROFILE tmpProfile = cmsOpenProfileFromMem(d->embedded_profile.data(), + (DWORD)d->embedded_profile.size()); + TQString embeddedProfileDescriptor = TQString(cmsTakeProductDesc(tmpProfile)); + cmsCloseProfile(tmpProfile); + return embeddedProfileDescriptor; +} + +TQString IccTransform::getInputProfileDescriptor() +{ + if (d->input_profile.isEmpty()) return TQString(); + cmsHPROFILE tmpProfile = cmsOpenProfileFromMem(d->input_profile.data(), (DWORD)d->input_profile.size()); + TQString embeddedProfileDescriptor = TQString(cmsTakeProductDesc(tmpProfile)); + cmsCloseProfile(tmpProfile); + return embeddedProfileDescriptor; +} + +TQString IccTransform::getOutpoutProfileDescriptor() +{ + if (d->output_profile.isEmpty()) return TQString(); + cmsHPROFILE tmpProfile = cmsOpenProfileFromMem(d->output_profile.data(), (DWORD)d->output_profile.size()); + TQString embeddedProfileDescriptor = TQString(cmsTakeProductDesc(tmpProfile)); + cmsCloseProfile(tmpProfile); + return embeddedProfileDescriptor; +} + +TQString IccTransform::getProofProfileDescriptor() +{ + if (d->proof_profile.isEmpty()) return TQString(); + cmsHPROFILE tmpProfile = cmsOpenProfileFromMem(d->proof_profile.data(), (DWORD)d->proof_profile.size()); + TQString embeddedProfileDescriptor = TQString(cmsTakeProductDesc(tmpProfile)); + cmsCloseProfile(tmpProfile); + return embeddedProfileDescriptor; +} + +bool IccTransform::apply(DImg& image) +{ + cmsHPROFILE inprofile=0, outprofile=0, proofprofile=0; + cmsHTRANSFORM transform; + int inputFormat = 0; + int intent = INTENT_PERCEPTUAL; + + switch (getRenderingIntent()) + { + case 0: + intent = INTENT_PERCEPTUAL; + break; + case 1: + intent = INTENT_RELATIVE_COLORIMETRIC; + break; + case 2: + intent = INTENT_SATURATION; + break; + case 3: + intent = INTENT_ABSOLUTE_COLORIMETRIC; + break; + } + + //DDebug() << "Intent is: " << intent << endl; + + if (d->has_embedded_profile) + { + inprofile = cmsOpenProfileFromMem(d->embedded_profile.data(), + (DWORD)d->embedded_profile.size()); + } + else + { + inprofile = cmsOpenProfileFromMem(d->input_profile.data(), + (DWORD)d->input_profile.size()); + } + if (inprofile == NULL) + { + DDebug() << "Error: Input profile is NULL" << endl; + cmsCloseProfile(inprofile); + return false; + } + +// if (d->has_embedded_profile) +// { +// outprofile = cmsOpenProfileFromMem(d->embedded_profile.data(), +// (DWORD)d->embedded_profile.size()); +// } +// else +// { + outprofile = cmsOpenProfileFromMem(d->output_profile.data(), + (DWORD)d->output_profile.size()); +// } + + if (outprofile == NULL) + { + DDebug() << "Error: Output profile is NULL" << endl; + cmsCloseProfile(outprofile); + return false; + } + + if (!d->do_proof_profile) + { + if (image.sixteenBit()) + { + if (image.hasAlpha()) + { + switch (cmsGetColorSpace(inprofile)) + { + case icSigGrayData: + inputFormat = TYPE_GRAYA_16; + break; + case icSigCmykData: + inputFormat = TYPE_CMYK_16; + break; + default: + inputFormat = TYPE_BGRA_16; + } + + transform = cmsCreateTransform( inprofile, + inputFormat, + outprofile, + TYPE_BGRA_16, + intent, + cmsFLAGS_WHITEBLACKCOMPENSATION); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + else + { + switch (cmsGetColorSpace(inprofile)) + { + case icSigGrayData: + inputFormat = TYPE_GRAY_16; + break; + case icSigCmykData: + inputFormat = TYPE_CMYK_16; + break; + default: + inputFormat = TYPE_BGR_16; + } + + transform = cmsCreateTransform( inprofile, + inputFormat, + outprofile, + TYPE_BGR_16, + intent, + cmsFLAGS_WHITEBLACKCOMPENSATION); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + } + else + { + if (image.hasAlpha()) + { + switch (cmsGetColorSpace(inprofile)) + { + case icSigGrayData: + inputFormat = TYPE_GRAYA_8; + break; + case icSigCmykData: + inputFormat = TYPE_CMYK_8; + break; + default: + inputFormat = TYPE_BGRA_8; + } + + transform = cmsCreateTransform( inprofile, + inputFormat, + outprofile, + TYPE_BGRA_8, + intent, + cmsFLAGS_WHITEBLACKCOMPENSATION); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + else + { + switch (cmsGetColorSpace(inprofile)) + { + case icSigGrayData: + inputFormat = TYPE_GRAYA_8; + break; + case icSigCmykData: + inputFormat = TYPE_CMYK_8; + //DDebug() << "input profile: cmyk no alpha" << endl; + break; + default: + inputFormat = TYPE_BGR_8; + //DDebug() << "input profile: default no alpha" << endl; + } + + transform = cmsCreateTransform(inprofile, inputFormat, outprofile, + TYPE_BGR_8, intent, + cmsFLAGS_WHITEBLACKCOMPENSATION); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + } + } + else + { + proofprofile = cmsOpenProfileFromMem(d->proof_profile.data(), + (DWORD)d->proof_profile.size()); + + if (proofprofile == NULL) + { + DDebug() << "Error: Input profile is NULL" << endl; + cmsCloseProfile(inprofile); + cmsCloseProfile(outprofile); + return false; + } + + if (image.sixteenBit()) + { + if (image.hasAlpha()) + { + transform = cmsCreateProofingTransform( inprofile, + TYPE_BGRA_16, + outprofile, + TYPE_BGRA_16, + proofprofile, + INTENT_ABSOLUTE_COLORIMETRIC, + INTENT_ABSOLUTE_COLORIMETRIC, + cmsFLAGS_WHITEBLACKCOMPENSATION); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + else + { + transform = cmsCreateProofingTransform( inprofile, + TYPE_BGR_16, + outprofile, + TYPE_BGR_16, + proofprofile, + INTENT_ABSOLUTE_COLORIMETRIC, + INTENT_ABSOLUTE_COLORIMETRIC, + cmsFLAGS_WHITEBLACKCOMPENSATION); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + } + else + { + if (image.hasAlpha()) + { + transform = cmsCreateProofingTransform( inprofile, + TYPE_BGR_8, + outprofile, + TYPE_BGR_8, + proofprofile, + INTENT_ABSOLUTE_COLORIMETRIC, + INTENT_ABSOLUTE_COLORIMETRIC, + cmsFLAGS_WHITEBLACKCOMPENSATION); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + else + { + transform = cmsCreateProofingTransform( inprofile, + TYPE_BGR_8, + outprofile, + TYPE_BGR_8, + proofprofile, + INTENT_ABSOLUTE_COLORIMETRIC, + INTENT_ABSOLUTE_COLORIMETRIC, + cmsFLAGS_WHITEBLACKCOMPENSATION); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + } + } + + // We need to work using temp pixel buffer to apply ICC transformations. + uchar transdata[image.bytesDepth()]; + + // Always working with uchar* prevent endianess problem. + uchar *data = image.bits(); + + // We scan all image pixels one by one. + for (uint i=0; i < image.width()*image.height()*image.bytesDepth(); i+=image.bytesDepth()) + { + // Apply ICC transformations. + cmsDoTransform( transform, &data[i], &transdata[0], 1); + + // Copy buffer to source to update original image with ICC corrections. + // Alpha channel is restored in all cases. + memcpy (&data[i], &transdata[0], (image.bytesDepth() == 8) ? 6 : 3); + } + + cmsDeleteTransform(transform); + cmsCloseProfile(inprofile); + cmsCloseProfile(outprofile); + + if (d->do_proof_profile) + cmsCloseProfile(proofprofile); + + return true; +} + +bool IccTransform::apply( DImg& image, TQByteArray& profile, int intent, bool useBPC, + bool checkGamut, bool useBuiltin ) +{ + cmsHPROFILE inprofile=0, outprofile=0, proofprofile=0; + cmsHTRANSFORM transform; + int transformFlags = 0, inputFormat = 0; + + switch (intent) + { + case 0: + intent = INTENT_PERCEPTUAL; + break; + case 1: + intent = INTENT_RELATIVE_COLORIMETRIC; + break; + case 2: + intent = INTENT_SATURATION; + break; + case 3: + intent = INTENT_ABSOLUTE_COLORIMETRIC; + break; + } + + //DDebug() << "Intent is: " << intent << endl; + + if (!profile.isNull()) + { + inprofile = cmsOpenProfileFromMem(profile.data(), + (DWORD)profile.size()); + } + else if (useBuiltin) + { + inprofile = cmsCreate_sRGBProfile(); + } + else + { + inprofile = cmsOpenProfileFromMem(d->input_profile.data(), + (DWORD)d->input_profile.size()); + } + + if (inprofile == NULL) + { + DDebug() << "Error: Input profile is NULL" << endl; + return false; + } + + outprofile = cmsOpenProfileFromMem(d->output_profile.data(), + (DWORD)d->output_profile.size()); + + if (outprofile == NULL) + { + DDebug() << "Error: Output profile is NULL" << endl; + cmsCloseProfile(inprofile); + return false; + } + + if (useBPC) + { + transformFlags |= cmsFLAGS_WHITEBLACKCOMPENSATION; + } + + if (!d->do_proof_profile) + { + if (image.sixteenBit()) + { + if (image.hasAlpha()) + { + switch (cmsGetColorSpace(inprofile)) + { + case icSigGrayData: + inputFormat = TYPE_GRAYA_16; + break; + case icSigCmykData: + inputFormat = TYPE_CMYK_16; + break; + default: + inputFormat = TYPE_BGRA_16; + } + + transform = cmsCreateTransform( inprofile, + inputFormat, + outprofile, + TYPE_BGRA_16, + intent, + transformFlags); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + else + { + switch (cmsGetColorSpace(inprofile)) + { + case icSigGrayData: + inputFormat = TYPE_GRAY_16; + break; + case icSigCmykData: + inputFormat = TYPE_CMYK_16; + break; + default: + inputFormat = TYPE_BGR_16; + } + + transform = cmsCreateTransform( inprofile, + inputFormat, + outprofile, + TYPE_BGR_16, + intent, + transformFlags); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + } + else + { + if (image.hasAlpha()) + { + switch (cmsGetColorSpace(inprofile)) + { + case icSigGrayData: + inputFormat = TYPE_GRAYA_8; + break; + case icSigCmykData: + inputFormat = TYPE_CMYK_8; + break; + default: + inputFormat = TYPE_BGRA_8; + } + + transform = cmsCreateTransform( inprofile, + inputFormat, + outprofile, + TYPE_BGRA_8, + intent, + transformFlags); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + else + { + switch (cmsGetColorSpace(inprofile)) + { + case icSigGrayData: + inputFormat = TYPE_GRAY_8; + break; + case icSigCmykData: + inputFormat = TYPE_CMYK_8; + break; + default: + inputFormat = TYPE_BGR_8; + } + + transform = cmsCreateTransform( inprofile, + inputFormat, + outprofile, + TYPE_BGR_8, + intent, + transformFlags); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + } + } + else + { + proofprofile = cmsOpenProfileFromMem(d->proof_profile.data(), + (DWORD)d->proof_profile.size()); + + if (proofprofile == NULL) + { + DDebug() << "Error: Input profile is NULL" << endl; + cmsCloseProfile(inprofile); + cmsCloseProfile(outprofile); + return false; + } + + transformFlags |= cmsFLAGS_SOFTPROOFING; + if (checkGamut) + { + cmsSetAlarmCodes(126, 255, 255); + transformFlags |= cmsFLAGS_GAMUTCHECK; + } + + if (image.sixteenBit()) + { + if (image.hasAlpha()) + { + transform = cmsCreateProofingTransform( inprofile, + TYPE_BGRA_16, + outprofile, + TYPE_BGRA_16, + proofprofile, + intent, + intent, + transformFlags); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + else + { + transform = cmsCreateProofingTransform( inprofile, + TYPE_BGR_16, + outprofile, + TYPE_BGR_16, + proofprofile, + intent, + intent, + transformFlags); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + } + else + { + if (image.hasAlpha()) + { + transform = cmsCreateProofingTransform( inprofile, + TYPE_BGR_8, + outprofile, + TYPE_BGR_8, + proofprofile, + intent, + intent, + transformFlags); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + else + { + transform = cmsCreateProofingTransform( inprofile, + TYPE_BGR_8, + outprofile, + TYPE_BGR_8, + proofprofile, + intent, + intent, + transformFlags); + + if (!transform) + { + DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; + return false; + } + } + } + } + + //DDebug() << "Transform flags are: " << transformFlags << endl; + + // We need to work using temp pixel buffer to apply ICC transformations. + uchar transdata[image.bytesDepth()]; + + // Always working with uchar* prevent endianess problem. + uchar *data = image.bits(); + + // We scan all image pixels one by one. + for (uint i=0; i < image.width()*image.height()*image.bytesDepth(); i+=image.bytesDepth()) + { + // Apply ICC transformations. + cmsDoTransform( transform, &data[i], &transdata[0], 1); + + // Copy buffer to source to update original image with ICC corrections. + // Alpha channel is restored in all cases. + memcpy (&data[i], &transdata[0], (image.bytesDepth() == 8) ? 6 : 3); + } + + cmsDeleteTransform(transform); + cmsCloseProfile(inprofile); + cmsCloseProfile(outprofile); + + if (d->do_proof_profile) + cmsCloseProfile(proofprofile); + + return true; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/filters/icctransform.h b/src/libs/dimg/filters/icctransform.h new file mode 100644 index 00000000..0590c44f --- /dev/null +++ b/src/libs/dimg/filters/icctransform.h @@ -0,0 +1,94 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-11-18 + * Description : a class to apply ICC color correction to image. + * + * Copyright (C) 2005-2006 by F.J. Cruz + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef ICCTRANSFORM_H +#define ICCTRANSFORM_H + +// TQt includes. + +#include + +// Local includes. + +#include "dimg.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class IccTransformPriv; + +class DIGIKAM_EXPORT IccTransform +{ +public: + + IccTransform(); + ~IccTransform(); + + bool apply(DImg& image); + bool apply(DImg& image, TQByteArray& profile, int intent, + bool useBPC=false, bool checkGamut=false, bool useBuiltin=false); + + void getTransformType(bool do_proof_profile); + void getEmbeddedProfile(const DImg& image); + int getRenderingIntent(); + bool getUseBPC(); + + bool hasInputProfile(); + bool hasOutputProfile(); + + TQByteArray embeddedProfile() const; + TQByteArray inputProfile() const; + TQByteArray outputProfile() const; + TQByteArray proofProfile() const; + + /** Input profile from file methods */ + void setProfiles(const TQString& input_profile, const TQString& output_profile); + void setProfiles(const TQString& input_profile, const TQString& output_profile, const TQString& proof_profile); + + /** Embedded input profile methods */ + void setProfiles(const TQString& output_profile); + void setProfiles(const TQString& output_profile, const TQString& proof_profile, bool forProof); + + /** Profile info methods */ + TQString getProfileDescription(const TQString& profile); + + TQString getEmbeddedProfileDescriptor(); + TQString getInputProfileDescriptor(); + TQString getOutpoutProfileDescriptor(); + TQString getProofProfileDescriptor(); + +private: + + TQByteArray loadICCProfilFile(const TQString& filePath); + +private: + + IccTransformPriv* d; + +}; + +} // NameSpace Digikam + +#endif // ICCTRANSFORM_H diff --git a/src/libs/dimg/loaders/Makefile.am b/src/libs/dimg/loaders/Makefile.am new file mode 100644 index 00000000..62e96fe8 --- /dev/null +++ b/src/libs/dimg/loaders/Makefile.am @@ -0,0 +1,21 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libdimgloaders.la + +libdimgloaders_la_SOURCES = dimgloader.cpp pngloader.cpp jpegloader.cpp tiffloader.cpp \ + rawloader.cpp ppmloader.cpp qimageloader.cpp iccjpeg.c \ + jp2kloader.cpp jpegsettings.cpp pngsettings.cpp \ + tiffsettings.cpp jp2ksettings.cpp + +libdimgloaders_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) \ + $(LIBJPEG) $(LIB_TIFF) $(LIB_PNG) $(LIB_JASPER) + +INCLUDES = $(all_includes) -I$(top_srcdir)/src/libs/dimg \ + -I$(top_srcdir)/src/libs/dimg/filters \ + -I$(top_srcdir)/src/libs/curves \ + -I$(top_srcdir)/src/libs/levels \ + -I$(top_srcdir)/src/libs/histogram \ + -I$(top_srcdir)/src/libs/whitebalance \ + -I$(top_srcdir)/src/libs/dmetadata \ + -I$(top_srcdir)/src/digikam \ + $(LIBKEXIV2_CFLAGS) $(LIBKDCRAW_CFLAGS) diff --git a/src/libs/dimg/loaders/README b/src/libs/dimg/loaders/README new file mode 100644 index 00000000..b77e9c4c --- /dev/null +++ b/src/libs/dimg/loaders/README @@ -0,0 +1,42 @@ +--------------------------------------------------------------------------- + +Native DIMG Loaders status (Gilles Caulier - 2006-11-29) + +Format Read Write ICC MetaData Thumb 8bits 16bits depency Remarks + +JPG Done Done Done Done TODO yes N.A libjpeg Metadata are EXIF/IPTC/XMP/ICC profil +PNG Done Done Done Done N.A yes yes libpng Metadata are EXIF/IPTC/XMP/ICC profil +TIF/EP Done Done Done Done TODO yes yes libtiff Metadata are EXIF/IPTC/XMP/ICC profil +RAW Done N.A N.A Done Done yes yes dcraw Metadata are EXIF +PPM Done TODO N.A N.A N.A yes yes none +JPEG2K Done Done Done TODO N.A yes yes libjasper Metadata are EXIF/XMP/ICC profil + +Others file formats are supported only in 8 bits/color/pixel using TQImage/kimgio. +QT3.x + KDE 3.4.x support these formats : + +Format Read Write Remarks + +PSD yes no Photoshop file format +EXR yes no OpenEXR (libopenexr) +XCF yes no Gimp file format +PBM yes yes +PGM yes yes +PPM no yes +TGA yes yes +PCX yes yes +BMP yes yes Win32 bitmap format +RGB yes yes +XBM yes yes +XPM yes yes +EPS yes yes +DDS yes no +ICO yes no Win32 icon format +MNG yes no +GIF yes no + +--------------------------------------------------------------------------- + +TODO : + +Add PCD support using http://linux.bytesex.org/fbida/libpcd.html + diff --git a/src/libs/dimg/loaders/dimgloader.cpp b/src/libs/dimg/loaders/dimgloader.cpp new file mode 100644 index 00000000..615b11e1 --- /dev/null +++ b/src/libs/dimg/loaders/dimgloader.cpp @@ -0,0 +1,200 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-14 + * Description : DImg image loader interface + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2005-2009 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// KDE includes. + +#include + +// Local includes. + +#include "ddebug.h" +#include "dimgprivate.h" +#include "dmetadata.h" +#include "dimgloaderobserver.h" +#include "dimgloader.h" + +namespace Digikam +{ + +DImgLoader::DImgLoader(DImg* image) + : m_image(image) +{ +} + +int DImgLoader::granularity(DImgLoaderObserver *observer, int total, float progressSlice) +{ + // Splits expect total value into the chunks where checks shall occur + // and combines this with a possible correction factor from observer. + // Progress slice is the part of 100% concerned with the current granularity + // (E.g. in a loop only the values from 10% to 90% are used, then progressSlice is 0.8) + // Current default is 1/20, that is progress info every 5% + int granularity=0; + + if (observer) + granularity = (int)(( total / (20 * progressSlice)) / observer->granularity()); + + return granularity ? granularity : 1; +} + +unsigned char*& DImgLoader::imageData() +{ + return m_image->m_priv->data; +} + +unsigned int& DImgLoader::imageWidth() +{ + return m_image->m_priv->width; +} + +unsigned int& DImgLoader::imageHeight() +{ + return m_image->m_priv->height; +} + +bool DImgLoader::imageHasAlpha() +{ + return m_image->hasAlpha(); +} + +bool DImgLoader::imageSixteenBit() +{ + return m_image->sixteenBit(); +} + +int DImgLoader::imageBitsDepth() +{ + return m_image->bitsDepth(); +} + +int DImgLoader::imageBytesDepth() +{ + return m_image->bytesDepth(); +} + +TQMap& DImgLoader::imageMetaData() +{ + return m_image->m_priv->metaData; +} + +TQVariant DImgLoader::imageGetAttribute(const TQString& key) +{ + return m_image->attribute(key); +} + +TQString DImgLoader::imageGetEmbbededText(const TQString& key) +{ + return m_image->embeddedText(key); +} + +void DImgLoader::imageSetAttribute(const TQString& key, const TQVariant& value) +{ + m_image->setAttribute(key, value); +} + +TQMap& DImgLoader::imageEmbeddedText() +{ + return m_image->m_priv->embeddedText; +} + +void DImgLoader::imageSetEmbbededText(const TQString& key, const TQString& text) +{ + m_image->setEmbeddedText(key, text); +} + +bool DImgLoader::readMetadata(const TQString& filePath, DImg::FORMAT /*ff*/) +{ + TQMap& imageMetadata = imageMetaData(); + imageMetadata.clear(); + + DMetadata metaDataFromFile(filePath); + if (!metaDataFromFile.load(filePath)) + return false; + + // Do not insert null data into metaData map: + // Even if byte array is null, if there is a key in the map, it will + // be interpreted as "There was data, so write it again to the file". + if (!metaDataFromFile.getComments().isNull()) + imageMetadata.insert(DImg::COM, metaDataFromFile.getComments()); + if (!metaDataFromFile.getExif().isNull()) + imageMetadata.insert(DImg::EXIF, metaDataFromFile.getExif()); + if (!metaDataFromFile.getIptc().isNull()) + imageMetadata.insert(DImg::IPTC, metaDataFromFile.getIptc()); + + return true; +} + +bool DImgLoader::saveMetadata(const TQString& filePath) +{ + DMetadata metaDataToFile(filePath); + metaDataToFile.setComments(m_image->getComments()); + metaDataToFile.setExif(m_image->getExif()); + metaDataToFile.setIptc(m_image->getIptc()); + return metaDataToFile.applyChanges(); +} + +bool DImgLoader::checkExifWorkingColorSpace() +{ + DMetadata metaData; + metaData.setExif(m_image->getExif()); + + // Check if Exif data contains an ICC color profile. + TQByteArray profile = metaData.getExifTagData("Exif.Image.InterColorProfile"); + if (!profile.isNull()) + { + DDebug() << "Found an ICC profile in Exif metadata" << endl; + m_image->setICCProfil(profile); + return true; + } + + // Else check the Exif color-space tag and use a default profiles available in digiKam. + TDEGlobal::dirs()->addResourceType("profiles", TDEGlobal::dirs()->kde_default("data") + "digikam/profiles"); + + switch(metaData.getImageColorWorkSpace()) + { + case DMetadata::WORKSPACE_SRGB: + { + TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "srgb-d65.icm"); + m_image->getICCProfilFromFile(directory + "srgb-d65.icm"); + DDebug() << "Exif color-space tag is sRGB. Using default sRGB ICC profile." << endl; + return true; + break; + } + + case DMetadata::WORKSPACE_ADOBERGB: + { + TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "adobergb.icm"); + m_image->getICCProfilFromFile(directory + "adobergb.icm"); + DDebug() << "Exif color-space tag is AdobeRGB. Using default AdobeRGB ICC profile." << endl; + return true; + break; + } + + default: + break; + } + + return false; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/dimgloader.h b/src/libs/dimg/loaders/dimgloader.h new file mode 100644 index 00000000..39025888 --- /dev/null +++ b/src/libs/dimg/loaders/dimgloader.h @@ -0,0 +1,97 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-14 + * Description : DImg image loader interface + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2005-2009 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DIMGLOADER_H +#define DIMGLOADER_H + +// TQt includes. + +#include +#include +#include +#include + +// Local includes. + +#include "dimg.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class DImgLoaderObserver; + +class DIGIKAM_EXPORT DImgLoader +{ +public: + + virtual ~DImgLoader() {}; + + virtual bool load(const TQString& filePath, DImgLoaderObserver *observer) = 0; + virtual bool save(const TQString& filePath, DImgLoaderObserver *observer) = 0; + + virtual bool hasAlpha() const = 0; + virtual bool sixteenBit() const = 0; + virtual bool isReadOnly() const = 0; + +protected: + + DImgLoader(DImg* image); + + unsigned char*& imageData(); + unsigned int& imageWidth(); + unsigned int& imageHeight(); + + bool imageHasAlpha(); + bool imageSixteenBit(); + + int imageBitsDepth(); + int imageBytesDepth(); + + TQMap& imageMetaData(); + TQVariant imageGetAttribute(const TQString& key); + void imageSetAttribute(const TQString& key, const TQVariant& value); + + TQMap& imageEmbeddedText(); + TQString imageGetEmbbededText(const TQString& key); + void imageSetEmbbededText(const TQString& key, const TQString& text); + + virtual bool readMetadata(const TQString& filePath, DImg::FORMAT ff); + virtual bool saveMetadata(const TQString& filePath); + virtual int granularity(DImgLoaderObserver *observer, int total, float progressSlice = 1.0); + + bool checkExifWorkingColorSpace(); + +protected: + + DImg *m_image; + +private: + + DImgLoader(); +}; + +} // NameSpace Digikam + +#endif /* DIMGLOADER_H */ diff --git a/src/libs/dimg/loaders/dimgloaderobserver.h b/src/libs/dimg/loaders/dimgloaderobserver.h new file mode 100644 index 00000000..ea83bede --- /dev/null +++ b/src/libs/dimg/loaders/dimgloaderobserver.h @@ -0,0 +1,67 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-01-03 + * Description : DImgLoader observer interface + * + * Copyright (C) 2006-2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DIMGLOADEROBSERVER_H +#define DIMGLOADEROBSERVER_H + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DImg; + +class DIGIKAM_EXPORT DImgLoaderObserver +{ + +public: + // posts progress information about image IO + virtual void progressInfo(const DImg *, float /*progress*/) + {}; + + // queries whether the image IO operation shall be continued + virtual bool continueQuery(const DImg *) + { return true; }; + + // Return a relative value which determines the granularity, the frequency + // with which the DImgLoaderObserver is checked and progress is posted. + // Standard is 1.0. Values < 1 mean less granularity (fewer checks), + // values > 1 mean higher granularity (more checks). + virtual float granularity() + { return 1.0; }; + + // This is a hack needed to prevent hanging when a TDEProcess-based loader (raw loader) + // is waiting for the process to finish, but the main thread is waiting + // for the thread to finish and no TDEProcess events are delivered. + // Remove when porting to TQt4. + virtual bool isShuttingDown() + { return false; } + + virtual ~DImgLoaderObserver(){}; +}; + +} // namespace Digikam + +#endif // DIMGLOADEROBSERVER_H diff --git a/src/libs/dimg/loaders/iccjpeg.c b/src/libs/dimg/loaders/iccjpeg.c new file mode 100644 index 00000000..fefa9509 --- /dev/null +++ b/src/libs/dimg/loaders/iccjpeg.c @@ -0,0 +1,270 @@ +/* + * Little cms + * Copyright (C) 1998-2004 Marti Maria + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * iccprofile.c + * + * This file provides code to read and write International Color Consortium + * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has + * defined a standard format for including such data in JPEG "APP2" markers. + * The code given here does not know anything about the internal structure + * of the ICC profile data; it just knows how to put the profile data into + * a JPEG file being written, or get it back out when reading. + * + * This code depends on new features added to the IJG JPEG library as of + * IJG release 6b; it will not compile or work with older IJG versions. + * + * NOTE: this code would need surgery to work on 16-bit-int machines + * with ICC profiles exceeding 64K bytes in size. If you need to do that, + * change all the "unsigned int" variables to "INT32". You'll also need + * to find a malloc() replacement that can allocate more than 64K. + */ + +#include "iccjpeg.h" +#include /* define malloc() */ + + +/* + * Since an ICC profile can be larger than the maximum size of a JPEG marker + * (64K), we need provisions to split it into multiple markers. The format + * defined by the ICC specifies one or more APP2 markers containing the + * following data: + * Identifying string ASCII "ICC_PROFILE\0" (12 bytes) + * Marker sequence number 1 for first APP2, 2 for next, etc (1 byte) + * Number of markers Total number of APP2's used (1 byte) + * Profile data (remainder of APP2 data) + * Decoders should use the marker sequence numbers to reassemble the profile, + * rather than assuming that the APP2 markers appear in the correct sequence. + */ + +#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ +#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ +#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ +#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) + + +/* + * This routine writes the given ICC profile data into a JPEG file. + * It *must* be called AFTER calling jpeg_start_compress() and BEFORE + * the first call to jpeg_write_scanlines(). + * (This ordering ensures that the APP2 marker(s) will appear after the + * SOI and JFIF or Adobe markers, but before all else.) + */ + +void +write_icc_profile (j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len) +{ + unsigned int num_markers; /* total number of markers we'll write */ + int cur_marker = 1; /* per spec, counting starts at 1 */ + unsigned int length; /* number of bytes to write in this marker */ + + /* Calculate the number of markers we'll need, rounding up of course */ + num_markers = icc_data_len / MAX_DATA_BYTES_IN_MARKER; + if (num_markers * MAX_DATA_BYTES_IN_MARKER != icc_data_len) + num_markers++; + + while (icc_data_len > 0) { + /* length of profile to put in this marker */ + length = icc_data_len; + if (length > MAX_DATA_BYTES_IN_MARKER) + length = MAX_DATA_BYTES_IN_MARKER; + icc_data_len -= length; + + /* Write the JPEG marker header (APP2 code and marker length) */ + jpeg_write_m_header(cinfo, ICC_MARKER, + (unsigned int) (length + ICC_OVERHEAD_LEN)); + + /* Write the marker identifying string "ICC_PROFILE" (null-terminated). + * We code it in this less-than-transparent way so that the code works + * even if the local character set is not ASCII. + */ + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x5F); + jpeg_write_m_byte(cinfo, 0x50); + jpeg_write_m_byte(cinfo, 0x52); + jpeg_write_m_byte(cinfo, 0x4F); + jpeg_write_m_byte(cinfo, 0x46); + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x4C); + jpeg_write_m_byte(cinfo, 0x45); + jpeg_write_m_byte(cinfo, 0x0); + + /* Add the sequencing info */ + jpeg_write_m_byte(cinfo, cur_marker); + jpeg_write_m_byte(cinfo, (int) num_markers); + + /* Add the profile data */ + while (length--) { + jpeg_write_m_byte(cinfo, *icc_data_ptr); + icc_data_ptr++; + } + cur_marker++; + } +} + + +/* + * Prepare for reading an ICC profile + */ + +void +setup_read_icc_profile (j_decompress_ptr cinfo) +{ + /* Tell the library to keep any APP2 data it may find */ + jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF); +} + + +/* + * Handy subroutine to test whether a saved marker is an ICC profile marker. + */ + +static boolean +marker_is_icc (jpeg_saved_marker_ptr marker) +{ + return + marker->marker == ICC_MARKER && + marker->data_length >= ICC_OVERHEAD_LEN && + /* verify the identifying string */ + GETJOCTET(marker->data[0]) == 0x49 && + GETJOCTET(marker->data[1]) == 0x43 && + GETJOCTET(marker->data[2]) == 0x43 && + GETJOCTET(marker->data[3]) == 0x5F && + GETJOCTET(marker->data[4]) == 0x50 && + GETJOCTET(marker->data[5]) == 0x52 && + GETJOCTET(marker->data[6]) == 0x4F && + GETJOCTET(marker->data[7]) == 0x46 && + GETJOCTET(marker->data[8]) == 0x49 && + GETJOCTET(marker->data[9]) == 0x4C && + GETJOCTET(marker->data[10]) == 0x45 && + GETJOCTET(marker->data[11]) == 0x0; +} + + +/* + * See if there was an ICC profile in the JPEG file being read; + * if so, reassemble and return the profile data. + * + * TRUE is returned if an ICC profile was found, FALSE if not. + * If TRUE is returned, *icc_data_ptr is set to point to the + * returned data, and *icc_data_len is set to its length. + * + * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc() + * and must be freed by the caller with free() when the caller no longer + * needs it. (Alternatively, we could write this routine to use the + * IJG library's memory allocator, so that the data would be freed implicitly + * at jpeg_finish_decompress() time. But it seems likely that many apps + * will prefer to have the data stick around after decompression finishes.) + * + * NOTE: if the file contains invalid ICC APP2 markers, we just silently + * return FALSE. You might want to issue an error message instead. + */ + +boolean +read_icc_profile (j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len) +{ + jpeg_saved_marker_ptr marker; + int num_markers = 0; + int seq_no; + JOCTET *icc_data; + unsigned int total_length; +#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */ + char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */ + unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */ + unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */ + + *icc_data_ptr = NULL; /* avoid confusion if FALSE return */ + *icc_data_len = 0; + + /* This first pass over the saved markers discovers whether there are + * any ICC markers and verifies the consistency of the marker numbering. + */ + + for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) + marker_present[seq_no] = 0; + + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + if (num_markers == 0) + num_markers = GETJOCTET(marker->data[13]); + else if (num_markers != GETJOCTET(marker->data[13])) + return FALSE; /* inconsistent num_markers fields */ + seq_no = GETJOCTET(marker->data[12]); + if (seq_no <= 0 || seq_no > num_markers) + return FALSE; /* bogus sequence number */ + if (marker_present[seq_no]) + return FALSE; /* duplicate sequence numbers */ + marker_present[seq_no] = 1; + data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN; + } + } + + if (num_markers == 0) + return FALSE; + + /* Check for missing markers, count total space needed, + * compute offset of each marker's part of the data. + */ + + total_length = 0; + for (seq_no = 1; seq_no <= num_markers; seq_no++) { + if (marker_present[seq_no] == 0) + return FALSE; /* missing sequence number */ + data_offset[seq_no] = total_length; + total_length += data_length[seq_no]; + } + + if (total_length <= 0) + return FALSE; /* found only empty markers? */ + + /* Allocate space for assembled data */ + icc_data = (JOCTET *) malloc(total_length * sizeof(JOCTET)); + if (icc_data == NULL) + return FALSE; /* oops, out of memory */ + + /* and fill it in */ + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker_is_icc(marker)) { + JOCTET FAR *src_ptr; + JOCTET *dst_ptr; + unsigned int length; + seq_no = GETJOCTET(marker->data[12]); + dst_ptr = icc_data + data_offset[seq_no]; + src_ptr = marker->data + ICC_OVERHEAD_LEN; + length = data_length[seq_no]; + while (length--) { + *dst_ptr++ = *src_ptr++; + } + } + } + + *icc_data_ptr = icc_data; + *icc_data_len = total_length; + + return TRUE; +} diff --git a/src/libs/dimg/loaders/iccjpeg.h b/src/libs/dimg/loaders/iccjpeg.h new file mode 100644 index 00000000..2aab4196 --- /dev/null +++ b/src/libs/dimg/loaders/iccjpeg.h @@ -0,0 +1,101 @@ +/* + * Little cms + * Copyright (C) 1998-2004 Marti Maria + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * + * iccprofile.h + * + * This file provides code to read and write International Color Consortium + * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has + * defined a standard format for including such data in JPEG "APP2" markers. + * The code given here does not know anything about the internal structure + * of the ICC profile data; it just knows how to put the profile data into + * a JPEG file being written, or get it back out when reading. + * + * This code depends on new features added to the IJG JPEG library as of + * IJG release 6b; it will not compile or work with older IJG versions. + * + * NOTE: this code would need surgery to work on 16-bit-int machines + * with ICC profiles exceeding 64K bytes in size. See iccprofile.c + * for details. + */ + +#ifndef ICCJPEG_H +#define ICCJPEG_H + +#include /* needed to define "FILE", "NULL" */ +#include + + +/** + * This routine writes the given ICC profile data into a JPEG file. + * It *must* be called AFTER calling jpeg_start_compress() and BEFORE + * the first call to jpeg_write_scanlines(). + * (This ordering ensures that the APP2 marker(s) will appear after the + * SOI and JFIF or Adobe markers, but before all else.) + */ + +extern void write_icc_profile JPP((j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len)); + + +/** + * Reading a JPEG file that may contain an ICC profile requires two steps: + * + * 1. After jpeg_create_decompress() but before jpeg_read_header(), + * call setup_read_icc_profile(). This routine tells the IJG library + * to save in memory any APP2 markers it may find in the file. + * + * 2. After jpeg_read_header(), call read_icc_profile() to find out + * whether there was a profile and obtain it if so. + */ + + +/** + * Prepare for reading an ICC profile + */ + +extern void setup_read_icc_profile JPP((j_decompress_ptr cinfo)); + + +/** + * See if there was an ICC profile in the JPEG file being read; + * if so, reassemble and return the profile data. + * + * TRUE is returned if an ICC profile was found, FALSE if not. + * If TRUE is returned, *icc_data_ptr is set to point to the + * returned data, and *icc_data_len is set to its length. + * + * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc() + * and must be freed by the caller with free() when the caller no longer + * needs it. (Alternatively, we could write this routine to use the + * IJG library's memory allocator, so that the data would be freed implicitly + * at jpeg_finish_decompress() time. But it seems likely that many apps + * will prefer to have the data stick around after decompression finishes.) + */ + +extern boolean read_icc_profile JPP((j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len)); + +#endif /* ICCJPEG_H */ diff --git a/src/libs/dimg/loaders/jp2kloader.cpp b/src/libs/dimg/loaders/jp2kloader.cpp new file mode 100644 index 00000000..66351c25 --- /dev/null +++ b/src/libs/dimg/loaders/jp2kloader.cpp @@ -0,0 +1,715 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-06-14 + * Description : A JPEG2000 IO file for DImg framework + * + * Copyright (C) 2006-2007 by Gilles Caulier + * + * This implementation use Jasper API + * library : http://www.ece.uvic.ca/~mdadams/jasper + * Other JPEG2000 encoder-decoder : http://www.openjpeg.org + * + * Others Linux JPEG2000 Loader implementation using Jasper: + * http://cvs.graphicsmagick.org/cgi-bin/cvsweb.cgi/GraphicsMagick/coders/jp2.c + * https://subversion.imagemagick.org/subversion/ImageMagick/trunk/coders/jp2.c + * http://svn.ghostscript.com:8080/jasper/trunk/src/appl/jasper.c + * http://websvn.kde.org/trunk/KDE/tdelibs/kimgio/jp2.cpp + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// This line must be commented to prevent any latency time +// when we use threaded image loader interface for each image +// files io. Uncomment this line only for debugging. +//#define ENABLE_DEBUG_MESSAGES + +// C ANSI includes. + +extern "C" +{ +#if !defined(__STDC_LIMIT_MACROS) +#define __STDC_LIMIT_MACROS +#endif +#include +} + +// TQt includes. + +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "jp2kloader.h" + +namespace Digikam +{ + +JP2KLoader::JP2KLoader(DImg* image) + : DImgLoader(image) +{ + m_hasAlpha = false; + m_sixteenBit = false; +} + +bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + readMetadata(filePath, DImg::JPEG); + + FILE *file = fopen(TQFile::encodeName(filePath), "rb"); + if (!file) + return false; + + unsigned char header[9]; + + if (fread(&header, 9, 1, file) != 1) + { + fclose(file); + return false; + } + + unsigned char jp2ID[5] = { 0x6A, 0x50, 0x20, 0x20, 0x0D, }; + unsigned char jpcID[2] = { 0xFF, 0x4F }; + + if (memcmp(&header[4], &jp2ID, 5) != 0 && + memcmp(&header, &jpcID, 2) != 0) + { + // not a jpeg2000 file + fclose(file); + return false; + } + + fclose(file); + + // ------------------------------------------------------------------- + // Initialize JPEG 2000 API. + + long i, x, y; + int components[4]; + unsigned int maximum_component_depth, scale[4], x_step[4], y_step[4]; + unsigned long number_components; + + jas_image_t *jp2_image = 0; + jas_stream_t *jp2_stream = 0; + jas_matrix_t *pixels[4]; + + int init = jas_init(); + if (init != 0) + { + DDebug() << "Unable to init JPEG2000 decoder" << endl; + return false; + } + + jp2_stream = jas_stream_fopen(TQFile::encodeName(filePath), "rb"); + if (jp2_stream == 0) + { + DDebug() << "Unable to open JPEG2000 stream" << endl; + return false; + } + + jp2_image = jas_image_decode(jp2_stream, -1, 0); + if (jp2_image == 0) + { + jas_stream_close(jp2_stream); + DDebug() << "Unable to decode JPEG2000 image" << endl; + return false; + } + + jas_stream_close(jp2_stream); + + // some pseudo-progress + if (observer) + observer->progressInfo(m_image, 0.1); + + // ------------------------------------------------------------------- + // Check color space. + + switch (jas_clrspc_fam(jas_image_clrspc(jp2_image))) + { + case JAS_CLRSPC_FAM_RGB: + { + components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_R); + components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_G); + components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_B); + if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0)) + { + jas_image_destroy(jp2_image); + DDebug() << "Error parsing JPEG2000 image : Missing Image Channel" << endl; + return false; + } + + number_components = 3; + components[3] = jas_image_getcmptbytype(jp2_image, 3); + if (components[3] > 0) + { + m_hasAlpha = true; + number_components++; + } + break; + } + case JAS_CLRSPC_FAM_GRAY: + { + components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_GRAY_Y); + if (components[0] < 0) + { + jas_image_destroy(jp2_image); + DDebug() << "Error parsing JP2000 image : Missing Image Channel" << endl; + return false; + } + number_components=1; + break; + } + case JAS_CLRSPC_FAM_YCBCR: + { + components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_Y); + components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CB); + components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CR); + if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0)) + { + jas_image_destroy(jp2_image); + DDebug() << "Error parsing JP2000 image : Missing Image Channel" << endl; + return false; + } + number_components = 3; + components[3] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_UNKNOWN); + if (components[3] > 0) + { + m_hasAlpha = true; + number_components++; + } + // FIXME : image->colorspace=YCbCrColorspace; + break; + } + default: + { + jas_image_destroy(jp2_image); + DDebug() << "Error parsing JP2000 image : Colorspace Model Is Not Supported" << endl; + return false; + } + } + + // ------------------------------------------------------------------- + // Check image geometry. + + imageWidth() = jas_image_width(jp2_image); + imageHeight() = jas_image_height(jp2_image); + + for (i = 0; i < (long)number_components; i++) + { + if ((((jas_image_cmptwidth(jp2_image, components[i])* + jas_image_cmpthstep(jp2_image, components[i])) != (long)imageWidth())) || + (((jas_image_cmptheight(jp2_image, components[i])* + jas_image_cmptvstep(jp2_image, components[i])) != (long)imageHeight())) || + (jas_image_cmpttlx(jp2_image, components[i]) != 0) || + (jas_image_cmpttly(jp2_image, components[i]) != 0) || + (jas_image_cmptsgnd(jp2_image, components[i]) != false)) + { + jas_image_destroy(jp2_image); + DDebug() << "Error parsing JPEG2000 image : Irregular Channel Geometry Not Supported" << endl; + return false; + } + x_step[i] = jas_image_cmpthstep(jp2_image, components[i]); + y_step[i] = jas_image_cmptvstep(jp2_image, components[i]); + } + + // ------------------------------------------------------------------- + // Convert image data. + + m_hasAlpha = number_components > 3; + maximum_component_depth = 0; + + for (i = 0; i < (long)number_components; i++) + { + maximum_component_depth = TQMAX(jas_image_cmptprec(jp2_image,components[i]), + (long)maximum_component_depth); + pixels[i] = jas_matrix_create(1, ((unsigned int)imageWidth())/x_step[i]); + if (!pixels[i]) + { + jas_image_destroy(jp2_image); + DDebug() << "Error decoding JPEG2000 image data : Memory Allocation Failed" << endl; + return false; + } + } + + if (maximum_component_depth > 8) + m_sixteenBit = true; + + for (i = 0 ; i < (long)number_components ; i++) + { + scale[i] = 1; + int prec = jas_image_cmptprec(jp2_image, components[i]); + if (m_sixteenBit && prec < 16) + scale[i] = (1 << (16 - jas_image_cmptprec(jp2_image, components[i]))); + } + + uchar* data = 0; + if (m_sixteenBit) // 16 bits image. + data = new uchar[imageWidth()*imageHeight()*8]; + else + data = new uchar[imageWidth()*imageHeight()*4]; + + if (!data) + { + DDebug() << "Error decoding JPEG2000 image data : Memory Allocation Failed" << endl; + jas_image_destroy(jp2_image); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + return false; + } + + uint checkPoint = 0; + uchar *dst = data; + unsigned short *dst16 = (unsigned short *)data; + + for (y = 0 ; y < (long)imageHeight() ; y++) + { + for (i = 0 ; i < (long)number_components; i++) + { + int ret = jas_image_readcmpt(jp2_image, (short)components[i], 0, + ((unsigned int) y) / y_step[i], + ((unsigned int) imageWidth()) / x_step[i], + 1, pixels[i]); + if (ret != 0) + { + DDebug() << "Error decoding JPEG2000 image data" << endl; + delete [] data; + jas_image_destroy(jp2_image); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + return false; + } + } + + switch (number_components) + { + case 1: // Grayscale. + { + for (x = 0 ; x < (long)imageWidth() ; x++) + { + dst[0] = (uchar)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0])); + dst[1] = dst[0]; + dst[2] = dst[0]; + dst[3] = 0xFF; + + dst += 4; + } + break; + } + case 3: // RGB. + { + if (!m_sixteenBit) // 8 bits image. + { + for (x = 0 ; x < (long)imageWidth() ; x++) + { + // Blue + dst[0] = (uchar)(scale[2]*jas_matrix_getv(pixels[2], x/x_step[2])); + // Green + dst[1] = (uchar)(scale[1]*jas_matrix_getv(pixels[1], x/x_step[1])); + // Red + dst[2] = (uchar)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0])); + // Alpha + dst[3] = 0xFF; + + dst += 4; + } + } + else // 16 bits image. + { + for (x = 0 ; x < (long)imageWidth() ; x++) + { + // Blue + dst16[0] = (unsigned short)(scale[2]*jas_matrix_getv(pixels[2], x/x_step[2])); + // Green + dst16[1] = (unsigned short)(scale[1]*jas_matrix_getv(pixels[1], x/x_step[1])); + // Red + dst16[2] = (unsigned short)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0])); + // Alpha + dst16[3] = 0xFFFF; + + dst16 += 4; + } + } + + break; + } + case 4: // RGBA. + { + if (!m_sixteenBit) // 8 bits image. + { + for (x = 0 ; x < (long)imageWidth() ; x++) + { + // Blue + dst[0] = (uchar)(scale[2] * jas_matrix_getv(pixels[2], x/x_step[2])); + // Green + dst[1] = (uchar)(scale[1] * jas_matrix_getv(pixels[1], x/x_step[1])); + // Red + dst[2] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x/x_step[0])); + // Alpha + dst[3] = (uchar)(scale[3] * jas_matrix_getv(pixels[3], x/x_step[3])); + + dst += 4; + } + } + else // 16 bits image. + { + for (x = 0 ; x < (long)imageWidth() ; x++) + { + // Blue + dst16[0] = (unsigned short)(scale[2]*jas_matrix_getv(pixels[2], x/x_step[2])); + // Green + dst16[1] = (unsigned short)(scale[1]*jas_matrix_getv(pixels[1], x/x_step[1])); + // Red + dst16[2] = (unsigned short)(scale[0]*jas_matrix_getv(pixels[0], x/x_step[0])); + // Alpha + dst16[3] = (unsigned short)(scale[3]*jas_matrix_getv(pixels[3], x/x_step[3])); + + dst16 += 4; + } + } + + break; + } + } + + // use 0-10% and 90-100% for pseudo-progress + if (observer && y >= (long)checkPoint) + { + checkPoint += granularity(observer, y, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + jas_image_destroy(jp2_image); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)y)/((float)imageHeight()) ))); + } + } + + // ------------------------------------------------------------------- + // Get ICC color profile. + + jas_iccprof_t *icc_profile = 0; + jas_stream_t *icc_stream = 0; + jas_cmprof_t *cm_profile = 0; + + cm_profile = jas_image_cmprof(jp2_image); + if (cm_profile != 0) + icc_profile = jas_iccprof_createfromcmprof(cm_profile); + + if (icc_profile != 0) + { + icc_stream = jas_stream_memopen(NULL, 0); + + if (icc_stream != 0) + { + if (jas_iccprof_save(icc_profile, icc_stream) == 0) + { + if (jas_stream_flush(icc_stream) == 0) + { + TQMap& metaData = imageMetaData(); + jas_stream_memobj_t *blob = (jas_stream_memobj_t *) icc_stream->obj_; + TQByteArray profile_rawdata(blob->len_); + memcpy(profile_rawdata.data(), blob->buf_, blob->len_); + metaData.insert(DImg::ICC, profile_rawdata); + jas_stream_close(icc_stream); + } + } + } + } + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageSetAttribute("format", "JP2K"); + imageData() = data; + + jas_image_destroy(jp2_image); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + + return true; +} + +bool JP2KLoader::save(const TQString& filePath, DImgLoaderObserver *observer) +{ + FILE *file = fopen(TQFile::encodeName(filePath), "wb"); + if (!file) + return false; + + fclose(file); + + // ------------------------------------------------------------------- + // Initialize JPEG 2000 API. + + long i, x, y; + unsigned long number_components; + + jas_image_t *jp2_image = 0; + jas_stream_t *jp2_stream = 0; + jas_matrix_t *pixels[4]; + jas_image_cmptparm_t component_info[4]; + + int init = jas_init(); + if (init != 0) + { + DDebug() << "Unable to init JPEG2000 decoder" << endl; + return false; + } + + jp2_stream = jas_stream_fopen(TQFile::encodeName(filePath), "wb"); + if (jp2_stream == 0) + { + DDebug() << "Unable to open JPEG2000 stream" << endl; + return false; + } + + number_components = imageHasAlpha() ? 4 : 3; + + for (i = 0 ; i < (long)number_components ; i++) + { + component_info[i].tlx = 0; + component_info[i].tly = 0; + component_info[i].hstep = 1; + component_info[i].vstep = 1; + component_info[i].width = imageWidth(); + component_info[i].height = imageHeight(); + component_info[i].prec = imageBitsDepth(); + component_info[i].sgnd = false; + } + + jp2_image = jas_image_create(number_components, component_info, JAS_CLRSPC_UNKNOWN); + if (jp2_image == 0) + { + jas_stream_close(jp2_stream); + DDebug() << "Unable to create JPEG2000 image" << endl; + return false; + } + + if (observer) + observer->progressInfo(m_image, 0.1); + + // ------------------------------------------------------------------- + // Check color space. + + if (number_components >= 3 ) // RGB & RGBA + { + // Alpha Channel + if (number_components == 4 ) + jas_image_setcmpttype(jp2_image, 3, JAS_IMAGE_CT_OPACITY); + + jas_image_setclrspc(jp2_image, JAS_CLRSPC_SRGB); + jas_image_setcmpttype(jp2_image, 0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R)); + jas_image_setcmpttype(jp2_image, 1, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G)); + jas_image_setcmpttype(jp2_image, 2, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B)); + } + + // ------------------------------------------------------------------- + // Set ICC color profile. + + // FIXME : doesn't work yet! + + jas_cmprof_t *cm_profile = 0; + jas_iccprof_t *icc_profile = 0; + + TQByteArray profile_rawdata = m_image->getICCProfil(); + + icc_profile = jas_iccprof_createfrombuf((uchar*)profile_rawdata.data(), profile_rawdata.size()); + if (icc_profile != 0) + { + cm_profile = jas_cmprof_createfromiccprof(icc_profile); + if (cm_profile != 0) + { + jas_image_setcmprof(jp2_image, cm_profile); + } + } + + // ------------------------------------------------------------------- + // Convert to JPEG 2000 pixels. + + for (i = 0 ; i < (long)number_components ; i++) + { + pixels[i] = jas_matrix_create(1, (unsigned int)imageWidth()); + if (pixels[i] == 0) + { + for (x = 0 ; x < i ; x++) + jas_matrix_destroy(pixels[x]); + + jas_image_destroy(jp2_image); + DDebug() << "Error encoding JPEG2000 image data : Memory Allocation Failed" << endl; + return false; + } + } + + unsigned char* data = imageData(); + unsigned char* pixel; + unsigned short r, g, b, a=0; + uint checkpoint = 0; + + for (y = 0 ; y < (long)imageHeight() ; y++) + { + if (observer && y == (long)checkpoint) + { + checkpoint += granularity(observer, imageHeight(), 0.8); + if (!observer->continueQuery(m_image)) + { + jas_image_destroy(jp2_image); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)y)/((float)imageHeight()) ))); + } + + for (x = 0 ; x < (long)imageWidth() ; x++) + { + pixel = &data[((y * imageWidth()) + x) * imageBytesDepth()]; + + if ( imageSixteenBit() ) // 16 bits image. + { + b = (unsigned short)(pixel[0]+256*pixel[1]); + g = (unsigned short)(pixel[2]+256*pixel[3]); + r = (unsigned short)(pixel[4]+256*pixel[5]); + + if (imageHasAlpha()) + a = (unsigned short)(pixel[6]+256*pixel[7]); + } + else // 8 bits image. + { + b = (unsigned short)pixel[0]; + g = (unsigned short)pixel[1]; + r = (unsigned short)pixel[2]; + + if (imageHasAlpha()) + a = (unsigned short)(pixel[3]); + } + + jas_matrix_setv(pixels[0], x, r); + jas_matrix_setv(pixels[1], x, g); + jas_matrix_setv(pixels[2], x, b); + + if (number_components > 3) + jas_matrix_setv(pixels[3], x, a); + } + + for (i = 0 ; i < (long)number_components ; i++) + { + int ret = jas_image_writecmpt(jp2_image, (short) i, 0, (unsigned int)y, + (unsigned int)imageWidth(), 1, pixels[i]); + if (ret != 0) + { + DDebug() << "Error encoding JPEG2000 image data" << endl; + + jas_image_destroy(jp2_image); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + return false; + } + } + } + + TQVariant qualityAttr = imageGetAttribute("quality"); + int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; + + if (quality < 0) + quality = 90; + if (quality > 100) + quality = 100; + + TQString rate; + TQTextStream ts( &rate, IO_WriteOnly ); + + // NOTE: to have a lossless compression use quality=100. + // jp2_encode()::optstr: + // - rate=#B => the resulting file size is about # bytes + // - rate=0.0 .. 1.0 => the resulting file size is about the factor times + // the uncompressed size + ts << "rate=" << ( quality / 100.0F ); + + DDebug() << "JPEG2000 quality: " << quality << endl; + DDebug() << "JPEG2000 " << rate << endl; + +# if defined(JAS_VERSION_MAJOR) && (JAS_VERSION_MAJOR >= 3) + const jas_image_fmtinfo_t *jp2_fmtinfo = jas_image_lookupfmtbyname("jp2"); + int ret = -1; + if (jp2_fmtinfo) + { + ret = jas_image_encode(jp2_image, jp2_stream, jp2_fmtinfo->id, rate.utf8().data()); + } +# else + int ret = jp2_encode(jp2_image, jp2_stream, rate.utf8().data()); +# endif + + if (ret != 0) + { + DDebug() << "Unable to encode JPEG2000 image" << endl; + + jas_image_destroy(jp2_image); + jas_stream_close(jp2_stream); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + + return false; + } + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageSetAttribute("savedformat", "JP2K"); + + saveMetadata(filePath); + + jas_image_destroy(jp2_image); + jas_stream_close(jp2_stream); + for (i = 0 ; i < (long)number_components ; i++) + jas_matrix_destroy(pixels[i]); + + jas_cleanup(); + + return true; +} + +bool JP2KLoader::hasAlpha() const +{ + return m_hasAlpha; +} + +bool JP2KLoader::sixteenBit() const +{ + return m_sixteenBit; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/jp2kloader.h b/src/libs/dimg/loaders/jp2kloader.h new file mode 100644 index 00000000..04ec214e --- /dev/null +++ b/src/libs/dimg/loaders/jp2kloader.h @@ -0,0 +1,69 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-06-14 + * Description : A JPEG2000 IO file for DImg framework + * + * Copyright (C) 2006-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef JP2KLOADER_H +#define JP2KLOADER_H + +// C ansi includes. + +extern "C" +{ +#include +} + +// TQt includes. + +#include + +// Local includes. + +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ +class DImg; + +class DIGIKAM_EXPORT JP2KLoader : public DImgLoader +{ + +public: + + JP2KLoader(DImg* image); + + bool load(const TQString& filePath, DImgLoaderObserver *observer); + bool save(const TQString& filePath, DImgLoaderObserver *observer); + + virtual bool hasAlpha() const; + virtual bool sixteenBit() const; + virtual bool isReadOnly() const { return false; }; + +private: + + bool m_sixteenBit; + bool m_hasAlpha; +}; + +} // NameSpace Digikam + +#endif /* JP2KLOADER_H */ diff --git a/src/libs/dimg/loaders/jp2ksettings.cpp b/src/libs/dimg/loaders/jp2ksettings.cpp new file mode 100644 index 00000000..af0737c1 --- /dev/null +++ b/src/libs/dimg/loaders/jp2ksettings.cpp @@ -0,0 +1,139 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save JPEG 2000 image options. + * + * Copyright (C) 2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include + +// Local includes. + +#include "jp2ksettings.h" +#include "jp2ksettings.moc" + +namespace Digikam +{ + +class JP2KSettingsPriv +{ + +public: + + JP2KSettingsPriv() + { + JPEG2000Grid = 0; + labelJPEG2000compression = 0; + JPEG2000compression = 0; + JPEG2000LossLess = 0; + } + + TQGridLayout *JPEG2000Grid; + + TQLabel *labelJPEG2000compression; + + TQCheckBox *JPEG2000LossLess; + + KIntNumInput *JPEG2000compression; +}; + +JP2KSettings::JP2KSettings(TQWidget *parent) + : TQWidget(parent, 0, TQt::WDestructiveClose) +{ + d = new JP2KSettingsPriv; + + d->JPEG2000Grid = new TQGridLayout(this, 1, 1, KDialog::spacingHint()); + d->JPEG2000LossLess = new TQCheckBox(i18n("Lossless JPEG 2000 files"), this); + + TQWhatsThis::add(d->JPEG2000LossLess, i18n("

Toggle lossless compression for JPEG 2000 images.

" + "If you enable this option, you will use a lossless method " + "to compress JPEG 2000 pictures.

")); + + d->JPEG2000compression = new KIntNumInput(75, this); + d->JPEG2000compression->setRange(1, 100, 1, true ); + d->labelJPEG2000compression = new TQLabel(i18n("JPEG 2000 quality:"), this); + + TQWhatsThis::add( d->JPEG2000compression, i18n("

The quality value for JPEG 2000 images:

" + "1: low quality (high compression and small " + "file size)

" + "50: medium quality

" + "75: good quality (default)

" + "100: high quality (no compression and " + "large file size)

" + "Note: JPEG 2000 is not a lossless image " + "compression format when you use this setting.")); + + d->JPEG2000Grid->addMultiCellWidget(d->JPEG2000LossLess, 0, 0, 0, 1); + d->JPEG2000Grid->addMultiCellWidget(d->labelJPEG2000compression, 1, 1, 0, 0); + d->JPEG2000Grid->addMultiCellWidget(d->JPEG2000compression, 1, 1, 1, 1); + d->JPEG2000Grid->setColStretch(1, 10); + + connect(d->JPEG2000LossLess, TQ_SIGNAL(toggled(bool)), + this, TQ_SLOT(slotToggleJPEG2000LossLess(bool))); + + connect(d->JPEG2000LossLess, TQ_SIGNAL(toggled(bool)), + this, TQ_SLOT(slotToggleJPEG2000LossLess(bool))); +} + +JP2KSettings::~JP2KSettings() +{ + delete d; +} + +void JP2KSettings::setCompressionValue(int val) +{ + d->JPEG2000compression->setValue(val); +} + +int JP2KSettings::getCompressionValue() +{ + return d->JPEG2000compression->value(); +} + +void JP2KSettings::setLossLessCompression(bool b) +{ + d->JPEG2000LossLess->setChecked(b); + slotToggleJPEG2000LossLess(d->JPEG2000LossLess->isChecked()); +} + +bool JP2KSettings::getLossLessCompression() +{ + return d->JPEG2000LossLess->isChecked(); +} + +void JP2KSettings::slotToggleJPEG2000LossLess(bool b) +{ + d->JPEG2000compression->setEnabled(!b); + d->labelJPEG2000compression->setEnabled(!b); +} + +} // namespace Digikam + diff --git a/src/libs/dimg/loaders/jp2ksettings.h b/src/libs/dimg/loaders/jp2ksettings.h new file mode 100644 index 00000000..951cb2fc --- /dev/null +++ b/src/libs/dimg/loaders/jp2ksettings.h @@ -0,0 +1,67 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save JPEG 2000 image options. + * + * Copyright (C) 2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef JP2KSETTINGS_H +#define JP2KSETTINGS_H + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class JP2KSettingsPriv; + +class DIGIKAM_EXPORT JP2KSettings : public TQWidget +{ +TQ_OBJECT + + +public: + + JP2KSettings(TQWidget *parent=0); + ~JP2KSettings(); + + void setCompressionValue(int val); + int getCompressionValue(); + + void setLossLessCompression(bool b); + bool getLossLessCompression(); + +private slots: + + void slotToggleJPEG2000LossLess(bool); + +private: + + JP2KSettingsPriv* d; +}; + +} // namespace Digikam + +#endif /* JP2KSETTINGS_H */ diff --git a/src/libs/dimg/loaders/jpegloader.cpp b/src/libs/dimg/loaders/jpegloader.cpp new file mode 100644 index 00000000..58f6e20a --- /dev/null +++ b/src/libs/dimg/loaders/jpegloader.cpp @@ -0,0 +1,676 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-14 + * Description : A JPEG IO file for DImg framework + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#define XMD_H + +// This line must be commented to prevent any latency time +// when we use threaded image loader interface for each image +// files io. Uncomment this line only for debugging. +//#define ENABLE_DEBUG_MESSAGES + +// C ansi includes. + +extern "C" +{ +#include "iccjpeg.h" +} + +// C+ includes. + +#include +#include + +// TQt includes. + +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "jpegloader.h" + +namespace Digikam +{ + +// To manage Errors/Warnings handling provide by libjpeg + +void JPEGLoader::dimg_jpeg_error_exit(j_common_ptr cinfo) +{ + dimg_jpeg_error_mgr* myerr = (dimg_jpeg_error_mgr*) cinfo->err; + + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << endl; +#endif + + longjmp(myerr->setjmp_buffer, 1); +} + +void JPEGLoader::dimg_jpeg_emit_message(j_common_ptr cinfo, int msg_level) +{ + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << " (" << msg_level << ")" << endl; +#else + Q_UNUSED(msg_level); +#endif +} + +void JPEGLoader::dimg_jpeg_output_message(j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << endl; +#endif +} + +JPEGLoader::JPEGLoader(DImg* image) + : DImgLoader(image) +{ +} + +bool JPEGLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + readMetadata(filePath, DImg::JPEG); + + FILE *file = fopen(TQFile::encodeName(filePath), "rb"); + if (!file) + return false; + + unsigned char header[2]; + + if (fread(&header, 2, 1, file) != 1) + { + fclose(file); + return false; + } + + unsigned char jpegID[] = { 0xFF, 0xD8 }; + + if (memcmp(header, jpegID, 2) != 0) + { + // not a jpeg file + fclose(file); + return false; + } + + fseek(file, 0L, SEEK_SET); + + struct jpeg_decompress_struct cinfo; + struct dimg_jpeg_error_mgr jerr; + + // ------------------------------------------------------------------- + // JPEG error handling. + + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = dimg_jpeg_error_exit; + cinfo.err->emit_message = dimg_jpeg_emit_message; + cinfo.err->output_message = dimg_jpeg_output_message; + + // If an error occurs during reading, libjpeg will jump here + + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_decompress(&cinfo); + fclose(file); + return false; + } + + // ------------------------------------------------------------------- + // Find out if we do the fast-track loading with reduced size. Jpeg specific. + int scaledLoadingSize = 0; + TQVariant attribute = imageGetAttribute("jpegScaledLoadingSize"); + if (attribute.isValid()) + scaledLoadingSize = attribute.toInt(); + + // ------------------------------------------------------------------- + // Set JPEG decompressor instance + + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, file); + + // Recording ICC profile marker (from iccjpeg.c) + setup_read_icc_profile(&cinfo); + + // read image information + jpeg_read_header(&cinfo, true); + + // set decompression parameters + cinfo.do_fancy_upsampling = false; + cinfo.do_block_smoothing = false; + + if (scaledLoadingSize) + { + int imgSize = TQMAX(cinfo.image_width, cinfo.image_height); + + // libjpeg supports 1/1, 1/2, 1/4, 1/8 + int scale=1; + while(scaledLoadingSize*scale*2<=imgSize) + { + scale*=2; + } + if(scale>8) scale=8; + + cinfo.scale_num=1; + cinfo.scale_denom=scale; + } + + // Libjpeg handles the following conversions: + // YCbCr => GRAYSCALE, YCbCr => RGB, GRAYSCALE => RGB, YCCK => CMYK + // So we cannot get RGB from CMYK or YCCK, CMYK conversion is handled below + switch (cinfo.jpeg_color_space) + { + case JCS_UNKNOWN: + // perhaps jpeg_read_header did some guessing, leave value unchanged + break; + case JCS_GRAYSCALE: + case JCS_RGB: + case JCS_YCbCr: + cinfo.out_color_space = JCS_RGB; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo.out_color_space = JCS_CMYK; + break; + } + + // initialize decompression + jpeg_start_decompress(&cinfo); + + // some pseudo-progress + if (observer) + observer->progressInfo(m_image, 0.1); + + // ------------------------------------------------------------------- + // Get image data. + + int w = cinfo.output_width; + int h = cinfo.output_height; + uchar *dest = 0; + + uchar *ptr, *line[16], *data=0; + uchar *ptr2=0; + int x, y, l, i, scans, count, prevy; + + if (cinfo.rec_outbuf_height > 16) + { + jpeg_destroy_decompress(&cinfo); + fclose(file); + DDebug() << k_funcinfo << "Height of JPEG scanline buffer out of range!" << endl; + return false; + } + + // We only take RGB with 1 or 3 components, or CMYK with 4 components + if (!( + (cinfo.out_color_space == JCS_RGB && (cinfo.output_components == 3 || cinfo.output_components == 1)) + || (cinfo.out_color_space == JCS_CMYK && cinfo.output_components == 4) + )) + { + jpeg_destroy_decompress(&cinfo); + fclose(file); + DDebug() << k_funcinfo + << "JPEG colorspace (" + << cinfo.out_color_space + << ") or Number of JPEG color components (" + << cinfo.output_components + << ") unsupported!" << endl; + return false; + } + + data = new uchar[w * 16 * cinfo.output_components]; + + if (!data) + { + jpeg_destroy_decompress(&cinfo); + fclose(file); + DDebug() << k_funcinfo << "Cannot allocate memory!" << endl; + return false; + } + + dest = new uchar[w * h * 4]; + + if (!dest) + { + delete [] data; + jpeg_destroy_decompress(&cinfo); + fclose(file); + DDebug() << k_funcinfo << "Cannot allocate memory!" << endl; + return false; + } + + ptr2 = dest; + count = 0; + prevy = 0; + + if (cinfo.output_components == 3) + { + for (i = 0; i < cinfo.rec_outbuf_height; i++) + line[i] = data + (i * w * 3); + + int checkPoint = 0; + for (l = 0; l < h; l += cinfo.rec_outbuf_height) + { + // use 0-10% and 90-100% for pseudo-progress + if (observer && l >= checkPoint) + { + checkPoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] dest; + jpeg_destroy_decompress(&cinfo); + fclose(file); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)l)/((float)h) ))); + } + + jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height); + scans = cinfo.rec_outbuf_height; + + if ((h - l) < scans) + scans = h - l; + + ptr = data; + + for (y = 0; y < scans; y++) + { + for (x = 0; x < w; x++) + { + ptr2[3] = 0xFF; + ptr2[2] = ptr[0]; + ptr2[1] = ptr[1]; + ptr2[0] = ptr[2]; + + ptr += 3; + ptr2 += 4; + } + } + } + } + else if (cinfo.output_components == 1) + { + for (i = 0; i < cinfo.rec_outbuf_height; i++) + line[i] = data + (i * w); + + int checkPoint = 0; + for (l = 0; l < h; l += cinfo.rec_outbuf_height) + { + if (observer && l >= checkPoint) + { + checkPoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] dest; + jpeg_destroy_decompress(&cinfo); + fclose(file); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)l)/((float)h) ))); + } + + jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height); + scans = cinfo.rec_outbuf_height; + + if ((h - l) < scans) + scans = h - l; + + ptr = data; + + for (y = 0; y < scans; y++) + { + for (x = 0; x < w; x++) + { + ptr2[3] = 0xFF; + ptr2[2] = ptr[0]; + ptr2[1] = ptr[0]; + ptr2[0] = ptr[0]; + + ptr ++; + ptr2 += 4; + } + } + } + } + else // CMYK + { + for (i = 0; i < cinfo.rec_outbuf_height; i++) + line[i] = data + (i * w * 4); + + int checkPoint = 0; + for (l = 0; l < h; l += cinfo.rec_outbuf_height) + { + // use 0-10% and 90-100% for pseudo-progress + if (observer && l >= checkPoint) + { + checkPoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] dest; + jpeg_destroy_decompress(&cinfo); + fclose(file); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)l)/((float)h) ))); + } + + jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height); + scans = cinfo.rec_outbuf_height; + + if ((h - l) < scans) + scans = h - l; + + ptr = data; + + for (y = 0; y < scans; y++) + { + for (x = 0; x < w; x++) + { + // Inspired by TQt's JPEG loader + + int k = ptr[3]; + + ptr2[3] = 0xFF; + ptr2[2] = k * ptr[0] / 255; + ptr2[1] = k * ptr[1] / 255; + ptr2[0] = k * ptr[2] / 255; + + ptr += 4; + ptr2 += 4; + } + } + } + } + + delete [] data; + + // ------------------------------------------------------------------- + // Read image ICC profile + + TQMap& metaData = imageMetaData(); + + JOCTET *profile_data=NULL; + uint profile_size; + + read_icc_profile (&cinfo, &profile_data, &profile_size); + + if (profile_data != NULL) + { + TQByteArray profile_rawdata(profile_size); + memcpy(profile_rawdata.data(), profile_data, profile_size); + metaData.insert(DImg::ICC, profile_rawdata); + free (profile_data); + } + else + { + // If ICC profile is null, check Exif metadata. + checkExifWorkingColorSpace(); + } + + // ------------------------------------------------------------------- + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + // ------------------------------------------------------------------- + + fclose(file); + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageWidth() = w; + imageHeight() = h; + imageData() = dest; + imageSetAttribute("format", "JPEG"); + + return true; +} + +bool JPEGLoader::save(const TQString& filePath, DImgLoaderObserver *observer) +{ + FILE *file = fopen(TQFile::encodeName(filePath), "wb"); + if (!file) + return false; + + struct jpeg_compress_struct cinfo; + struct dimg_jpeg_error_mgr jerr; + + // ------------------------------------------------------------------- + // JPEG error handling. + + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = dimg_jpeg_error_exit; + cinfo.err->emit_message = dimg_jpeg_emit_message; + cinfo.err->output_message = dimg_jpeg_output_message; + + // If an error occurs during writing, libjpeg will jump here + + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_compress(&cinfo); + fclose(file); + return false; + } + + // ------------------------------------------------------------------- + // Set JPEG compressor instance + + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, file); + + uint& w = imageWidth(); + uint& h = imageHeight(); + unsigned char*& data = imageData(); + + // Size of image. + cinfo.image_width = w; + cinfo.image_height = h; + + // Color components of image in RGB. + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + + TQVariant qualityAttr = imageGetAttribute("quality"); + int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; + + if (quality < 0) + quality = 90; + if (quality > 100) + quality = 100; + + TQVariant subSamplingAttr = imageGetAttribute("subsampling"); + int subsampling = subSamplingAttr.isValid() ? subSamplingAttr.toInt() : 1; // Medium + + jpeg_set_defaults(&cinfo); + + // B.K.O #149578: set horizontal and vertical chroma subsampling factor to encoder. + // See this page for details: http://en.wikipedia.org/wiki/Chroma_subsampling + + switch (subsampling) + { + case 1: // 2x1, 1x1, 1x1 (4:2:2) : Medium + { + DDebug() << "Using LibJPEG medium chroma-subsampling (4:2:2)" << endl; + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 1; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + break; + } + case 2: // 2x2, 1x1, 1x1 (4:1:1) : High + { + DDebug() << "Using LibJPEG high chroma-subsampling (4:1:1)" << endl; + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 2; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + break; + } + default: // 1x1 1x1 1x1 (4:4:4) : None + { + DDebug() << "Using LibJPEG none chroma-subsampling (4:4:4)" << endl; + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + break; + } + } + + jpeg_set_quality(&cinfo, quality, true); + jpeg_start_compress(&cinfo, true); + + DDebug() << "Using LibJPEG quality compression value: " << quality << endl; + + if (observer) + observer->progressInfo(m_image, 0.1); + + // ------------------------------------------------------------------- + // Write ICC profil. + + TQByteArray profile_rawdata = m_image->getICCProfil(); + + if (!profile_rawdata.isEmpty()) + { + write_icc_profile (&cinfo, (JOCTET *)profile_rawdata.data(), profile_rawdata.size()); + } + + if (observer) + observer->progressInfo(m_image, 0.2); + + // ------------------------------------------------------------------- + // Write Image data. + + uchar* line = new uchar[w*3]; + uchar* dstPtr = 0; + uint checkPoint = 0; + + if (!imageSixteenBit()) // 8 bits image. + { + + uchar* srcPtr = data; + + for (uint j=0; jcontinueQuery(m_image)) + { + delete [] line; + jpeg_destroy_compress(&cinfo); + fclose(file); + return false; + } + // use 0-20% for pseudo-progress, now fill 20-100% + observer->progressInfo(m_image, 0.2 + (0.8 * ( ((float)j)/((float)h) ))); + } + + dstPtr = line; + + for (uint i = 0; i < w; i++) + { + dstPtr[2] = srcPtr[0]; + dstPtr[1] = srcPtr[1]; + dstPtr[0] = srcPtr[2]; + + srcPtr += 4; + dstPtr += 3; + } + + jpeg_write_scanlines(&cinfo, &line, 1); + } + } + else + { + unsigned short* srcPtr = (unsigned short*)data; + + for (uint j=0; jcontinueQuery(m_image)) + { + delete [] line; + jpeg_destroy_compress(&cinfo); + fclose(file); + return false; + } + // use 0-20% for pseudo-progress, now fill 20-100% + observer->progressInfo(m_image, 0.2 + (0.8 * ( ((float)j)/((float)h) ))); + } + + dstPtr = line; + + for (uint i = 0; i < w; i++) + { + dstPtr[2] = (srcPtr[0] * 255UL)/65535UL; + dstPtr[1] = (srcPtr[1] * 255UL)/65535UL; + dstPtr[0] = (srcPtr[2] * 255UL)/65535UL; + + srcPtr += 4; + dstPtr += 3; + } + + jpeg_write_scanlines(&cinfo, &line, 1); + } + } + + delete [] line; + + // ------------------------------------------------------------------- + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + fclose(file); + + imageSetAttribute("savedformat", "JPEG"); + + saveMetadata(filePath); + + return true; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/jpegloader.h b/src/libs/dimg/loaders/jpegloader.h new file mode 100644 index 00000000..b5d64f18 --- /dev/null +++ b/src/libs/dimg/loaders/jpegloader.h @@ -0,0 +1,80 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-14 + * Description : A JPEG IO file for DImg framework + * + * Copyright (C) 2005 by Renchi Raju , Gilles Caulier + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef JPEGLOADER_H +#define JPEGLOADER_H + +// C ansi includes. + +extern "C" +{ +#include +#include +} + +// TQt includes. + +#include + +// Local includes. + +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ +class DImg; + +class DIGIKAM_EXPORT JPEGLoader : public DImgLoader +{ + +public: + + JPEGLoader(DImg* image); + + bool load(const TQString& filePath, DImgLoaderObserver *observer); + bool save(const TQString& filePath, DImgLoaderObserver *observer); + + virtual bool hasAlpha() const { return false; } + virtual bool sixteenBit() const { return false; } + virtual bool isReadOnly() const { return false; }; + +private: + + // To manage Errors/Warnings handling provide by libjpeg + + struct dimg_jpeg_error_mgr : public jpeg_error_mgr + { + jmp_buf setjmp_buffer; + }; + + static void dimg_jpeg_error_exit(j_common_ptr cinfo); + static void dimg_jpeg_emit_message(j_common_ptr cinfo, int msg_level); + static void dimg_jpeg_output_message(j_common_ptr cinfo); + +}; + +} // NameSpace Digikam + +#endif /* JPEGLOADER_H */ diff --git a/src/libs/dimg/loaders/jpegsettings.cpp b/src/libs/dimg/loaders/jpegsettings.cpp new file mode 100644 index 00000000..62bd6365 --- /dev/null +++ b/src/libs/dimg/loaders/jpegsettings.cpp @@ -0,0 +1,154 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save JPEG image options. + * + * Copyright (C) 2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include + +// Local includes. + +#include "jpegsettings.h" +#include "jpegsettings.moc" + +namespace Digikam +{ + +class JPEGSettingsPriv +{ + +public: + + JPEGSettingsPriv() + { + JPEGGrid = 0; + labelJPEGcompression = 0; + JPEGcompression = 0; + labelWarning = 0; + labelSubSampling = 0; + subSamplingCB = 0; + } + + TQGridLayout *JPEGGrid; + + TQLabel *labelJPEGcompression; + TQLabel *labelSubSampling; + + TQComboBox *subSamplingCB; + + KActiveLabel *labelWarning; + + KIntNumInput *JPEGcompression; +}; + +JPEGSettings::JPEGSettings(TQWidget *parent) + : TQWidget(parent, 0, TQt::WDestructiveClose) +{ + d = new JPEGSettingsPriv; + + d->JPEGGrid = new TQGridLayout(this, 1, 2, KDialog::spacingHint()); + d->JPEGcompression = new KIntNumInput(75, this); + d->JPEGcompression->setRange(1, 100, 1, true ); + d->labelJPEGcompression = new TQLabel(i18n("JPEG quality:"), this); + + TQWhatsThis::add(d->JPEGcompression, i18n("

The JPEG image quality:

" + "1: low quality (high compression and small " + "file size)

" + "50: medium quality

" + "75: good quality (default)

" + "100: high quality (no compression and " + "large file size)

" + "Note: JPEG always uses lossy compression.")); + + d->labelWarning = new KActiveLabel(i18n("" + "Warning: JPEG is a
" + "lossy compression
" + "image format!

" + ""), this); + + d->labelWarning->setFrameStyle(TQFrame::Box | TQFrame::Plain); + d->labelWarning->setLineWidth(1); + d->labelWarning->setFrameShape(TQFrame::Box); + + d->labelSubSampling = new TQLabel(i18n("Chroma subsampling:"), this); + + d->subSamplingCB = new TQComboBox(false, this); + d->subSamplingCB->insertItem(i18n("None")); // 1x1, 1x1, 1x1 (4:4:4) + d->subSamplingCB->insertItem(i18n("Medium")); // 2x1, 1x1, 1x1 (4:2:2) + d->subSamplingCB->insertItem(i18n("High")); // 2x2, 1x1, 1x1 (4:1:1) + TQWhatsThis::add(d->subSamplingCB, i18n("

JPEG Chroma subsampling level \n(color is saved with less resolution " "than luminance):

" + "None=best: uses 4:4:4 ratio. Does not employ chroma " + "subsampling at all. This preserves edges and contrasting " + "colors, whilst adding no additional compression

" + "Medium: uses 4:2:2 ratio. Medium compression: reduces " + "the color resolution by one-third with little to " + "no visual difference

" + "High: use 4:1:1 ratio. High compression: suits " + "images with soft edges but tends to alter colors

" + "Note: JPEG always uses lossy compression.")); + + d->JPEGGrid->addMultiCellWidget(d->labelJPEGcompression, 0, 0, 0, 0); + d->JPEGGrid->addMultiCellWidget(d->JPEGcompression, 0, 0, 1, 1); + d->JPEGGrid->addMultiCellWidget(d->labelSubSampling, 1, 1, 0, 0); + d->JPEGGrid->addMultiCellWidget(d->subSamplingCB, 1, 1, 1, 1); + d->JPEGGrid->addMultiCellWidget(d->labelWarning, 0, 1, 2, 2); + d->JPEGGrid->setColStretch(1, 10); + d->JPEGGrid->setRowStretch(2, 10); +} + +JPEGSettings::~JPEGSettings() +{ + delete d; +} + +void JPEGSettings::setCompressionValue(int val) +{ + d->JPEGcompression->setValue(val); +} + +int JPEGSettings::getCompressionValue() +{ + return d->JPEGcompression->value(); +} + +void JPEGSettings::setSubSamplingValue(int val) +{ + d->subSamplingCB->setCurrentItem(val); +} + +int JPEGSettings::getSubSamplingValue() +{ + return d->subSamplingCB->currentItem(); +} + +} // namespace Digikam diff --git a/src/libs/dimg/loaders/jpegsettings.h b/src/libs/dimg/loaders/jpegsettings.h new file mode 100644 index 00000000..70e20d42 --- /dev/null +++ b/src/libs/dimg/loaders/jpegsettings.h @@ -0,0 +1,63 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save JPEG image options. + * + * Copyright (C) 2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef JPEGSETTINGS_H +#define JPEGSETTINGS_H + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class JPEGSettingsPriv; + +class DIGIKAM_EXPORT JPEGSettings : public TQWidget +{ +TQ_OBJECT + + +public: + + JPEGSettings(TQWidget *parent=0); + ~JPEGSettings(); + + void setCompressionValue(int val); + int getCompressionValue(); + + void setSubSamplingValue(int val); + int getSubSamplingValue(); + +private: + + JPEGSettingsPriv* d; +}; + +} // namespace Digikam + +#endif /* JPEGSETTINGS_H */ diff --git a/src/libs/dimg/loaders/pngloader.cpp b/src/libs/dimg/loaders/pngloader.cpp new file mode 100644 index 00000000..402cf944 --- /dev/null +++ b/src/libs/dimg/loaders/pngloader.cpp @@ -0,0 +1,993 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-11-01 + * Description : a PNG image loader for DImg framework. + * + * Copyright (C) 2005-2009 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// This line must be commented to prevent any latency time +// when we use threaded image loader interface for each image +// files io. Uncomment this line only for debugging. +//#define ENABLE_DEBUG_MESSAGES + +#define PNG_BYTES_TO_CHECK 4 + +// C Ansi includes. + +extern "C" +{ +#include +#include +} + +// C++ includes. + +#include +#include + +// TQt includes. + +#include +#include + +// Local includes. + +#include "daboutdata.h" +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "pngloader.h" + +namespace Digikam +{ + +#if PNG_LIBPNG_VER_MAJOR > 1 || ( PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5 ) + typedef png_bytep iCCP_data; +#else + typedef png_charp iCCP_data; +#endif + +PNGLoader::PNGLoader(DImg* image) + : DImgLoader(image) +{ + m_hasAlpha = false; + m_sixteenBit = false; +} + +bool PNGLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + png_uint_32 w32, h32; + int width, height; + FILE *f; + int bit_depth, color_type, interlace_type; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + + readMetadata(filePath, DImg::PNG); + + // ------------------------------------------------------------------- + // Open the file + + f = fopen(TQFile::encodeName(filePath), "rb"); + if ( !f ) + { + DDebug() << k_funcinfo << "Cannot open image file." << endl; + return false; + } + + unsigned char buf[PNG_BYTES_TO_CHECK]; + + fread(buf, 1, PNG_BYTES_TO_CHECK, f); + if (png_sig_cmp(buf, 0, PNG_BYTES_TO_CHECK)) + { + DDebug() << k_funcinfo << "Not a PNG image file." << endl; + fclose(f); + return false; + } + rewind(f); + + // ------------------------------------------------------------------- + // Initialize the internal structures + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) + { + DDebug() << k_funcinfo << "Invalid PNG image file structure." << endl; + fclose(f); + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + DDebug() << k_funcinfo << "Cannot reading PNG image file structure." << endl; + png_destroy_read_struct(&png_ptr, NULL, NULL); + fclose(f); + return false; + } + + // ------------------------------------------------------------------- + // PNG error handling. If an error occurs during reading, libpng + // will jump here + + if (setjmp(png_jmpbuf(png_ptr))) + { + DDebug() << k_funcinfo << "Internal libPNG error during reading file. Process aborted!" << endl; + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(f); + return false; + } + + png_init_io(png_ptr, f); + + // ------------------------------------------------------------------- + // Read all PNG info up to image data + + png_read_info(png_ptr, info_ptr); + + png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *) (&w32), + (png_uint_32 *) (&h32), &bit_depth, &color_type, + &interlace_type, NULL, NULL); + + width = (int)w32; + height = (int)h32; + + // TODO: Endianness: + // You may notice that the code for little and big endian + // below is now identical. This was found to work by PPC users. + // If this proves right, all the conditional clauses can be removed. + + if (bit_depth == 16) + { +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in 16 bits/color/pixel." << endl; +#endif + m_sixteenBit = true; + + switch (color_type) + { + case PNG_COLOR_TYPE_RGB : // RGB +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_RGB" << endl; +#endif + m_hasAlpha = false; + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); + else // PPC + png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); + + break; + + case PNG_COLOR_TYPE_RGB_ALPHA : // RGBA +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_RGB_ALPHA" << endl; +#endif + m_hasAlpha = true; + break; + + case PNG_COLOR_TYPE_GRAY : // Grayscale +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_GRAY" << endl; +#endif + png_set_gray_to_rgb(png_ptr); + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); + else // PPC + png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); + + m_hasAlpha = false; + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA : // Grayscale + Alpha +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_GRAY_ALPHA" << endl; +#endif + png_set_gray_to_rgb(png_ptr); + m_hasAlpha = true; + break; + + case PNG_COLOR_TYPE_PALETTE : // Indexed +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_PALETTE" << endl; +#endif + png_set_palette_to_rgb(png_ptr); + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); + else // PPC + png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); + + m_hasAlpha = false; + break; + + default: +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << "PNG color type unknown." << endl; +#endif + return false; + } + } + else + { +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << "PNG in >=8 bits/color/pixel." << endl; +#endif + m_sixteenBit = false; + png_set_packing(png_ptr); + + switch (color_type) + { + case PNG_COLOR_TYPE_RGB : // RGB +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_RGB" << endl; +#endif + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + else // PPC + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + + m_hasAlpha = false; + break; + + case PNG_COLOR_TYPE_RGB_ALPHA : // RGBA +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_RGB_ALPHA" << endl; +#endif + m_hasAlpha = true; + break; + + case PNG_COLOR_TYPE_GRAY : // Grayscale +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_GRAY" << endl; +#endif + png_set_expand_gray_1_2_4_to_8(png_ptr); + png_set_gray_to_rgb(png_ptr); + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + else // PPC + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + + m_hasAlpha = false; + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA : // Grayscale + alpha +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_GRAY_ALPHA" << endl; +#endif + png_set_gray_to_rgb(png_ptr); + m_hasAlpha = true; + break; + + case PNG_COLOR_TYPE_PALETTE : // Indexed +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "PNG in PNG_COLOR_TYPE_PALETTE" << endl; +#endif + png_set_packing(png_ptr); + png_set_palette_to_rgb(png_ptr); + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + else // PPC + png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); + + m_hasAlpha = true; + break; + + default: +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << "PNG color type unknown." << endl; +#endif + return false; + } + } + + if(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png_ptr); + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_bgr(png_ptr); + else // PPC + png_set_bgr(png_ptr); + //png_set_swap_alpha(png_ptr); + + if (observer) + observer->progressInfo(m_image, 0.1); + + // ------------------------------------------------------------------- + // Get image data. + + // Call before png_read_update_info and png_start_read_image() + // For non-interlaced images number_passes will be 1 + int number_passes = png_set_interlace_handling(png_ptr); + + png_read_update_info(png_ptr, info_ptr); + + uchar *data = 0; + + if (m_sixteenBit) + data = new uchar[width*height*8]; // 16 bits/color/pixel + else + data = new uchar[width*height*4]; // 8 bits/color/pixel + + uchar **lines = 0; + lines = (uchar **)malloc(height * sizeof(uchar *)); + if (!lines) + { + DDebug() << k_funcinfo << "Cannot allocate memory to load PNG image data." << endl; + png_read_end(png_ptr, info_ptr); + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); + fclose(f); + delete [] data; + return false; + } + + for (int i = 0; i < height; i++) + { + if (m_sixteenBit) + lines[i] = data + (i * width * 8); + else + lines[i] = data + (i * width * 4); + } + + // The easy way to read the whole image + // png_read_image(png_ptr, lines); + // The other way to read images is row by row. Necessary for observer. + // Now we need to deal with interlacing. + + for (int pass = 0; pass < number_passes; pass++) + { + int y; + int checkPoint = 0; + for (y = 0; y < height; y++) + { + if (observer && y == checkPoint) + { + checkPoint += granularity(observer, height, 0.7); + if (!observer->continueQuery(m_image)) + { + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); + fclose(f); + delete [] data; + free(lines); + return false; + } + // use 10% - 80% for progress while reading rows + observer->progressInfo(m_image, 0.1 + (0.7 * ( ((float)y)/((float)height) )) ); + } + + png_read_rows(png_ptr, lines+y, NULL, 1); + } + } + + free(lines); + + // Swap bytes in 16 bits/color/pixel for DImg + + if (m_sixteenBit) + { + uchar ptr[8]; // One pixel to swap + + for (int p = 0; p < width*height*8; p+=8) + { + memcpy (&ptr[0], &data[p], 8); // Current pixel + + data[ p ] = ptr[1]; // Blue + data[p+1] = ptr[0]; + data[p+2] = ptr[3]; // Green + data[p+3] = ptr[2]; + data[p+4] = ptr[5]; // Red + data[p+5] = ptr[4]; + data[p+6] = ptr[7]; // Alpha + data[p+7] = ptr[6]; + } + } + + if (observer) + observer->progressInfo(m_image, 0.9); + + // ------------------------------------------------------------------- + // Read image ICC profile + + TQMap& metaData = imageMetaData(); + +#if PNG_LIBPNG_VER_MAJOR > 1 || ( PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5 ) + png_charp profile_name; + iCCP_data profile_data=NULL; +#else + png_charp profile_name, profile_data=NULL; +#endif + png_uint_32 profile_size; + int compression_type; + + png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &profile_size); + + if (profile_data != NULL) + { + TQByteArray profile_rawdata(profile_size); + memcpy(profile_rawdata.data(), profile_data, profile_size); + metaData.insert(DImg::ICC, profile_rawdata); + } + else + { + // If ICC profile is null, check Exif metadata. + checkExifWorkingColorSpace(); + } + + // ------------------------------------------------------------------- + // Get embbeded text data. + + png_text* text_ptr; + int num_comments = png_get_text(png_ptr, info_ptr, &text_ptr, NULL); + + /* + Standard Embedded text includes in PNG : + + Title Short (one line) title or caption for image + Author Name of image's creator + Description Description of image (possibly long) + Copyright Copyright notice + Creation Time Time of original image creation + Software Software used to create the image + Disclaimer Legal disclaimer + Warning Warning of nature of content + Source Device used to create the image + Comment Miscellaneous comment; conversion from GIF comment + + Extra Raw profiles tag are used by ImageMAgick and defines at this URL : + http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-5.87/html/TagNames/PNG.html#TextualData + */ + + for (int i = 0; i < num_comments; i++) + { + // Check if we have a Raw profile embedded using ImageMagick technic. + + if (memcmp(text_ptr[i].key, "Raw profile type exif", 21) != 0 || + memcmp(text_ptr[i].key, "Raw profile type APP1", 21) != 0 || + memcmp(text_ptr[i].key, "Raw profile type iptc", 21) != 0) + { + imageSetEmbbededText(text_ptr[i].key, text_ptr[i].text); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "Reading PNG Embedded text: key=" << text_ptr[i].key + << " text=" << text_ptr[i].text << endl; +#endif + } + } + + // ------------------------------------------------------------------- + + png_read_end(png_ptr, info_ptr); + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); + fclose(f); + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageWidth() = width; + imageHeight() = height; + imageData() = data; + imageSetAttribute("format", "PNG"); + + return true; +} + +bool PNGLoader::save(const TQString& filePath, DImgLoaderObserver *observer) +{ + FILE *f; + png_structp png_ptr; + png_infop info_ptr; + uchar *ptr, *data = 0; + uint x, y, j; + png_bytep row_ptr; + png_color_8 sig_bit; + int quality = 75; + int compression = 3; + + // ------------------------------------------------------------------- + // Open the file + + f = fopen(TQFile::encodeName(filePath), "wb"); + if ( !f ) + { + DDebug() << k_funcinfo << "Cannot open target image file." << endl; + return false; + } + + + // ------------------------------------------------------------------- + // Initialize the internal structures + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) + { + DDebug() << k_funcinfo << "Invalid target PNG image file structure." << endl; + fclose(f); + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) + { + DDebug() << k_funcinfo << "Cannot create PNG image file structure." << endl; + png_destroy_write_struct(&png_ptr, (png_infopp) NULL); + fclose(f); + return false; + } + + // ------------------------------------------------------------------- + // PNG error handling. If an error occurs during writing, libpng + // will jump here + + if (setjmp(png_jmpbuf(png_ptr))) + { + DDebug() << k_funcinfo << "Internal libPNG error during writing file. Process aborted!" << endl; + fclose(f); + png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); + png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); + return false; + } + + png_init_io(png_ptr, f); + + if (TQImage::systemByteOrder() == TQImage::LittleEndian) // Intel + png_set_bgr(png_ptr); + else // PPC + png_set_swap_alpha(png_ptr); + + if (imageHasAlpha()) + { + png_set_IHDR(png_ptr, info_ptr, imageWidth(), imageHeight(), imageBitsDepth(), + PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + if (imageSixteenBit()) + data = new uchar[imageWidth() * 8 * sizeof(uchar)]; + else + data = new uchar[imageWidth() * 4 * sizeof(uchar)]; + } + else + { + png_set_IHDR(png_ptr, info_ptr, imageWidth(), imageHeight(), imageBitsDepth(), + PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + if (imageSixteenBit()) + data = new uchar[imageWidth() * 6 * sizeof(uchar)]; + else + data = new uchar[imageWidth() * 3 * sizeof(uchar)]; + } + + sig_bit.red = imageBitsDepth(); + sig_bit.green = imageBitsDepth(); + sig_bit.blue = imageBitsDepth(); + sig_bit.alpha = imageBitsDepth(); + png_set_sBIT(png_ptr, info_ptr, &sig_bit); + + // ------------------------------------------------------------------- + // Quality to convert to compression + + TQVariant qualityAttr = imageGetAttribute("quality"); + quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; + + if (quality < 1) + quality = 1; + if (quality > 99) + quality = 99; + + quality = quality / 10; + compression = 9 - quality; + + if (compression < 0) + compression = 0; + if (compression > 9) + compression = 9; + + png_set_compression_level(png_ptr, compression); + + // ------------------------------------------------------------------- + // Write ICC profil. + + TQByteArray profile_rawdata = m_image->getICCProfil(); + + if (!profile_rawdata.isEmpty()) + { +#if PNG_LIBPNG_VER_MAJOR > 1 || ( PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5 ) + png_set_iCCP(png_ptr, info_ptr, (png_charp)("icc"), PNG_COMPRESSION_TYPE_BASE, reinterpret_cast(profile_rawdata.data()), profile_rawdata.size()); +#else + png_set_iCCP(png_ptr, info_ptr, (png_charp)"icc", PNG_COMPRESSION_TYPE_BASE, profile_rawdata.data(), profile_rawdata.size()); +#endif + } + + // ------------------------------------------------------------------- + // Write embbeded Text + + typedef TQMap EmbeddedTextMap; + EmbeddedTextMap map = imageEmbeddedText(); + + for (EmbeddedTextMap::iterator it = map.begin(); it != map.end(); ++it) + { + if (it.key() != TQString("Software") && it.key() != TQString("Comment")) + { + png_text text; + text.key = (char*)it.key().ascii(); + text.text = (char*)it.data().ascii(); +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "Writing PNG Embedded text: key=" << text.key << " text=" << text.text << endl; +#endif + text.compression = PNG_TEXT_COMPRESSION_zTXt; + png_set_text(png_ptr, info_ptr, &(text), 1); + } + } + + // Update 'Software' text tag. + TQString software("digiKam "); + software.append(digikam_version); + TQString libpngver(PNG_HEADER_VERSION_STRING); + libpngver.replace('\n', ' '); + software.append(TQString(" (%1)").arg(libpngver)); + png_text text; + text.key = (png_charp)("Software"); + text.text = (char *)software.ascii(); +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "Writing PNG Embedded text: key=" << text.key << " text=" << text.text << endl; +#endif + text.compression = PNG_TEXT_COMPRESSION_zTXt; + png_set_text(png_ptr, info_ptr, &(text), 1); + + // Write embedded Raw profiles metadata (Exif/Iptc) in text tag using ImageMagick technic. + // Write digiKam comment like an iTXt chunk using UTF8 encoding. + // NOTE: iTXt will be enable by default with libpng >= 1.3.0. + + typedef TQMap MetaDataMap; + MetaDataMap metaDataMap = imageMetaData(); + + for (MetaDataMap::iterator it = metaDataMap.begin(); it != metaDataMap.end(); ++it) + { + TQByteArray ba = it.data(); + + switch (it.key()) + { + +#ifdef PNG_iTXt_SUPPORTED + + // TODO : this code is not yet tested. It require libpng 1.3.0. + + case(DImg::COM): + { + png_text comment; + comment.key = "Comment"; + comment.text = ba.data(); + comment.itxt_length = ba.size(); + comment.compression = PNG_ITXT_COMPRESSION_zTXt; + png_set_text(png_ptr, info_ptr, &(comment), 1); + + DDebug() << "Writing digiKam comment into iTXt PNG chunk : " << ba << endl; + break; + } +#endif + + case(DImg::EXIF): + { + const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; + TQByteArray profile; + + // If bytes array do not start with ImageMagick header, Exif metadata have been created from + // scratch using Exiv2. In this case, we need to add Exif header from start. + if (memcmp(ba.data(), "exif", 4) != 0 && + memcmp(ba.data(), "iptc", 4) != 0 && + memcmp(ba.data(), "profile", 7) != 0) + { + profile = TQByteArray(ba.size() + sizeof(ExifHeader)); + memcpy(profile.data(), ExifHeader, sizeof(ExifHeader)); + memcpy(profile.data()+sizeof(ExifHeader), ba.data(), ba.size()); + } + else + { + profile = ba; + } + + writeRawProfile(png_ptr, info_ptr, (png_charp)("exif"), profile.data(), (png_uint_32) profile.size()); + break; + } + case(DImg::IPTC): + { + writeRawProfile(png_ptr, info_ptr, (png_charp)("iptc"), ba.data(), (png_uint_32) ba.size()); + break; + } + default: + break; + } + } + + if (observer) + observer->progressInfo(m_image, 0.2); + + // ------------------------------------------------------------------- + // Write image data + + png_write_info(png_ptr, info_ptr); + png_set_shift(png_ptr, &sig_bit); + png_set_packing(png_ptr); + ptr = imageData(); + + uint checkPoint = 0; + for (y = 0; y < imageHeight(); y++) + { + + if (observer && y == checkPoint) + { + checkPoint += granularity(observer, imageHeight(), 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + fclose(f); + png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); + png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); + return false; + } + observer->progressInfo(m_image, 0.2 + (0.8 * ( ((float)y)/((float)imageHeight()) ))); + } + + j = 0; + + for (x = 0; x < imageWidth()*imageBytesDepth(); x+=imageBytesDepth()) + { + if (imageSixteenBit()) + { + if (imageHasAlpha()) + { + data[j++] = ptr[x+1]; // Blue + data[j++] = ptr[ x ]; + data[j++] = ptr[x+3]; // Green + data[j++] = ptr[x+2]; + data[j++] = ptr[x+5]; // Red + data[j++] = ptr[x+4]; + data[j++] = ptr[x+7]; // Alpha + data[j++] = ptr[x+6]; + } + else + { + data[j++] = ptr[x+1]; // Blue + data[j++] = ptr[ x ]; + data[j++] = ptr[x+3]; // Green + data[j++] = ptr[x+2]; + data[j++] = ptr[x+5]; // Red + data[j++] = ptr[x+4]; + } + } + else + { + if (imageHasAlpha()) + { + data[j++] = ptr[ x ]; // Blue + data[j++] = ptr[x+1]; // Green + data[j++] = ptr[x+2]; // Red + data[j++] = ptr[x+3]; // Alpha + } + else + { + data[j++] = ptr[ x ]; // Blue + data[j++] = ptr[x+1]; // Green + data[j++] = ptr[x+2]; // Red + } + } + } + + row_ptr = (png_bytep) data; + + png_write_rows(png_ptr, &row_ptr, 1); + ptr += (imageWidth() * imageBytesDepth()); + } + + delete [] data; + + // ------------------------------------------------------------------- + + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); + png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); + + fclose(f); + + imageSetAttribute("savedformat", "PNG"); + + saveMetadata(filePath); + + return true; +} + +bool PNGLoader::hasAlpha() const +{ + return m_hasAlpha; +} + +bool PNGLoader::sixteenBit() const +{ + return m_sixteenBit; +} + +void PNGLoader::writeRawProfile(png_struct *ping, png_info *ping_info, char *profile_type, + char *profile_data, png_uint_32 length) +{ + png_textp text; + + long i; + + uchar *sp; + + png_charp dp; + + png_uint_32 allocated_length, description_length; + + const uchar hex[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; + + DDebug() << "Writing Raw profile: type=" << profile_type << ", length=" << length << endl; + + text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text)); + description_length = strlen((const char *) profile_type); + allocated_length = (png_uint_32) (length*2 + (length >> 5) + 20 + description_length); + + text[0].text = (png_charp) png_malloc(ping, allocated_length); + text[0].key = (png_charp) png_malloc(ping, (png_uint_32) 80); + text[0].key[0] = '\0'; + + concatenateString(text[0].key, "Raw profile type ", 4096); + concatenateString(text[0].key, (const char *) profile_type, 62); + + sp = (uchar*)profile_data; + dp = text[0].text; + *dp++='\n'; + + copyString(dp, (const char *) profile_type, allocated_length); + + dp += description_length; + *dp++='\n'; + + formatString(dp, allocated_length-strlen(text[0].text), "%8lu ", length); + + dp += 8; + + for (i=0; i < (long) length; i++) + { + if (i%36 == 0) + *dp++='\n'; + + *(dp++)=(char) hex[((*sp >> 4) & 0x0f)]; + *(dp++)=(char) hex[((*sp++ ) & 0x0f)]; + } + + *dp++='\n'; + *dp='\0'; + text[0].text_length = (png_size_t) (dp-text[0].text); + text[0].compression = -1; + + if (text[0].text_length <= allocated_length) + png_set_text(ping, ping_info,text, 1); + + png_free(ping, text[0].text); + png_free(ping, text[0].key); + png_free(ping, text); +} + +size_t PNGLoader::concatenateString(char *destination, const char *source, const size_t length) +{ + char *q; + + const char *p; + + size_t i; + + size_t count; + + if ( !destination || !source || length == 0 ) + return 0; + + p = source; + q = destination; + i = length; + + while ((i-- != 0) && (*q != '\0')) + q++; + + count = (size_t) (q-destination); + i = length-count; + + if (i == 0) + return(count+strlen(p)); + + while (*p != '\0') + { + if (i != 1) + { + *q++=(*p); + i--; + } + p++; + } + + *q='\0'; + + return(count+(p-source)); +} + +size_t PNGLoader::copyString(char *destination, const char *source, const size_t length) +{ + char *q; + + const char *p; + + size_t i; + + if ( !destination || !source || length == 0 ) + return 0; + + p = source; + q = destination; + i = length; + + if ((i != 0) && (--i != 0)) + { + do + { + if ((*q++=(*p++)) == '\0') + break; + } + while (--i != 0); + } + + if (i == 0) + { + if (length != 0) + *q='\0'; + + do + { + } + while (*p++ != '\0'); + } + + return((size_t) (p-source-1)); +} + +long PNGLoader::formatString(char *string, const size_t length, const char *format,...) +{ + long n; + + va_list operands; + + va_start(operands,format); + n = (long) formatStringList(string, length, format, operands); + va_end(operands); + return(n); +} + +long PNGLoader::formatStringList(char *string, const size_t length, const char *format, va_list operands) +{ + int n = vsnprintf(string, length, format, operands); + + if (n < 0) + string[length-1] = '\0'; + + return((long) n); +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/pngloader.h b/src/libs/dimg/loaders/pngloader.h new file mode 100644 index 00000000..202a278c --- /dev/null +++ b/src/libs/dimg/loaders/pngloader.h @@ -0,0 +1,73 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-11-01 + * Description : a PNG image loader for DImg framework. + * + * Copyright (C) 2005-2009 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef PNGLOADER_H +#define PNGLOADER_H + +extern "C" +{ +#include +#include +} + +// Local includes. + +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ +class DImg; + +class DIGIKAM_EXPORT PNGLoader : public DImgLoader +{ +public: + + PNGLoader(DImg* image); + + bool load(const TQString& filePath, DImgLoaderObserver *observer); + bool save(const TQString& filePath, DImgLoaderObserver *observer); + + virtual bool hasAlpha() const; + virtual bool sixteenBit() const; + virtual bool isReadOnly() const { return false; }; + +private: + + void writeRawProfile(png_struct *ping, png_info *ping_info, char *profile_type, + char *profile_data, png_uint_32 length); + + size_t concatenateString(char *destination, const char *source, const size_t length); + size_t copyString(char *destination, const char *source, const size_t length); + long formatString(char *string, const size_t length, const char *format,...); + long formatStringList(char *string, const size_t length, const char *format, va_list operands); + +private: + + bool m_sixteenBit; + bool m_hasAlpha; +}; + +} // NameSpace Digikam + +#endif /* PNGLOADER_H */ diff --git a/src/libs/dimg/loaders/pngsettings.cpp b/src/libs/dimg/loaders/pngsettings.cpp new file mode 100644 index 00000000..ce39219b --- /dev/null +++ b/src/libs/dimg/loaders/pngsettings.cpp @@ -0,0 +1,102 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save PNG image options. + * + * Copyright (C) 2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include + +// Local includes. + +#include "pngsettings.h" +#include "pngsettings.moc" + +namespace Digikam +{ + +class PNGSettingsPriv +{ + +public: + + PNGSettingsPriv() + { + PNGGrid = 0; + labelPNGcompression = 0; + PNGcompression = 0; + } + + TQGridLayout *PNGGrid; + + TQLabel *labelPNGcompression; + + KIntNumInput *PNGcompression; +}; + +PNGSettings::PNGSettings(TQWidget *parent) + : TQWidget(parent, 0, TQt::WDestructiveClose) +{ + d = new PNGSettingsPriv; + + d->PNGGrid = new TQGridLayout(this, 1, 1, KDialog::spacingHint()); + d->PNGcompression = new KIntNumInput(9, this); + d->PNGcompression->setRange(1, 9, 1, true ); + d->labelPNGcompression = new TQLabel(i18n("PNG compression:"), this); + + TQWhatsThis::add(d->PNGcompression, i18n("

The compression value for PNG images:

" + "1: low compression (large file size but " + "short compression duration - default)

" + "5: medium compression

" + "9: high compression (small file size but " + "long compression duration)

" + "Note: PNG is always a lossless image " + "compression format.")); + d->PNGGrid->addMultiCellWidget(d->labelPNGcompression, 0, 0, 0, 0); + d->PNGGrid->addMultiCellWidget(d->PNGcompression, 0, 0, 1, 1); + d->PNGGrid->setColStretch(1, 10); +} + +PNGSettings::~PNGSettings() +{ + delete d; +} + +void PNGSettings::setCompressionValue(int val) +{ + d->PNGcompression->setValue(val); +} + +int PNGSettings::getCompressionValue() +{ + return d->PNGcompression->value(); +} + +} // namespace Digikam diff --git a/src/libs/dimg/loaders/pngsettings.h b/src/libs/dimg/loaders/pngsettings.h new file mode 100644 index 00000000..aecca935 --- /dev/null +++ b/src/libs/dimg/loaders/pngsettings.h @@ -0,0 +1,60 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save PNG image options. + * + * Copyright (C) 2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef PNGSETTINGS_H +#define PNGSETTINGS_H + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class PNGSettingsPriv; + +class DIGIKAM_EXPORT PNGSettings : public TQWidget +{ +TQ_OBJECT + + +public: + + PNGSettings(TQWidget *parent=0); + ~PNGSettings(); + + void setCompressionValue(int val); + int getCompressionValue(); + +private: + + PNGSettingsPriv* d; +}; + +} // namespace Digikam + +#endif /* PNGSETTINGS_H */ diff --git a/src/libs/dimg/loaders/ppmloader.cpp b/src/libs/dimg/loaders/ppmloader.cpp new file mode 100644 index 00000000..15c19423 --- /dev/null +++ b/src/libs/dimg/loaders/ppmloader.cpp @@ -0,0 +1,178 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-21-11 + * Description : A 16 bits/color/pixel PPM IO file for + * DImg framework + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// This line must be commented to prevent any latency time +// when we use threaded image loader interface for each image +// files io. Uncomment this line only for debugging. +//#define ENABLE_DEBUG_MESSAGES + +// C ansi includes. + +extern "C" +{ +#include +} + +// C++ includes. + +#include +#include + +// TQt includes. + +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "ppmloader.h" + +namespace Digikam +{ + +PPMLoader::PPMLoader(DImg* image) + : DImgLoader(image) +{ +} + +bool PPMLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + //TODO: progress information + int width, height, rgbmax; + char nl; + + FILE *file = fopen(TQFile::encodeName(filePath), "rb"); + if (!file) + { + DDebug() << k_funcinfo << "Cannot open image file." << endl; + return false; + } + + ushort header; + + if (fread(&header, 2, 1, file) != 1) + { + DDebug() << k_funcinfo << "Cannot read header of file." << endl; + fclose(file); + return false; + } + + uchar* c = (uchar*) &header; + if (*c != 'P') + { + DDebug() << k_funcinfo << "Not a PPM file." << endl; + fclose(file); + return false; + } + + c++; + if (*c != '6') + { + DDebug() << k_funcinfo << "Not a PPM file." << endl; + fclose(file); + return false; + } + + rewind(file); + + if (fscanf (file, "P6 %d %d %d%c", &width, &height, &rgbmax, &nl) != 4) + { + DDebug() << "Corrupted PPM file." << endl; + pclose (file); + return false; + } + + if (rgbmax <= 255) + { + DDebug() << k_funcinfo << "Not a 16 bits per color per pixel PPM file." << endl; + pclose (file); + return false; + } + + if (observer) + observer->progressInfo(m_image, 0.1); + + unsigned short *data; + + data = new unsigned short[width*height*4]; + unsigned short *dst = data; + uchar src[6]; + float fac = 65535.0 / rgbmax; + int checkpoint = 0; + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << "rgbmax=" << rgbmax << " fac=" << fac << endl; +#endif + + for (int h = 0; h < height; h++) + { + + if (observer && h == checkpoint) + { + checkpoint += granularity(observer, height, 0.9); + if (!observer->continueQuery(m_image)) + { + delete [] data; + pclose( file ); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.9 * ( ((float)h)/((float)height) ))); + } + + for (int w = 0; w < width; w++) + { + + fread (src, 6 *sizeof(unsigned char), 1, file); + + dst[0] = (unsigned short)((src[4]*256 + src[5]) * fac); // Blue + dst[1] = (unsigned short)((src[2]*256 + src[3]) * fac); // Green + dst[2] = (unsigned short)((src[0]*256 + src[1]) * fac); // Red + dst[3] = 0xFFFF; + + dst += 4; + } + } + + fclose( file ); + + //---------------------------------------------------------- + + imageWidth() = width; + imageHeight() = height; + imageData() = (uchar*)data; + imageSetAttribute("format", "PPM"); + + return true; +} + +bool PPMLoader::save(const TQString& /*filePath*/, DImgLoaderObserver */*observer*/) +{ + return false; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/ppmloader.h b/src/libs/dimg/loaders/ppmloader.h new file mode 100644 index 00000000..283fdd26 --- /dev/null +++ b/src/libs/dimg/loaders/ppmloader.h @@ -0,0 +1,59 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-21-11 + * Description : A 16 bits/color/pixel PPM IO file for + * DImg framework + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2005-2006 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef PPMLOADER_H +#define PPMLOADER_H + +// Local includes. + +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ +class DImg; + +class DIGIKAM_EXPORT PPMLoader : public DImgLoader +{ +public: + + PPMLoader(DImg* image); + + bool load(const TQString& filePath, DImgLoaderObserver *observer); + bool save(const TQString& filePath, DImgLoaderObserver *observer); + + virtual bool hasAlpha() const { return false; }; + virtual bool sixteenBit() const { return true; }; + virtual bool isReadOnly() const { return true; }; + +private: + + bool m_alpha; + bool m_sixteenBit; +}; + +} // NameSpace Digikam + +#endif /* PPMLOADER_H */ diff --git a/src/libs/dimg/loaders/qimageloader.cpp b/src/libs/dimg/loaders/qimageloader.cpp new file mode 100644 index 00000000..f35335cf --- /dev/null +++ b/src/libs/dimg/loaders/qimageloader.cpp @@ -0,0 +1,126 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-14 + * Description : A TQImage loader for DImg framework. + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2006-2007 by Caulier Gilles + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "qimageloader.h" + +namespace Digikam +{ + +TQImageLoader::TQImageLoader(DImg* image) + : DImgLoader(image) +{ +} + +bool TQImageLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + // Loading is opaque to us. No support for stopping from observer, + // progress info are only pseudo values + TQImage image(filePath); + + if (observer) + observer->progressInfo(m_image, 0.9); + + if (image.isNull()) + { + DDebug() << "Cannot loading \"" << filePath << "\" using DImg::TQImageLoader!" << endl; + return false; + } + + m_hasAlpha = image.hasAlphaBuffer(); + TQImage target = image.convertDepth(32); + + uint w = target.width(); + uint h = target.height(); + uchar* data = new uchar[w*h*4]; + uint* sptr = (uint*)target.bits(); + uchar* dptr = data; + + for (uint i = 0 ; i < w*h ; i++) + { + dptr[0] = tqBlue(*sptr); + dptr[1] = tqGreen(*sptr); + dptr[2] = tqRed(*sptr); + dptr[3] = tqAlpha(*sptr); + + dptr += 4; + sptr++; + } + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageWidth() = w; + imageHeight() = h; + imageData() = data; + + // We considering that PNG is the most representative format of an image loaded by TQt + imageSetAttribute("format", "PNG"); + + return true; +} + +bool TQImageLoader::save(const TQString& filePath, DImgLoaderObserver *observer) +{ + TQVariant qualityAttr = imageGetAttribute("quality"); + int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; + + if (quality < 0) + quality = 90; + if (quality > 100) + quality = 100; + + TQVariant formatAttr = imageGetAttribute("format"); + TQCString format = formatAttr.toCString(); + + TQImage image = m_image->copyTQImage(); + + if (observer) + observer->progressInfo(m_image, 0.1); + + // Saving is opaque to us. No support for stopping from observer, + // progress info are only pseudo values + bool success = image.save(filePath, format.upper(), quality); + if (observer && success) + observer->progressInfo(m_image, 1.0); + + imageSetAttribute("format", format.upper()); + + return success; +} + +bool TQImageLoader::hasAlpha() const +{ + return m_hasAlpha; +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/qimageloader.h b/src/libs/dimg/loaders/qimageloader.h new file mode 100644 index 00000000..f81cf7ef --- /dev/null +++ b/src/libs/dimg/loaders/qimageloader.h @@ -0,0 +1,57 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-14 + * Description : A TQImage loader for DImg framework. + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2006-2007 by Caulier Gilles + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef TQIMAGELOADER_H +#define TQIMAGELOADER_H + +// Local includes. + +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ +class DImg; + +class DIGIKAM_EXPORT TQImageLoader : public DImgLoader +{ +public: + + TQImageLoader(DImg* image); + + virtual bool load(const TQString& filePath, DImgLoaderObserver *observer); + virtual bool save(const TQString& filePath, DImgLoaderObserver *observer); + + virtual bool hasAlpha() const; + virtual bool sixteenBit() const { return false; }; + virtual bool isReadOnly() const { return false; }; + +private: + + bool m_hasAlpha; +}; + +} // NameSpace Digikam + +#endif /* TQIMAGELOADER_H */ diff --git a/src/libs/dimg/loaders/rawloader.cpp b/src/libs/dimg/loaders/rawloader.cpp new file mode 100644 index 00000000..8ecaa1f3 --- /dev/null +++ b/src/libs/dimg/loaders/rawloader.cpp @@ -0,0 +1,371 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-11-01 + * Description : A digital camera RAW files loader for DImg + * framework using an external dcraw instance. + * + * Copyright (C) 2005-2008 by Gilles Caulier + * Copyright (C) 2005-2008 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// C++ includes. + +#include + +// TQt includes. + +#include + +// KDE includes. + +#include + +// Local includes. + +#include "ddebug.h" +#include "imagehistogram.h" +#include "imagecurves.h" +#include "imagelevels.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "bcgmodifier.h" +#include "whitebalance.h" +#include "rawloader.h" +#include "rawloader.moc" + +namespace Digikam +{ + +RAWLoader::RAWLoader(DImg* image, DRawDecoding rawDecodingSettings) + : DImgLoader(image) +{ + m_rawDecodingSettings = rawDecodingSettings; + m_customRawSettings = rawDecodingSettings; + m_observer = 0; +} + +bool RAWLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + m_observer = observer; + + // We are using TDEProcess here, and make two assumptions: + // - there is an event loop (not for ioslaves) + // - we are not called from the event loop thread + // These assumptions are currently true for all use cases in digikam, + // except the thumbnails iosalve, which will set this attribute. + // I hope when porting to TQt4, all the event loop stuff (and this problem) can be removed. + if (imageGetAttribute("noeventloop").isValid()) + return false; + + readMetadata(filePath, DImg::RAW); + + // NOTE: Here, we don't check a possible embedded work-space color profile using + // the method checkExifWorkingColorSpace() like with JPEG, PNG, and TIFF loaders, + // because RAW file are always in linear mode. + + int width, height, rgbmax; + TQByteArray data; + if (!KDcrawIface::KDcraw::decodeRAWImage(filePath, m_rawDecodingSettings, + data, width, height, rgbmax)) + return false; + + return (loadedFromDcraw(data, width, height, rgbmax, observer)); +} + +bool RAWLoader::checkToCancelWaitingData() +{ + return (m_observer ? !m_observer->continueQuery(m_image) : false); +} + +void RAWLoader::setWaitingDataProgress(double value) +{ + if (m_observer) + m_observer->progressInfo(m_image, value); +} + +#if KDCRAW_VERSION < 0x000106 +bool RAWLoader::checkToCancelRecievingData() +{ + return (m_observer ? m_observer->isShuttingDown() : false); +} + +void RAWLoader::setRecievingDataProgress(double value) +{ + if (m_observer) + m_observer->progressInfo(m_image, value); +} +#endif + +bool RAWLoader::loadedFromDcraw(TQByteArray data, int width, int height, int rgbmax, + DImgLoaderObserver *observer) +{ + int checkpoint = 0; + + if (m_rawDecodingSettings.sixteenBitsImage) // 16 bits image + { + uchar *image = new uchar[width*height*8]; + + unsigned short *dst = (unsigned short *)image; + uchar *src = (uchar*)data.data(); + float fac = 65535.0 / rgbmax; + checkpoint = 0; + + for (int h = 0; h < height; h++) + { + if (observer && h == checkpoint) + { + checkpoint += granularity(observer, height, 1.0); + if (!observer->continueQuery(m_image)) + { + return false; + } + observer->progressInfo(m_image, 0.7 + 0.2*(((float)h)/((float)height)) ); + } + + for (int w = 0; w < width; w++) + { +#if KDCRAW_VERSION < 0x000106 + dst[0] = (unsigned short)((src[4]*256 + src[5]) * fac); // Blue + dst[1] = (unsigned short)((src[2]*256 + src[3]) * fac); // Green + dst[2] = (unsigned short)((src[0]*256 + src[1]) * fac); // Red +#else + dst[0] = (unsigned short)((src[5]*256 + src[4]) * fac); // Blue + dst[1] = (unsigned short)((src[3]*256 + src[2]) * fac); // Green + dst[2] = (unsigned short)((src[1]*256 + src[0]) * fac); // Red +#endif + dst[3] = 0xFFFF; + + dst += 4; + src += 6; + } + } + + +#if KDCRAW_VERSION < 0x000106 + // ---------------------------------------------------------- + + // Special case : if Color Management is not used here, output color space is in sRGB* color space + // RAW decoded image is a linear-histogram image with 16 bits color depth. + // No auto white balance and no gamma adjustemnts are performed. Image is a black hole. + // We need to reproduce all dcraw 8 bits color depth adjustements here. + + if (m_rawDecodingSettings.outputColorSpace != DRawDecoding::RAWCOLOR) + { + ImageHistogram histogram(image, width, height, true); + + int perc, val, total; + float white=0.0, r, gamma=2.222222; + unsigned short lut[65536]; + + // Search 99th percentile white level. + + perc = (int)(width * height * 0.01); + DDebug() << "White Level: " << perc << endl; + for (int c = 1 ; c < 4 ; c++) + { + total = 0; + for (val = 65535 ; val > 256 ; --val) + if ((total += (int)histogram.getValue(c, val)) > perc) + break; + + if (white < val) white = (float)val; + } + + white *= 1.0 / m_rawDecodingSettings.brightness; + + DDebug() << "White Point: " << white << endl; + + // Compute the Gamma lut accordingly. + + for (int i=0; i < 65536; i++) + { + r = i / white; + val = (int)(65536.0 * (r <= 0.018 ? r*4.5 : pow(r, 1.0/gamma) * 1.099-0.099)); + if (val > 65535) val = 65535; + lut[i] = val; + } + + // Apply Gamma lut to the whole image. + + unsigned short *im = (unsigned short *)image; + for (int i = 0; i < width*height; i++) + { + im[0] = lut[im[0]]; // Blue + im[1] = lut[im[1]]; // Green + im[2] = lut[im[2]]; // Red + im += 4; + } + } +#endif + + // ---------------------------------------------------------- + + imageData() = (uchar *)image; + } + else // 8 bits image + { + uchar *image = new uchar[width*height*4]; + uchar *dst = image; + uchar *src = (uchar*)data.data(); + checkpoint = 0; + + for (int h = 0; h < height; h++) + { + + if (observer && h == checkpoint) + { + checkpoint += granularity(observer, height, 1.0); + if (!observer->continueQuery(m_image)) + { + return false; + } + observer->progressInfo(m_image, 0.7 + 0.2*(((float)h)/((float)height)) ); + } + + for (int w = 0; w < width; w++) + { + // No need to adapt RGB components accordinly with rgbmax value because dcraw + // always return rgbmax to 255 in 8 bits/color/pixels. + + dst[0] = src[2]; // Blue + dst[1] = src[1]; // Green + dst[2] = src[0]; // Red + dst[3] = 0xFF; // Alpha + + dst += 4; + src += 3; + } + } + + // NOTE: if Color Management is not used here, output color space is in sRGB* color space. + // Gamma and White balance are previously adjusted by dcraw in 8 bits color depth. + + imageData() = image; + } + + //---------------------------------------------------------- + // Assign the right color-space profile. + + TDEGlobal::dirs()->addResourceType("profiles", TDEGlobal::dirs()->kde_default("data") + "digikam/profiles"); + switch(m_rawDecodingSettings.outputColorSpace) + { + case DRawDecoding::SRGB: + { + TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "srgb.icm"); + m_image->getICCProfilFromFile(directory + "srgb.icm"); + break; + } + case DRawDecoding::ADOBERGB: + { + TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "adobergb.icm"); + m_image->getICCProfilFromFile(directory + "adobergb.icm"); + break; + } + case DRawDecoding::WIDEGAMMUT: + { + TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "widegamut.icm"); + m_image->getICCProfilFromFile(directory + "widegamut.icm"); + break; + } + case DRawDecoding::PROPHOTO: + { + TQString directory = TDEGlobal::dirs()->findResourceDir("profiles", "prophoto.icm"); + m_image->getICCProfilFromFile(directory + "prophoto.icm"); + break; + } + default: + // No icc color-space profile to assign in RAW color mode. + break; + } + + //---------------------------------------------------------- + + + imageWidth() = width; + imageHeight() = height; + imageSetAttribute("format", "RAW"); + + postProcessing(observer); + + return true; +} + +void RAWLoader::postProcessing(DImgLoaderObserver *observer) +{ + if (!m_customRawSettings.postProcessingSettingsIsDirty()) + return; + + if (m_customRawSettings.exposureComp != 0.0 || m_customRawSettings.saturation != 1.0) + { + WhiteBalance wb(m_rawDecodingSettings.sixteenBitsImage); + wb.whiteBalance(imageData(), imageWidth(), imageHeight(), m_rawDecodingSettings.sixteenBitsImage, + 0.0, // black + m_customRawSettings.exposureComp, // exposure + 6500.0, // temperature (neutral) + 1.0, // green + 0.5, // dark + 1.0, // gamma + m_customRawSettings.saturation); // saturation + } + if (observer) observer->progressInfo(m_image, 0.92); + + if (m_customRawSettings.lightness != 0.0 || + m_customRawSettings.contrast != 1.0 || + m_customRawSettings.gamma != 1.0) + { + BCGModifier bcg; + bcg.setBrightness(m_customRawSettings.lightness); + bcg.setContrast(m_customRawSettings.contrast); + bcg.setGamma(m_customRawSettings.gamma); + bcg.applyBCG(imageData(), imageWidth(), imageHeight(), m_rawDecodingSettings.sixteenBitsImage); + } + if (observer) observer->progressInfo(m_image, 0.94); + + if (!m_customRawSettings.curveAdjust.isEmpty()) + { + DImg tmp(imageWidth(), imageHeight(), m_rawDecodingSettings.sixteenBitsImage); + ImageCurves curves(m_rawDecodingSettings.sixteenBitsImage); + curves.setCurvePoints(ImageHistogram::ValueChannel, m_customRawSettings.curveAdjust); + curves.curvesCalculateCurve(ImageHistogram::ValueChannel); + curves.curvesLutSetup(ImageHistogram::AlphaChannel); + curves.curvesLutProcess(imageData(), tmp.bits(), imageWidth(), imageHeight()); + memcpy(imageData(), tmp.bits(), tmp.numBytes()); + } + if (observer) observer->progressInfo(m_image, 0.96); + + if (!m_customRawSettings.levelsAdjust.isEmpty()) + { + DImg tmp(imageWidth(), imageHeight(), m_rawDecodingSettings.sixteenBitsImage); + ImageLevels levels(m_rawDecodingSettings.sixteenBitsImage); + int j=0; + for (int i = 0 ; i < 4; i++) + { + levels.setLevelLowInputValue(i, m_customRawSettings.levelsAdjust[j++]); + levels.setLevelHighInputValue(i, m_customRawSettings.levelsAdjust[j++]); + levels.setLevelLowOutputValue(i, m_customRawSettings.levelsAdjust[j++]); + levels.setLevelHighOutputValue(i, m_customRawSettings.levelsAdjust[j++]); + } + + levels.levelsLutSetup(ImageHistogram::AlphaChannel); + levels.levelsLutProcess(imageData(), tmp.bits(), imageWidth(), imageHeight()); + memcpy(imageData(), tmp.bits(), tmp.numBytes()); + } + if (observer) observer->progressInfo(m_image, 0.98); +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/rawloader.h b/src/libs/dimg/loaders/rawloader.h new file mode 100644 index 00000000..22171a20 --- /dev/null +++ b/src/libs/dimg/loaders/rawloader.h @@ -0,0 +1,86 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-11-01 + * Description : A digital camera RAW files loader for DImg + * framework using an external dcraw instance. + * + * Copyright (C) 2005-2008 by Gilles Caulier + * Copyright (C) 2005-2008 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef RAWLOADER_H +#define RAWLOADER_H + +// LibKDcraw includes. + +#include +#include + +// Local includes. + +#include "drawdecoding.h" +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ +class DImg; + +class DIGIKAM_EXPORT RAWLoader : public KDcrawIface::KDcraw, public DImgLoader +{ + TQ_OBJECT + + +public: + + RAWLoader(DImg* image, DRawDecoding rawDecodingSettings=DRawDecoding()); + + bool load(const TQString& filePath, DImgLoaderObserver *observer=0); + + // NOTE: RAW files are always Read only. + bool save(const TQString& /*filePath*/, DImgLoaderObserver */*observer=0*/) { return false; }; + + bool hasAlpha() const { return false; }; + bool isReadOnly() const { return true; }; + bool sixteenBit() const { return m_rawDecodingSettings.sixteenBitsImage; }; + +private: + + // Methods to load RAW image using external dcraw instance. + + bool loadedFromDcraw(TQByteArray data, int width, int height, int rgbmax, + DImgLoaderObserver *observer); + + bool checkToCancelWaitingData(); + void setWaitingDataProgress(double value); + void postProcessing(DImgLoaderObserver *observer); + +#if KDCRAW_VERSION < 0x000106 + bool checkToCancelRecievingData(); + void setRecievingDataProgress(double value); +#endif + +private: + + DImgLoaderObserver *m_observer; + DRawDecoding m_customRawSettings; +}; + +} // NameSpace Digikam + +#endif /* RAWLOADER_H */ diff --git a/src/libs/dimg/loaders/tiffloader.cpp b/src/libs/dimg/loaders/tiffloader.cpp new file mode 100644 index 00000000..2e554143 --- /dev/null +++ b/src/libs/dimg/loaders/tiffloader.cpp @@ -0,0 +1,806 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-17 + * Description : A TIFF IO file for DImg framework + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2006-2008 by Gilles Caulier + * + * Specifications & references: + * - TIFF 6.0 : http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf + * - TIFF/EP : http://www.map.tu.chiba-u.ac.jp/IEC/100/TA2/recdoc/N4378.pdf + * - TIFF/Tags : http://www.awaresystems.be/imaging/tiff/tifftags.html + * - DNG : http://www.adobe.com/products/dng/pdfs/dng_spec.pdf + * + * Others Linux Tiff Loader implementation using libtiff: + * - http://websvn.kde.org/trunk/koffice/filters/krita/tiff/kis_tiff_converter.cc + * - http://artis.inrialpes.fr/Software/TiffIO/ + * - http://cvs.graphicsmagick.org/cgi-bin/cvsweb.cgi/GraphicsMagick/coders/tiff.c + * - http://freeimage.cvs.sourceforge.net/freeimage/FreeImage/Source/FreeImage/PluginTIFF.cpp + * - http://freeimage.cvs.sourceforge.net/freeimage/FreeImage/Source/Metadata/XTIFF.cpp + * - https://subversion.imagemagick.org/subversion/ImageMagick/trunk/coders/tiff.c + * + * Test images repository: + * - http://www.remotesensing.org/libtiff/images.html + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// This line must be commented to prevent any latency time +// when we use threaded image loader interface for each image +// files io. Uncomment this line only for debugging. +//#define ENABLE_DEBUG_MESSAGES + +// C ANSI includes. + +extern "C" +{ +#include +} + +// C++ includes. + +#include + +// TQt includes. + +#include + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "dmetadata.h" +#include "tiffloader.h" + +namespace Digikam +{ + +// To manage Errors/Warnings handling provide by libtiff + +void TIFFLoader::dimg_tiff_warning(const char* module, const char* format, va_list warnings) +{ +#ifdef ENABLE_DEBUG_MESSAGES + char message[4096]; + vsnprintf(message, 4096, format, warnings); + DDebug() << module << "::" << message << endl; +#else + Q_UNUSED(module); + Q_UNUSED(format); + Q_UNUSED(warnings); +#endif +} + +void TIFFLoader::dimg_tiff_error(const char* module, const char* format, va_list errors) +{ +#ifdef ENABLE_DEBUG_MESSAGES + char message[4096]; + vsnprintf(message, 4096, format, errors); + DDebug() << module << "::" << message << endl; +#else + Q_UNUSED(module); + Q_UNUSED(format); + Q_UNUSED(errors); +#endif +} + +TIFFLoader::TIFFLoader(DImg* image) + : DImgLoader(image) +{ + m_hasAlpha = false; + m_sixteenBit = false; +} + +bool TIFFLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + readMetadata(filePath, DImg::TIFF); + + // ------------------------------------------------------------------- + // TIFF error handling. If an errors/warnings occurs during reading, + // libtiff will call these methods + + TIFFSetWarningHandler(dimg_tiff_warning); + TIFFSetErrorHandler(dimg_tiff_error); + + // ------------------------------------------------------------------- + // Open the file + + TIFF* tif = TIFFOpen(TQFile::encodeName(filePath), "r"); + if (!tif) + { + DDebug() << k_funcinfo << "Cannot open image file." << endl; + return false; + } + +#ifdef ENABLE_DEBUG_MESSAGES + TIFFPrintDirectory(tif, stdout, 0); +#endif + + // ------------------------------------------------------------------- + // Get image information. + + uint32 w, h; + uint16 bits_per_sample; + uint16 samples_per_pixel; + uint16 photometric; + uint32 rows_per_strip; + tsize_t strip_size; + tstrip_t num_of_strips; + + TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGEWIDTH, &w); + TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGELENGTH, &h); + + TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); + TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel); + + if (TIFFGetFieldDefaulted(tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip) == 0 || + rows_per_strip == 0 || rows_per_strip == (unsigned int)-1) + { + DWarning() << "TIFF loader: Cannot handle non-stripped images. Loading file " << filePath << endl; + TIFFClose(tif); + return false; + } + + if (bits_per_sample == 0 || samples_per_pixel == 0 || + rows_per_strip == 0 || rows_per_strip > h) + { + DWarning() << "TIFF loader: Encountered invalid value 0 in image." + << " bits_per_sample " << bits_per_sample + << " samples_per_pixel " << samples_per_pixel + << " rows_per_strip " << rows_per_strip + << " Loading file " << filePath << endl; + TIFFClose(tif); + return false; + } + + // TODO: check others TIFF color-spaces here. Actually, only RGB and MINISBLACK + // have been tested. + // Complete description of TIFFTAG_PHOTOMETRIC tag can be found at this url: + // http://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html + + TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric); + if (photometric != PHOTOMETRIC_RGB && + photometric != PHOTOMETRIC_MINISBLACK) + { + DWarning() << "Can't handle image without RGB color-space: " + << photometric << endl; + TIFFClose(tif); + return false; + } + + if (samples_per_pixel == 4) + m_hasAlpha = true; + else + m_hasAlpha = false; + + if (bits_per_sample == 16) + m_sixteenBit = true; + else + m_sixteenBit = false; + + // ------------------------------------------------------------------- + // Read image ICC profile + + TQMap& metaData = imageMetaData(); + + uchar *profile_data=0; + uint32 profile_size; + + if (TIFFGetField (tif, TIFFTAG_ICCPROFILE, &profile_size, &profile_data)) + { + TQByteArray profile_rawdata(profile_size); + memcpy(profile_rawdata.data(), profile_data, profile_size); + metaData.insert(DImg::ICC, profile_rawdata); + } + else + { + // If ICC profile is null, check Exif metadata. + checkExifWorkingColorSpace(); + } + + // ------------------------------------------------------------------- + // Get image data. + + if (observer) + observer->progressInfo(m_image, 0.1); + + uchar* data = 0; + + strip_size = TIFFStripSize(tif); + num_of_strips = TIFFNumberOfStrips(tif); + + if (bits_per_sample == 16) // 16 bits image. + { + data = new uchar[w*h*8]; + uchar* strip = new uchar[strip_size]; + long offset = 0; + long bytesRead = 0; + + uint checkpoint = 0; + + for (tstrip_t st=0; st < num_of_strips; st++) + { + if (observer && st == checkpoint) + { + checkpoint += granularity(observer, num_of_strips, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] strip; + TIFFClose(tif); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)st)/((float)num_of_strips) ))); + } + + bytesRead = TIFFReadEncodedStrip(tif, st, strip, strip_size); + + if (bytesRead == -1) + { + DDebug() << k_funcinfo << "Failed to read strip" << endl; + delete [] data; + TIFFClose(tif); + return false; + } + + ushort *stripPtr = (ushort*)(strip); + ushort *dataPtr = (ushort*)(data + offset); + ushort *p; + + // tiff data is read as BGR or ABGR or Greyscale + + if (samples_per_pixel == 3) + { + for (int i=0; i < bytesRead/6; i++) + { + p = dataPtr; + + // See B.K.O #148037 : take a care about byte order with Motorola computers. + if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC + { + p[3] = *stripPtr++; + p[0] = *stripPtr++; + p[1] = *stripPtr++; + p[2] = 0xFFFF; + } + else + { + p[2] = *stripPtr++; + p[1] = *stripPtr++; + p[0] = *stripPtr++; + p[3] = 0xFFFF; + } + + dataPtr += 4; + } + + offset += bytesRead/6 * 8; + } + else if (samples_per_pixel == 1) // See B.K.O #148400: Greyscale pictures only have _one_ sample per pixel + { + for (int i=0; i < bytesRead/2; i++) + { + // We have to read two bytes for one pixel + p = dataPtr; + + // See B.K.O #148037 : take a care about byte order with Motorola computers. + if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC + { + p[3] = 0xFFFF; + p[0] = *stripPtr; + p[1] = *stripPtr; + p[2] = *stripPtr++; + } + else + { + p[0] = *stripPtr; // RGB have to be set to the _same_ value + p[1] = *stripPtr; + p[2] = *stripPtr++; + p[3] = 0xFFFF; // set alpha to 100% + } + dataPtr += 4; + } + + offset += bytesRead*4; // The _byte_offset in the data array is, of course, four times bytesRead + } + else // ABGR + { + for (int i=0; i < bytesRead/8; i++) + { + p = dataPtr; + + // See B.K.O #148037 : take a care about byte order with Motorola computers. + if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC + { + p[3] = *stripPtr++; + p[0] = *stripPtr++; + p[1] = *stripPtr++; + p[2] = *stripPtr++; + } + else + { + p[2] = *stripPtr++; + p[1] = *stripPtr++; + p[0] = *stripPtr++; + p[3] = *stripPtr++; + } + + dataPtr += 4; + } + + offset += bytesRead; + } + } + + delete [] strip; + } + else // Non 16 bits images ==> get it on BGRA 8 bits. + { + data = new uchar[w*h*4]; + uchar* strip = new uchar[w*rows_per_strip*4]; + long offset = 0; + long pixelsRead = 0; + + // this is inspired by TIFFReadRGBAStrip, tif_getimage.c + char emsg[1024] = ""; + TIFFRGBAImage img; + uint32 rows_to_read; + + uint checkpoint = 0; + + // test whether libtiff can read format and initiate reading + + if (!TIFFRGBAImageOK(tif, emsg) || !TIFFRGBAImageBegin(&img, tif, 0, emsg)) + { + DDebug() << k_funcinfo << "Failed to set up RGBA reading of image, filename " + << TIFFFileName(tif) << " error message from Libtiff: " << emsg << endl; + delete [] data; + delete [] strip; + TIFFClose(tif); + return false; + } + + img.req_orientation = ORIENTATION_TOPLEFT; + + // read strips from image: read rows_per_strip, so always start at beginning of a strip + for (uint row = 0; row < h; row += rows_per_strip) + { + if (observer && row >= checkpoint) + { + checkpoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] strip; + TIFFClose(tif); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)row)/((float)h) ))); + } + + img.row_offset = row; + img.col_offset = 0; + + if( row + rows_per_strip > img.height ) + rows_to_read = img.height - row; + else + rows_to_read = rows_per_strip; + + // Read data + + if (TIFFRGBAImageGet(&img, (uint32*)strip, img.width, rows_to_read ) == -1) + { + DDebug() << k_funcinfo << "Failed to read image data" << endl; + delete [] data; + delete [] strip; + TIFFClose(tif); + return false; + } + + pixelsRead = rows_to_read * img.width; + + uchar *stripPtr = (uchar*)(strip); + uchar *dataPtr = (uchar*)(data + offset); + uchar *p; + + // Reverse red and blue + + for (int i=0; i < pixelsRead; i++) + { + p = dataPtr; + + // See B.K.O #148037 : take a care about byte order with Motorola computers. + if (TQImage::systemByteOrder() == TQImage::BigEndian) // PPC + { + p[3] = *stripPtr++; + p[0] = *stripPtr++; + p[1] = *stripPtr++; + p[2] = *stripPtr++; + } + else + { + p[2] = *stripPtr++; + p[1] = *stripPtr++; + p[0] = *stripPtr++; + p[3] = *stripPtr++; + } + + dataPtr += 4; + } + + offset += pixelsRead * 4; + } + + TIFFRGBAImageEnd(&img); + delete [] strip; + } + + // ------------------------------------------------------------------- + + TIFFClose(tif); + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageWidth() = w; + imageHeight() = h; + imageData() = data; + imageSetAttribute("format", "TIFF"); + + return true; +} + +bool TIFFLoader::save(const TQString& filePath, DImgLoaderObserver *observer) +{ + TIFF *tif; + uchar *data; + uint32 w, h; + + w = imageWidth(); + h = imageHeight(); + data = imageData(); + + // ------------------------------------------------------------------- + // TIFF error handling. If an errors/warnings occurs during reading, + // libtiff will call these methods + + TIFFSetWarningHandler(dimg_tiff_warning); + TIFFSetErrorHandler(dimg_tiff_error); + + // ------------------------------------------------------------------- + // Open the file + + tif = TIFFOpen(TQFile::encodeName(filePath), "w"); + + if (!tif) + { + DDebug() << k_funcinfo << "Cannot open target image file." << endl; + return false; + } + + // ------------------------------------------------------------------- + // Set image properties + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, w); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, h); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE); + + // Image must be compressed using deflate algorithm ? + TQVariant compressAttr = imageGetAttribute("compress"); + bool compress = compressAttr.isValid() ? compressAttr.toBool() : false; + + if (compress) + { + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_ADOBE_DEFLATE); + TIFFSetField(tif, TIFFTAG_ZIPQUALITY, 9); + // NOTE : this tag values aren't defined in libtiff 3.6.1. '2' is PREDICTOR_HORIZONTAL. + // Use horizontal differencing for images which are + // likely to be continuous tone. The TIFF spec says that this + // usually leads to better compression. + // See this url for more details: + // http://www.awaresystems.be/imaging/tiff/tifftags/predictor.html + TIFFSetField(tif, TIFFTAG_PREDICTOR, 2); + } + else + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + + // Image has an alpha channel ? + if (imageHasAlpha()) + { + uint16 sampleinfo[1] = { EXTRASAMPLE_UNASSALPHA }; + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 4); + TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, EXTRASAMPLE_ASSOCALPHA, sampleinfo); + } + else + { + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + } + + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, (uint16)imageBitsDepth()); + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0)); + + // ------------------------------------------------------------------- + // Write meta-data Tags contents. + + DMetadata metaData; + metaData.setExif(m_image->getExif()); + metaData.setIptc(m_image->getIptc()); + + // Standard IPTC tag (available with libtiff 3.6.1) + + TQByteArray ba = metaData.getIptc(true); + if (!ba.isEmpty()) + { +#if defined(TIFFTAG_PHOTOSHOP) + TIFFSetField (tif, TIFFTAG_PHOTOSHOP, (uint32)ba.size(), (uchar *)ba.data()); +#endif + } + + // Standard XMP tag (available with libtiff 3.6.1) + +#if defined(TIFFTAG_XMLPACKET) + tiffSetExifDataTag(tif, TIFFTAG_XMLPACKET, &metaData, "Exif.Image.XMLPacket"); +#endif + + // Standard Exif Ascii tags (available with libtiff 3.6.1) + + tiffSetExifAsciiTag(tif, TIFFTAG_DOCUMENTNAME, &metaData, "Exif.Image.DocumentName"); + tiffSetExifAsciiTag(tif, TIFFTAG_IMAGEDESCRIPTION, &metaData, "Exif.Image.ImageDescription"); + tiffSetExifAsciiTag(tif, TIFFTAG_MAKE, &metaData, "Exif.Image.Make"); + tiffSetExifAsciiTag(tif, TIFFTAG_MODEL, &metaData, "Exif.Image.Model"); + tiffSetExifAsciiTag(tif, TIFFTAG_DATETIME, &metaData, "Exif.Image.DateTime"); + tiffSetExifAsciiTag(tif, TIFFTAG_ARTIST, &metaData, "Exif.Image.Artist"); + tiffSetExifAsciiTag(tif, TIFFTAG_COPYRIGHT, &metaData, "Exif.Image.Copyright"); + + TQString soft = metaData.getExifTagString("Exif.Image.Software"); + TQString libtiffver(TIFFLIB_VERSION_STR); + libtiffver.replace('\n', ' '); + soft.append(TQString(" ( %1 )").arg(libtiffver)); + TIFFSetField(tif, TIFFTAG_SOFTWARE, (const char*)soft.ascii()); + + // NOTE: All others Exif tags will be written by Exiv2 (<= 0.18) + + // ------------------------------------------------------------------- + // Write ICC profil. + + TQByteArray profile_rawdata = m_image->getICCProfil(); + + if (!profile_rawdata.isEmpty()) + { +#if defined(TIFFTAG_ICCPROFILE) + TIFFSetField(tif, TIFFTAG_ICCPROFILE, (uint32)profile_rawdata.size(), (uchar *)profile_rawdata.data()); +#endif + } + + // ------------------------------------------------------------------- + // Write full image data in tiff directory IFD0 + + if (observer) + observer->progressInfo(m_image, 0.1); + + uint8 *buf=0; + uchar *pixel; + double alpha_factor; + uint32 x, y; + uint8 r8, g8, b8, a8=0; + uint16 r16, g16, b16, a16=0; + int i=0; + + buf = (uint8 *) _TIFFmalloc(TIFFScanlineSize(tif)); + + if (!buf) + { + DDebug() << k_funcinfo << "Cannot allocate memory buffer for main image." << endl; + TIFFClose(tif); + return false; + } + + uint checkpoint = 0; + + for (y = 0; y < h; y++) + { + + if (observer && y == checkpoint) + { + checkpoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + _TIFFfree(buf); + TIFFClose(tif); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)y)/((float)h) ))); + } + + i = 0; + + for (x = 0; x < w; x++) + { + pixel = &data[((y * w) + x) * imageBytesDepth()]; + + if ( imageSixteenBit() ) // 16 bits image. + { + b16 = (uint16)(pixel[0]+256*pixel[1]); + g16 = (uint16)(pixel[2]+256*pixel[3]); + r16 = (uint16)(pixel[4]+256*pixel[5]); + + if (imageHasAlpha()) + { + // TIFF makes you pre-mutiply the rgb components by alpha + + a16 = (uint16)(pixel[6]+256*pixel[7]); + alpha_factor = ((double)a16 / 65535.0); + r16 = (uint16)(r16*alpha_factor); + g16 = (uint16)(g16*alpha_factor); + b16 = (uint16)(b16*alpha_factor); + } + + // This might be endian dependent + + buf[i++] = (uint8)(r16); + buf[i++] = (uint8)(r16 >> 8); + buf[i++] = (uint8)(g16); + buf[i++] = (uint8)(g16 >> 8); + buf[i++] = (uint8)(b16); + buf[i++] = (uint8)(b16 >> 8); + + if (imageHasAlpha()) + { + buf[i++] = (uint8)(a16) ; + buf[i++] = (uint8)(a16 >> 8) ; + } + } + else // 8 bits image. + { + b8 = (uint8)pixel[0]; + g8 = (uint8)pixel[1]; + r8 = (uint8)pixel[2]; + + if (imageHasAlpha()) + { + // TIFF makes you pre-mutiply the rgb components by alpha + + a8 = (uint8)(pixel[3]); + alpha_factor = ((double)a8 / 255.0); + r8 = (uint8)(r8*alpha_factor); + g8 = (uint8)(g8*alpha_factor); + b8 = (uint8)(b8*alpha_factor); + } + + // This might be endian dependent + + buf[i++] = r8; + buf[i++] = g8; + buf[i++] = b8; + + if (imageHasAlpha()) + buf[i++] = a8; + } + } + + if (!TIFFWriteScanline(tif, buf, y, 0)) + { + DDebug() << k_funcinfo << "Cannot write main image to target file." << endl; + _TIFFfree(buf); + TIFFClose(tif); + return false; + } + } + + _TIFFfree(buf); + TIFFWriteDirectory(tif); + + // ------------------------------------------------------------------- + // Write thumbnail in tiff directory IFD1 + + TQImage thumb = m_image->smoothScale(160, 120, TQSize::ScaleMin).copyTQImage(); + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, (uint32)thumb.width()); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, (uint32)thumb.height()); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0)); + + uchar *pixelThumb; + uchar *dataThumb = thumb.bits(); + uint8 *bufThumb = (uint8 *) _TIFFmalloc(TIFFScanlineSize(tif)); + + if (!bufThumb) + { + DDebug() << k_funcinfo << "Cannot allocate memory buffer for thumbnail." << endl; + TIFFClose(tif); + return false; + } + + for (y = 0 ; y < uint32(thumb.height()) ; y++) + { + i = 0; + + for (x = 0 ; x < uint32(thumb.width()) ; x++) + { + pixelThumb = &dataThumb[((y * thumb.width()) + x) * 4]; + + // This might be endian dependent + bufThumb[i++] = (uint8)pixelThumb[2]; + bufThumb[i++] = (uint8)pixelThumb[1]; + bufThumb[i++] = (uint8)pixelThumb[0]; + } + + if (!TIFFWriteScanline(tif, bufThumb, y, 0)) + { + DDebug() << k_funcinfo << "Cannot write thumbnail to target file." << endl; + _TIFFfree(bufThumb); + TIFFClose(tif); + return false; + } + } + + _TIFFfree(bufThumb); + TIFFClose(tif); + + // ------------------------------------------------------------------- + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageSetAttribute("savedformat", "TIFF"); + + saveMetadata(filePath); + + return true; +} + +bool TIFFLoader::hasAlpha() const +{ + return m_hasAlpha; +} + +bool TIFFLoader::sixteenBit() const +{ + return m_sixteenBit; +} + +void TIFFLoader::tiffSetExifAsciiTag(TIFF* tif, ttag_t tiffTag, + const DMetadata *metaData, const char* exifTagName) +{ + TQByteArray tag = metaData->getExifTagData(exifTagName); + if (!tag.isEmpty()) + { + TQCString str(tag.data(), tag.size()); + TIFFSetField(tif, tiffTag, (const char*)str); + } +} + +void TIFFLoader::tiffSetExifDataTag(TIFF* tif, ttag_t tiffTag, + const DMetadata *metaData, const char* exifTagName) +{ + TQByteArray tag = metaData->getExifTagData(exifTagName); + if (!tag.isEmpty()) + { + TIFFSetField (tif, tiffTag, (uint32)tag.size(), (char *)tag.data()); + } +} + +} // NameSpace Digikam diff --git a/src/libs/dimg/loaders/tiffloader.h b/src/libs/dimg/loaders/tiffloader.h new file mode 100644 index 00000000..484afd06 --- /dev/null +++ b/src/libs/dimg/loaders/tiffloader.h @@ -0,0 +1,76 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-17 + * Description : A TIFF IO file for DImg framework + * + * Copyright (C) 2005 by Renchi Raju + * Copyright (C) 2006-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef TIFFLOADER_H +#define TIFFLOADER_H + +// C ansi includes. + +extern "C" +{ +#include +#include +} + +// Local includes. + +#include "dimgloader.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class DImg; +class DMetadata; + +class DIGIKAM_EXPORT TIFFLoader : public DImgLoader +{ +public: + + TIFFLoader(DImg* image); + + bool load(const TQString& filePath, DImgLoaderObserver *observer); + bool save(const TQString& filePath, DImgLoaderObserver *observer); + + virtual bool hasAlpha() const; + virtual bool sixteenBit() const; + virtual bool isReadOnly() const { return false; }; + +private: + + void tiffSetExifAsciiTag(TIFF* tif, ttag_t tiffTag, const DMetadata *metaData, const char* exifTagName); + void tiffSetExifDataTag(TIFF* tif, ttag_t tiffTag, const DMetadata *metaData, const char* exifTagName); + + static void dimg_tiff_warning(const char* module, const char* format, va_list warnings); + static void dimg_tiff_error(const char* module, const char* format, va_list errors); + +private: + + bool m_sixteenBit; + bool m_hasAlpha; +}; + +} // NameSpace Digikam + +#endif /* TIFFLOADER_H */ diff --git a/src/libs/dimg/loaders/tiffsettings.cpp b/src/libs/dimg/loaders/tiffsettings.cpp new file mode 100644 index 00000000..3ea7e20c --- /dev/null +++ b/src/libs/dimg/loaders/tiffsettings.cpp @@ -0,0 +1,94 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save TIFF image options. + * + * Copyright (C) 2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include + +// Local includes. + +#include "tiffsettings.h" +#include "tiffsettings.moc" + +namespace Digikam +{ + +class TIFFSettingsPriv +{ + +public: + + TIFFSettingsPriv() + { + TIFFGrid = 0; + TIFFcompression = 0; + } + + TQGridLayout *TIFFGrid; + + TQCheckBox *TIFFcompression; +}; + +TIFFSettings::TIFFSettings(TQWidget *parent) + : TQWidget(parent, 0, TQt::WDestructiveClose) +{ + d = new TIFFSettingsPriv; + + d->TIFFGrid = new TQGridLayout(this, 1, 1, KDialog::spacingHint()); + d->TIFFcompression = new TQCheckBox(i18n("Compress TIFF files"), this); + + TQWhatsThis::add( d->TIFFcompression, i18n("

Toggle compression for TIFF images.

" + "If you enable this option, you can reduce " + "the final file size of the TIFF image.

" + "

A lossless compression format (Deflate) " + "is used to save the file.

")); + d->TIFFGrid->addMultiCellWidget(d->TIFFcompression, 0, 0, 0, 1); + d->TIFFGrid->setColStretch(1, 10); +} + +TIFFSettings::~TIFFSettings() +{ + delete d; +} + +void TIFFSettings::setCompression(bool b) +{ + d->TIFFcompression->setChecked(b); +} + +bool TIFFSettings::getCompression() +{ + return d->TIFFcompression->isChecked(); +} + +} // namespace Digikam + diff --git a/src/libs/dimg/loaders/tiffsettings.h b/src/libs/dimg/loaders/tiffsettings.h new file mode 100644 index 00000000..e546ce0f --- /dev/null +++ b/src/libs/dimg/loaders/tiffsettings.h @@ -0,0 +1,60 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-08-02 + * Description : save TIFF image options. + * + * Copyright (C) 2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef TIFFSETTINGS_H +#define TIFFSETTINGS_H + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class TIFFSettingsPriv; + +class DIGIKAM_EXPORT TIFFSettings : public TQWidget +{ +TQ_OBJECT + + +public: + + TIFFSettings(TQWidget *parent=0); + ~TIFFSettings(); + + void setCompression(bool b); + bool getCompression(); + +private: + + TIFFSettingsPriv* d; +}; + +} // namespace Digikam + +#endif /* TIFFSETTINGS_H */ diff --git a/src/libs/dmetadata/Makefile.am b/src/libs/dmetadata/Makefile.am new file mode 100644 index 00000000..73ff6010 --- /dev/null +++ b/src/libs/dmetadata/Makefile.am @@ -0,0 +1,18 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libdmetadata.la + +libdmetadata_la_SOURCES = dmetadata.cpp + +libdmetadata_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +libdmetadata_la_LIBADD = $(LIBKEXIV2_LIBS) $(LIBKDCRAW_LIBS) + +INCLUDES = -I$(top_srcdir)/src/libs/dimg \ + -I$(top_srcdir)/src/digikam \ + $(LIBKEXIV2_CFLAGS) \ + $(LIBKDCRAW_CFLAGS) \ + $(all_includes) + +digikaminclude_HEADERS = dmetadata.h photoinfocontainer.h +digikamincludedir = $(includedir)/digikam diff --git a/src/libs/dmetadata/dmetadata.cpp b/src/libs/dmetadata/dmetadata.cpp new file mode 100644 index 00000000..bb37506c --- /dev/null +++ b/src/libs/dmetadata/dmetadata.cpp @@ -0,0 +1,544 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-02-23 + * Description : image metadata interface + * + * Copyright (C) 2006-2007 by Gilles Caulier + * Copyright (C) 2006-2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include + +// LibKDcraw includes. + +#include +#include + +// Local includes. + +#include "daboutdata.h" +#include "constants.h" +#include "ddebug.h" +#include "dmetadata.h" + +namespace Digikam +{ + +DMetadata::DMetadata() + : KExiv2Iface::KExiv2() +{ +} + +DMetadata::DMetadata(const TQString& filePath) + : KExiv2Iface::KExiv2() +{ + load(filePath); +} + +DMetadata::~DMetadata() +{ +} + +bool DMetadata::load(const TQString& filePath) +{ + // In first, we trying to get metadata using Exiv2, + // else we will use dcraw to extract minimal information. + + if (!KExiv2::load(filePath)) + { + if (!loadUsingDcraw(filePath)) + return false; + } + + return true; +} + +bool DMetadata::loadUsingDcraw(const TQString& filePath) +{ + KDcrawIface::DcrawInfoContainer identify; + if (KDcrawIface::KDcraw::rawFileIdentify(identify, filePath)) + { + long int num=1, den=1; + + if (!identify.model.isNull()) + setExifTagString("Exif.Image.Model", identify.model.latin1(), false); + + if (!identify.make.isNull()) + setExifTagString("Exif.Image.Make", identify.make.latin1(), false); + + if (!identify.owner.isNull()) + setExifTagString("Exif.Image.Artist", identify.owner.latin1(), false); + + if (identify.sensitivity != -1) + setExifTagLong("Exif.Photo.ISOSpeedRatings", identify.sensitivity, false); + + if (identify.dateTime.isValid()) + setImageDateTime(identify.dateTime, false, false); + + if (identify.exposureTime != -1.0) + { + convertToRational(1/identify.exposureTime, &num, &den, 8); + setExifTagRational("Exif.Photo.ExposureTime", num, den, false); + } + + if (identify.aperture != -1.0) + { + convertToRational(identify.aperture, &num, &den, 8); + setExifTagRational("Exif.Photo.ApertureValue", num, den, false); + } + + if (identify.focalLength != -1.0) + { + convertToRational(identify.focalLength, &num, &den, 8); + setExifTagRational("Exif.Photo.FocalLength", num, den, false); + } + + if (identify.imageSize.isValid()) + setImageDimensions(identify.imageSize, false); + + // A RAW image is always uncalibrated. */ + setImageColorWorkSpace(WORKSPACE_UNCALIBRATED, false); + + return true; + } + + return false; +} + +TQString DMetadata::getImageComment() const +{ + if (getFilePath().isEmpty()) + return TQString(); + + // In first we trying to get image comments, outside of Exif and IPTC. + + TQString comment = getCommentsDecoded(); + if (!comment.isEmpty()) + return comment; + + // In second, we trying to get Exif comments + + if (!getExif().isEmpty()) + { + TQString exifComment = getExifComment(); + if (!exifComment.isEmpty()) + return exifComment; + } + + // In third, we trying to get IPTC comments + + if (!getIptc().isEmpty()) + { + TQString iptcComment = getIptcTagString("Iptc.Application2.Caption", false); + if (!iptcComment.isEmpty() && !iptcComment.stripWhiteSpace().isEmpty()) + return iptcComment; + } + + return TQString(); +} + +bool DMetadata::setImageComment(const TQString& comment) +{ + //See bug #139313: An empty string is also a valid value + //if (comment.isEmpty()) + // return false; + + DDebug() << getFilePath() << " ==> Comment: " << comment << endl; + + if (!setProgramId()) + return false; + + // In first we trying to set image comments, outside of Exif and IPTC. + + if (!setComments(comment.utf8())) + return false; + + // In Second we write comments into Exif. + + if (!setExifComment(comment)) + return false; + + // In Third we write comments into Iptc. + // Note that Caption IPTC tag is limited to 2000 char and ASCII charset. + + TQString commentIptc = comment; + commentIptc.truncate(2000); + + if (!setIptcTagString("Iptc.Application2.Caption", commentIptc)) + return false; + + return true; +} + +/* +Iptc.Application2.Urgency <==> digiKam Rating links: + +digiKam IPTC +Rating Urgency + +0 star <=> 8 // Least important +1 star <=> 7 +1 star <== 6 +2 star <=> 5 +3 star <=> 4 +4 star <== 3 +4 star <=> 2 +5 star <=> 1 // Most important +*/ + +int DMetadata::getImageRating() const +{ + if (getFilePath().isEmpty()) + return -1; + + // Check Exif rating tag set by Windows Vista + // Note : no need to check rating in percent tags (Exif.image.0x4747) here because + // its appear always with rating tag value (Exif.image.0x4749). + + if (!getExif().isEmpty()) + { + long rating = -1; + if (getExifTagLong("Exif.Image.0x4746", rating)) + { + if (rating >= RatingMin && rating <= RatingMax) + return rating; + } + } + + // Check Iptc Urgency tag content + + if (!getIptc().isEmpty()) + { + TQString IptcUrgency(getIptcTagData("Iptc.Application2.Urgency")); + + if (!IptcUrgency.isEmpty()) + { + if (IptcUrgency == TQString("1")) + return 5; + else if (IptcUrgency == TQString("2")) + return 4; + else if (IptcUrgency == TQString("3")) + return 4; + else if (IptcUrgency == TQString("4")) + return 3; + else if (IptcUrgency == TQString("5")) + return 2; + else if (IptcUrgency == TQString("6")) + return 1; + else if (IptcUrgency == TQString("7")) + return 1; + else if (IptcUrgency == TQString("8")) + return 0; + } + } + + return -1; +} + +bool DMetadata::setImageRating(int rating) +{ + if (rating < RatingMin || rating > RatingMax) + { + DDebug() << k_funcinfo << "Rating value to write is out of range!" << endl; + return false; + } + + DDebug() << getFilePath() << " ==> Rating: " << rating << endl; + + if (!setProgramId()) + return false; + + // Set Exif rating tag used by Windows Vista. + + if (!setExifTagLong("Exif.Image.0x4746", rating)) + return false; + + // Wrapper around rating percents managed by Windows Vista. + int ratePercents = 0; + switch(rating) + { + case 0: + ratePercents = 0; + break; + case 1: + ratePercents = 1; + break; + case 2: + ratePercents = 25; + break; + case 3: + ratePercents = 50; + break; + case 4: + ratePercents = 75; + break; + case 5: + ratePercents = 99; + break; + } + + if (!setExifTagLong("Exif.Image.0x4749", ratePercents)) + return false; + + // Set Iptc Urgency tag value. + + TQString urgencyTag; + + switch(rating) + { + case 0: + urgencyTag = TQString("8"); + break; + case 1: + urgencyTag = TQString("7"); + break; + case 2: + urgencyTag = TQString("5"); + break; + case 3: + urgencyTag = TQString("4"); + break; + case 4: + urgencyTag = TQString("3"); + break; + case 5: + urgencyTag = TQString("1"); + break; + } + + if (!setIptcTagString("Iptc.Application2.Urgency", urgencyTag)) + return false; + + return true; +} + +bool DMetadata::setIptcTag(const TQString& text, int maxLength, const char* debugLabel, const char* tagKey) +{ + TQString truncatedText = text; + truncatedText.truncate(maxLength); + DDebug() << getFilePath() << " ==> " << debugLabel << ": " << truncatedText << endl; + return setIptcTagString(tagKey, truncatedText); // returns false if failed +} + +bool DMetadata::setImagePhotographerId(const TQString& author, const TQString& authorTitle) +{ + if (!setProgramId()) + return false; + + //TODO Exernalize the hard-coded values + if (!setIptcTag(author, 32, "Author", "Iptc.Application2.Byline")) return false; + if (!setIptcTag(authorTitle, 32, "Author Title", "Iptc.Application2.BylineTitle")) return false; + + return true; +} + +bool DMetadata::setImageCredits(const TQString& credit, const TQString& source, const TQString& copyright) +{ + if (!setProgramId()) + return false; + + //TODO Exernalize the hard-coded values + if (!setIptcTag(credit, 32, "Credit", "Iptc.Application2.Credit")) return false; + if (!setIptcTag(source, 32, "Source", "Iptc.Application2.Source")) return false; + if (!setIptcTag(copyright, 128, "Copyright", "Iptc.Application2.Copyright")) return false; + + return true; +} + +bool DMetadata::setProgramId(bool on) +{ + if (on) + { + TQString version(digikam_version); + TQString software("digiKam"); + return setImageProgramId(software, version); + } + + return true; +} + +PhotoInfoContainer DMetadata::getPhotographInformations() const +{ + PhotoInfoContainer photoInfo; + + if (!getExif().isEmpty()) + { + photoInfo.dateTime = getImageDateTime(); + photoInfo.make = getExifTagString("Exif.Image.Make"); + photoInfo.model = getExifTagString("Exif.Image.Model"); + + photoInfo.aperture = getExifTagString("Exif.Photo.FNumber"); + if (photoInfo.aperture.isEmpty()) + photoInfo.aperture = getExifTagString("Exif.Photo.ApertureValue"); + + photoInfo.exposureTime = getExifTagString("Exif.Photo.ExposureTime"); + if (photoInfo.exposureTime.isEmpty()) + photoInfo.exposureTime = getExifTagString("Exif.Photo.ShutterSpeedValue"); + + photoInfo.exposureMode = getExifTagString("Exif.Photo.ExposureMode"); + photoInfo.exposureProgram = getExifTagString("Exif.Photo.ExposureProgram"); + + photoInfo.focalLength = getExifTagString("Exif.Photo.FocalLength"); + photoInfo.focalLength35mm = getExifTagString("Exif.Photo.FocalLengthIn35mmFilm"); + + photoInfo.sensitivity = getExifTagString("Exif.Photo.ISOSpeedRatings"); + if (photoInfo.sensitivity.isEmpty()) + photoInfo.sensitivity = getExifTagString("Exif.Photo.ExposureIndex"); + + photoInfo.flash = getExifTagString("Exif.Photo.Flash"); + photoInfo.whiteBalance = getExifTagString("Exif.Photo.WhiteBalance"); + } + + return photoInfo; +} + +/** +The following methods set and get an XML dataset into a private IPTC.Application2 tags +to backup digiKam image properties. The XML text data are compressed using zlib and stored +like a byte array. The XML text data format are like below: + + + + + + + + + + + + + +*/ + +bool DMetadata::getXMLImageProperties(TQString& comments, TQDateTime& date, + int& rating, TQStringList& tagsPath) +{ + rating = 0; + + TQByteArray data = getIptcTagData("Iptc.Application2.0x00ff"); + if (data.isEmpty()) + return false; + TQByteArray decompressedData = tqUncompress(data); + TQString doc; + TQDataStream ds(decompressedData, IO_ReadOnly); + ds >> doc; + + TQDomDocument xmlDoc; + TQString error; + int row, col; + if (!xmlDoc.setContent(doc, true, &error, &row, &col)) + { + DDebug() << doc << endl; + DDebug() << error << " :: row=" << row << " , col=" << col << endl; + return false; + } + + TQDomElement rootElem = xmlDoc.documentElement(); + if (rootElem.tagName() != TQString::fromLatin1("digikamproperties")) + return false; + + for (TQDomNode node = rootElem.firstChild(); + !node.isNull(); node = node.nextSibling()) + { + TQDomElement e = node.toElement(); + TQString name = e.tagName(); + TQString val = e.attribute(TQString::fromLatin1("value")); + + if (name == TQString::fromLatin1("comments")) + { + comments = val; + } + else if (name == TQString::fromLatin1("date")) + { + if (val.isEmpty()) continue; + date = TQDateTime::fromString(val, TQt::ISODate); + } + else if (name == TQString::fromLatin1("rating")) + { + if (val.isEmpty()) continue; + bool ok=false; + rating = val.toInt(&ok); + if (!ok) rating = 0; + } + else if (name == TQString::fromLatin1("tagslist")) + { + for (TQDomNode node2 = e.firstChild(); + !node2.isNull(); node2 = node2.nextSibling()) + { + TQDomElement e2 = node2.toElement(); + TQString name2 = e2.tagName(); + TQString val2 = e2.attribute(TQString::fromLatin1("path")); + + if (name2 == TQString::fromLatin1("tag")) + { + if (val2.isEmpty()) continue; + tagsPath.append(val2); + } + } + } + } + + return true; +} + +bool DMetadata::setXMLImageProperties(const TQString& comments, const TQDateTime& date, + int rating, const TQStringList& tagsPath) +{ + TQDomDocument xmlDoc; + + xmlDoc.appendChild(xmlDoc.createProcessingInstruction( TQString::fromLatin1("xml"), + TQString::fromLatin1("version=\"1.0\" encoding=\"UTF-8\"") ) ); + + TQDomElement propertiesElem = xmlDoc.createElement(TQString::fromLatin1("digikamproperties")); + xmlDoc.appendChild( propertiesElem ); + + TQDomElement c = xmlDoc.createElement(TQString::fromLatin1("comments")); + c.setAttribute(TQString::fromLatin1("value"), comments); + propertiesElem.appendChild(c); + + TQDomElement d = xmlDoc.createElement(TQString::fromLatin1("date")); + d.setAttribute(TQString::fromLatin1("value"), date.toString(TQt::ISODate)); + propertiesElem.appendChild(d); + + TQDomElement r = xmlDoc.createElement(TQString::fromLatin1("rating")); + r.setAttribute(TQString::fromLatin1("value"), rating); + propertiesElem.appendChild(r); + + TQDomElement tagsElem = xmlDoc.createElement(TQString::fromLatin1("tagslist")); + propertiesElem.appendChild(tagsElem); + + TQStringList path = tagsPath; + for ( TQStringList::Iterator it = path.begin(); it != path.end(); ++it ) + { + TQDomElement e = xmlDoc.createElement(TQString::fromLatin1("tag")); + e.setAttribute(TQString::fromLatin1("path"), *it); + tagsElem.appendChild(e); + } + + TQByteArray data, compressedData; + TQDataStream ds(data, IO_WriteOnly); + ds << xmlDoc.toString(); + compressedData = tqCompress(data); + return (setIptcTagData("Iptc.Application2.0x00ff", compressedData)); +} + +} // NameSpace Digikam diff --git a/src/libs/dmetadata/dmetadata.h b/src/libs/dmetadata/dmetadata.h new file mode 100644 index 00000000..a33bc61c --- /dev/null +++ b/src/libs/dmetadata/dmetadata.h @@ -0,0 +1,86 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-02-23 + * Description : image metadata interface + * + * Copyright (C) 2006-2007 by Gilles Caulier + * Copyright (C) 2006-2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef DMETADATA_H +#define DMETADATA_H + +// TQt includes. + +#include + +// LibKExiv2 includes. + +#include + +// Local includes. + +#include "dimg.h" +#include "photoinfocontainer.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT DMetadata : public KExiv2Iface::KExiv2 +{ + +public: + + DMetadata(); + DMetadata(const TQString& filePath); + ~DMetadata(); + + /** Re-implemented from libKexiv2 to use dcraw identify method if Exiv2 failed. */ + bool load(const TQString& filePath); + + /** Try to extract metadata using dcraw identify method */ + bool loadUsingDcraw(const TQString& filePath); + + /** Metadata manipulation methods */ + + TQString getImageComment() const; + bool setImageComment(const TQString& comment); + + int getImageRating() const; + bool setImageRating(int rating); + + bool setImagePhotographerId(const TQString& author, const TQString& authorTitle); + bool setImageCredits(const TQString& credit, const TQString& source, const TQString& copyright); + + PhotoInfoContainer getPhotographInformations() const; + + bool getXMLImageProperties(TQString& comments, TQDateTime& date, + int& rating, TQStringList& tagsPath); + bool setXMLImageProperties(const TQString& comments, const TQDateTime& date, + int rating, const TQStringList& tagsPath); + +private: + + bool setProgramId(bool on=true); + bool setIptcTag(const TQString& text, int maxLength, const char* debugLabel, const char* tagKey); +}; + +} // NameSpace Digikam + +#endif /* DMETADATA_H */ diff --git a/src/libs/dmetadata/photoinfocontainer.h b/src/libs/dmetadata/photoinfocontainer.h new file mode 100644 index 00000000..b008b6b4 --- /dev/null +++ b/src/libs/dmetadata/photoinfocontainer.h @@ -0,0 +1,82 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-04-21 + * Description : main photograph information container + * + * Copyright (C) 2006-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef PHOTOINFOCONTAINER_H +#define PHOTOINFOCONTAINER_H + +// TQt includes. + +#include +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT PhotoInfoContainer +{ + +public: + + PhotoInfoContainer(){}; + + bool isEmpty() + { + if ( make.isEmpty() && + model.isEmpty() && + exposureTime.isEmpty() && + exposureMode.isEmpty() && + exposureProgram.isEmpty() && + aperture.isEmpty() && + focalLength.isEmpty() && + focalLength35mm.isEmpty() && + sensitivity.isEmpty() && + flash.isEmpty() && + whiteBalance.isEmpty() && + !dateTime.isValid() ) + return true; + else + return false; + }; + + TQString make; + TQString model; + TQString exposureTime; + TQString exposureMode; + TQString exposureProgram; + TQString aperture; + TQString focalLength; + TQString focalLength35mm; + TQString sensitivity; + TQString flash; + TQString whiteBalance; + + TQDateTime dateTime; +}; + +} // namespace Digikam + +#endif /* PHOTOINFOCONTAINER_H */ diff --git a/src/libs/greycstoration/CImg.h b/src/libs/greycstoration/CImg.h new file mode 100644 index 00000000..9f6662e1 --- /dev/null +++ b/src/libs/greycstoration/CImg.h @@ -0,0 +1,36837 @@ +/* + # + # File : CImg.h + # ( C++ header file ) + # + # Description : The C++ Template Image Processing Library. + # This file is the main part of the CImg Library project. + # ( http://cimg.sourceforge.net ) + # + # Project manager : David Tschumperle. + # ( http://www.greyc.ensicaen.fr/~dtschump/ ) + # + # The complete contributor list can be seen in the 'README.txt' file. + # + # Licenses : This file is "dual-licensed", you have to choose one + # of the two licenses below to apply on this file. + # + # CeCILL-C + # The CeCILL-C license is close to the GNU LGPL. + # ( http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html ) + # + # or CeCILL v2.0 + # The CeCILL license is compatible with the GNU GPL. + # ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html ) + # + # This software is governed either by the CeCILL or the CeCILL-C license + # under French law and abiding by the rules of distribution of free software. + # You can use, modify and or redistribute the software under the terms of + # the CeCILL or CeCILL-C licenses as circulated by CEA, CNRS and INRIA + # at the following URL : "http://www.cecill.info". + # + # As a counterpart to the access to the source code and rights to copy, + # modify and redistribute granted by the license, users are provided only + # with a limited warranty and the software's author, the holder of the + # economic rights, and the successive licensors have only limited + # liability. + # + # In this respect, the user's attention is drawn to the risks associated + # with loading, using, modifying and/or developing or reproducing the + # software by the user in light of its specific status of free software, + # that may mean that it is complicated to manipulate, and that also + # therefore means that it is reserved for developers and experienced + # professionals having in-depth computer knowledge. Users are therefore + # encouraged to load and test the software's suitability as regards their + # requirements in conditions enabling the security of their systems and/or + # data to be ensured and, more generally, to use and operate it in the + # same conditions as regards security. + # + # The fact that you are presently reading this means that you have had + # knowledge of the CeCILL and CeCILL-C licenses and that you accept its terms. + # +*/ + +// Define version number of the current file. +// +#ifndef cimg_version +#define cimg_version 130 + +/*----------------------------------------------------------- + # + # Test/auto-set CImg configuration variables + # and include required headers. + # + # If you find that default configuration variables are + # not adapted, you can override their values before including + # the header file "CImg.h" (using the #define directive). + # + ------------------------------------------------------------*/ + +// Include required standard C++ headers. +// +#include +#include +#include +#include +#include +#include + +// Operating system configuration. +// +// Define 'cimg_OS' to : 0 for an unknown OS (will try to minize library dependancies). +// 1 for a Unix-like OS (Linux, Solaris, BSD, MacOSX, Irix, ...). +// 2 for Microsoft Windows. +// +#ifndef cimg_OS +#if defined(unix) || defined(__unix) || defined(__unix__) \ + || defined(linux) || defined(__linux) || defined(__linux__) \ + || defined(sun) || defined(__sun) \ + || defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) \ + || defined(__FreeBSD__) || defined __DragonFly__ \ + || defined(sgi) || defined(__sgi) \ + || defined(__MACOSX__) || defined(__APPLE__) \ + || defined(__CYGWIN__) +#define cimg_OS 1 +#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) \ + || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) +#define cimg_OS 2 +#else +#define cimg_OS 0 +#endif +#elif !(cimg_OS==0 || cimg_OS==1 || cimg_OS==2) +#error CImg Library : Configuration variable 'cimg_OS' is badly defined. +#error (valid values are '0=unknown OS', '1=Unix-like OS', '2=Microsoft Windows'). +#endif + +// Compiler configuration. +// +// Try to detect Microsoft VC++ compilers. +// (lot of workarounds are needed afterwards to +// make CImg working, particularly with VC++ 6.0). +// +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4311) +#pragma warning(disable:4312) +#pragma warning(disable:4800) +#pragma warning(disable:4804) +#pragma warning(disable:4996) +#define _CRT_SECURE_NO_DEPRECATE 1 +#define _CRT_NONSTDC_NO_DEPRECATE 1 +#if _MSC_VER<1300 +#define cimg_use_visualcpp6 +#define cimg_std +#define _WIN32_WINNT 0x0500 +#endif +#endif + +// Include OS-specific headers. +// +#if cimg_OS==1 +#include +#include +#elif cimg_OS==2 +#include +#ifndef _WIN32_IE +#define _WIN32_IE 0x0400 +#endif +#include +#endif + +// Define defaut pipe for output messages +// +// Define 'cimg_stdout' to : stdout to print CImg messages on the standard output. +// stderr to print CImg messages on the standart error output (default behavior). +// +#ifndef cimg_std +#define cimg_std std +#endif +#ifndef cimg_stdout +#define cimg_stdout stderr +#endif + +// Output messages configuration. +// +// Define 'cimg_debug' to : 0 to hide debug messages (quiet mode, but exceptions are still thrown). +// 1 to display debug messages on the console. +// 2 to display debug messages with dialog windows (default behavior). +// 3 to do as 1 + add extra warnings (may slow down the code !). +// 4 to do as 2 + add extra warnings (may slow down the code !). +// +// Define 'cimg_strict_warnings' to replace warning messages by exception throwns. +// +// Define 'cimg_use_vt100' to allow output of color messages (require VT100-compatible terminal). +// +#ifndef cimg_debug +#define cimg_debug 2 +#elif !(cimg_debug==0 || cimg_debug==1 || cimg_debug==2 || cimg_debug==3 || cimg_debug==4) +#error CImg Library : Configuration variable 'cimg_debug' is badly defined. +#error (valid values are '0=quiet', '1=console', '2=dialog', '3=console+warnings', '4=dialog+warnings'). +#endif + +// Display framework configuration. +// +// Define 'cimg_display' to : 0 to disable display capabilities. +// 1 to use X-Window framework (X11). +// 2 to use Microsoft GDI32 framework. +// 3 to use Apple Carbon framework. +// +#ifndef cimg_display +#if cimg_OS==0 +#define cimg_display 0 +#elif cimg_OS==1 +#if defined(__MACOSX__) || defined(__APPLE__) +#define cimg_display 1 +#else +#define cimg_display 1 +#endif +#elif cimg_OS==2 +#define cimg_display 2 +#endif +#elif !(cimg_display==0 || cimg_display==1 || cimg_display==2 || cimg_display==3) +#error CImg Library : Configuration variable 'cimg_display' is badly defined. +#error (valid values are '0=disable', '1=X-Window (X11)', '2=Microsoft GDI32', '3=Apple Carbon'). +#endif + +// Include display-specific headers. +// +#if cimg_display==1 +#include +#include +#include +#include +#ifdef cimg_use_xshm +#include +#include +#include +#endif +#ifdef cimg_use_xrandr +#include +#endif +#elif cimg_display==3 +#include +#include +#endif + +// OpenMP configuration. +// (http://www.openmp.org) +// +// Define 'cimg_use_openmp' to enable OpenMP support. +// +// OpenMP directives can be used in few CImg functions to get +// advantages of multi-core CPUs. Using OpenMP is not mandatory. +// +#ifdef cimg_use_openmp +#include "omp.h" +#endif + +// LibPNG configuration. +// (http://www.libpng.org) +// +// Define 'cimg_use_png' to enable LibPNG support. +// +// LibPNG can be used in functions 'CImg::{load,save}_png()' +// to get a builtin support of PNG files. Using LibPNG is not mandatory. +// +#ifdef cimg_use_png +extern "C" { +#include "png.h" +} +#endif + +// LibJPEG configuration. +// (http://en.wikipedia.org/wiki/Libjpeg) +// +// Define 'cimg_use_jpeg' to enable LibJPEG support. +// +// LibJPEG can be used in functions 'CImg::{load,save}_jpeg()' +// to get a builtin support of JPEG files. Using LibJPEG is not mandatory. +// +#ifdef cimg_use_jpeg +extern "C" { +#include "jpeglib.h" +} +#endif + +// LibTIFF configuration. +// (http://www.libtiff.org) +// +// Define 'cimg_use_tiff' to enable LibTIFF support. +// +// LibTIFF can be used in functions 'CImg[List]::{load,save}_tiff()' +// to get a builtin support of TIFF files. Using LibTIFF is not mandatory. +// +#ifdef cimg_use_tiff +extern "C" { +#include "tiffio.h" +} +#endif + +// FFMPEG Avcodec and Avformat libraries configuration. +// (http://www.ffmpeg.org) +// +// Define 'cimg_use_ffmpeg' to enable FFMPEG lib support. +// +// Avcodec and Avformat libraries can be used in functions +// 'CImg[List]::load_ffmpeg()' to get a builtin +// support of various image sequences files. +// Using FFMPEG libraries is not mandatory. +// +#ifdef cimg_use_ffmpeg +extern "C" { +#include "avformat.h" +#include "avcodec.h" +#include "swscale.h" +} +#endif + +// Zlib configuration +// (http://www.zlib.net) +// +// Define 'cimg_use_zlib' to enable Zlib support. +// +// Zlib can be used in functions 'CImg[List]::{load,save}_cimg()' +// to allow compressed data in '.cimg' files. Using Zlib is not mandatory. +// +#ifdef cimg_use_zlib +extern "C" { +#include "zlib.h" +} +#endif + +// Magick++ configuration. +// (http://www.imagemagick.org/Magick++) +// +// Define 'cimg_use_magick' to enable Magick++ support. +// +// Magick++ library can be used in functions 'CImg::{load,save}()' +// to get a builtin support of various image formats (PNG,JPEG,TIFF,...). +// Using Magick++ is not mandatory. +// +#ifdef cimg_use_magick +#include "Magick++.h" +#endif + +// FFTW3 configuration. +// (http://www.fftw.org) +// +// Define 'cimg_use_fftw3' to enable libFFTW3 support. +// +// FFTW3 library can be used in functions 'CImg[List]::FFT()' to +// efficiently compile the Fast Fourier Transform of image data. +// +#ifdef cimg_use_fftw3 +extern "C" { +#include "fftw3.h" +} +#endif + +// Board configuration. +// (http://libboard.sourceforge.net/) +// +// Define 'cimg_use_board' to enable Board support. +// +// Board library can be used in functions 'CImg::draw_object3d()' +// to draw objects 3D in vector-graphics canvas that can be saved +// as .PS or .SVG files afterwards. +// +#ifdef cimg_use_board +#include "Board.h" +#endif + +// Lapack configuration. +// (http://www.netlib.org/lapack) +// +// Define 'cimg_use_lapack' to enable LAPACK support. +// +// Lapack can be used in various CImg functions dealing with +// matrix computation and algorithms (eigenvalues, inverse, ...). +// Using Lapack is not mandatory. +// +#ifdef cimg_use_lapack +extern "C" { + extern void sgetrf_(int*, int*, float*, int*, int*, int*); + extern void sgetri_(int*, float*, int*, int*, float*, int*, int*); + extern void sgetrs_(char*, int*, int*, float*, int*, int*, float*, int*, int*); + extern void sgesvd_(char*, char*, int*, int*, float*, int*, float*, float*, int*, float*, int*, float*, int*, int*); + extern void ssyev_(char*, char*, int*, float*, int*, float*, float*, int*, int*); + extern void dgetrf_(int*, int*, double*, int*, int*, int*); + extern void dgetri_(int*, double*, int*, int*, double*, int*, int*); + extern void dgetrs_(char*, int*, int*, double*, int*, int*, double*, int*, int*); + extern void dgesvd_(char*, char*, int*, int*, double*, int*, double*, double*, int*, double*, int*, double*, int*, int*); + extern void dsyev_(char*, char*, int*, double*, int*, double*, double*, int*, int*); +} +#endif + +// Check if min/max macros are defined. +// +// CImg does not compile if macros 'min' or 'max' are defined, +// because min() and max() functions are also defined in the cimg:: namespace. +// so it '#undef' these macros if necessary, and restore them to reasonable +// values at the end of the file. +// +#ifdef min +#undef min +#define _cimg_redefine_min +#endif +#ifdef max +#undef max +#define _cimg_redefine_max +#endif + +// Set the current working directory for native MacOSX bundled applications. +// +// By default, MacOS bundled applications set the cwd at the root directory '/', +// the code below allows to set it to the current exec directory instead when +// a CImg-based program is executed. +// +#if cimg_OS==1 && cimg_display==3 +static struct _cimg_macosx_setcwd { + _cimg_macosx_setcwd() { + FSRef location; + ProcessSerialNumber psn; + char filePath[512]; + if (GetCurrentProcess(&psn)!=noErr) return; + if (GetProcessBundleLocation(&psn,&location)!=noErr) return; + FSRefMakePath(&location,(UInt8*)filePath,sizeof(filePath)-1); + int p = cimg_std::strlen(filePath); + while (filePath[p] != '/') --p; + filePath[p] = 0; + chdir(filePath); + } +} cimg_macosx_setcwd; +#endif + +/*------------------------------------------------------------------------------ + # + # Define user-friendly macros. + # + # User macros are prefixed by 'cimg_' and can be used in your own code. + # They are particularly useful for option parsing, and image loops creation. + # + ------------------------------------------------------------------------------*/ + +// Define the program usage, and retrieve command line arguments. +// +#define cimg_usage(usage) cimg_library::cimg::option((char*)0,argc,argv,(char*)0,usage) +#define cimg_help(str) cimg_library::cimg::option((char*)0,argc,argv,str,(char*)0) +#define cimg_option(name,defaut,usage) cimg_library::cimg::option(name,argc,argv,defaut,usage) +#define cimg_argument(pos) cimg_library::cimg::argument(pos,argc,argv) +#define cimg_argument1(pos,s0) cimg_library::cimg::argument(pos,argc,argv,1,s0) +#define cimg_argument2(pos,s0,s1) cimg_library::cimg::argument(pos,argc,argv,2,s0,s1) +#define cimg_argument3(pos,s0,s1,s2) cimg_library::cimg::argument(pos,argc,argv,3,s0,s1,s2) +#define cimg_argument4(pos,s0,s1,s2,s3) cimg_library::cimg::argument(pos,argc,argv,4,s0,s1,s2,s3) +#define cimg_argument5(pos,s0,s1,s2,s3,s4) cimg_library::cimg::argument(pos,argc,argv,5,s0,s1,s2,s3,s4) +#define cimg_argument6(pos,s0,s1,s2,s3,s4,s5) cimg_library::cimg::argument(pos,argc,argv,6,s0,s1,s2,s3,s4,s5) +#define cimg_argument7(pos,s0,s1,s2,s3,s4,s5,s6) cimg_library::cimg::argument(pos,argc,argv,7,s0,s1,s2,s3,s4,s5,s6) +#define cimg_argument8(pos,s0,s1,s2,s3,s4,s5,s6,s7) cimg_library::cimg::argument(pos,argc,argv,8,s0,s1,s2,s3,s4,s5,s6,s7) +#define cimg_argument9(pos,s0,s1,s2,s3,s4,s5,s6,s7,s8) cimg_library::cimg::argument(pos,argc,argv,9,s0,s1,s2,s3,s4,s5,s6,s7,s8) + +// Define and manipulate local neighborhoods. +// +#define CImg_2x2(I,T) T I[4]; \ + T& I##cc = I[0]; T& I##nc = I[1]; \ + T& I##cn = I[2]; T& I##nn = I[3]; \ + I##cc = I##nc = \ + I##cn = I##nn = 0 + +#define CImg_3x3(I,T) T I[9]; \ + T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; \ + T& I##pc = I[3]; T& I##cc = I[4]; T& I##nc = I[5]; \ + T& I##pn = I[6]; T& I##cn = I[7]; T& I##nn = I[8]; \ + I##pp = I##cp = I##np = \ + I##pc = I##cc = I##nc = \ + I##pn = I##cn = I##nn = 0 + +#define CImg_4x4(I,T) T I[16]; \ + T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; T& I##ap = I[3]; \ + T& I##pc = I[4]; T& I##cc = I[5]; T& I##nc = I[6]; T& I##ac = I[7]; \ + T& I##pn = I[8]; T& I##cn = I[9]; T& I##nn = I[10]; T& I##an = I[11]; \ + T& I##pa = I[12]; T& I##ca = I[13]; T& I##na = I[14]; T& I##aa = I[15]; \ + I##pp = I##cp = I##np = I##ap = \ + I##pc = I##cc = I##nc = I##ac = \ + I##pn = I##cn = I##nn = I##an = \ + I##pa = I##ca = I##na = I##aa = 0 + +#define CImg_5x5(I,T) T I[25]; \ + T& I##bb = I[0]; T& I##pb = I[1]; T& I##cb = I[2]; T& I##nb = I[3]; T& I##ab = I[4]; \ + T& I##bp = I[5]; T& I##pp = I[6]; T& I##cp = I[7]; T& I##np = I[8]; T& I##ap = I[9]; \ + T& I##bc = I[10]; T& I##pc = I[11]; T& I##cc = I[12]; T& I##nc = I[13]; T& I##ac = I[14]; \ + T& I##bn = I[15]; T& I##pn = I[16]; T& I##cn = I[17]; T& I##nn = I[18]; T& I##an = I[19]; \ + T& I##ba = I[20]; T& I##pa = I[21]; T& I##ca = I[22]; T& I##na = I[23]; T& I##aa = I[24]; \ + I##bb = I##pb = I##cb = I##nb = I##ab = \ + I##bp = I##pp = I##cp = I##np = I##ap = \ + I##bc = I##pc = I##cc = I##nc = I##ac = \ + I##bn = I##pn = I##cn = I##nn = I##an = \ + I##ba = I##pa = I##ca = I##na = I##aa = 0 + +#define CImg_2x2x2(I,T) T I[8]; \ + T& I##ccc = I[0]; T& I##ncc = I[1]; \ + T& I##cnc = I[2]; T& I##nnc = I[3]; \ + T& I##ccn = I[4]; T& I##ncn = I[5]; \ + T& I##cnn = I[6]; T& I##nnn = I[7]; \ + I##ccc = I##ncc = \ + I##cnc = I##nnc = \ + I##ccn = I##ncn = \ + I##cnn = I##nnn = 0 + +#define CImg_3x3x3(I,T) T I[27]; \ + T& I##ppp = I[0]; T& I##cpp = I[1]; T& I##npp = I[2]; \ + T& I##pcp = I[3]; T& I##ccp = I[4]; T& I##ncp = I[5]; \ + T& I##pnp = I[6]; T& I##cnp = I[7]; T& I##nnp = I[8]; \ + T& I##ppc = I[9]; T& I##cpc = I[10]; T& I##npc = I[11]; \ + T& I##pcc = I[12]; T& I##ccc = I[13]; T& I##ncc = I[14]; \ + T& I##pnc = I[15]; T& I##cnc = I[16]; T& I##nnc = I[17]; \ + T& I##ppn = I[18]; T& I##cpn = I[19]; T& I##npn = I[20]; \ + T& I##pcn = I[21]; T& I##ccn = I[22]; T& I##ncn = I[23]; \ + T& I##pnn = I[24]; T& I##cnn = I[25]; T& I##nnn = I[26]; \ + I##ppp = I##cpp = I##npp = \ + I##pcp = I##ccp = I##ncp = \ + I##pnp = I##cnp = I##nnp = \ + I##ppc = I##cpc = I##npc = \ + I##pcc = I##ccc = I##ncc = \ + I##pnc = I##cnc = I##nnc = \ + I##ppn = I##cpn = I##npn = \ + I##pcn = I##ccn = I##ncn = \ + I##pnn = I##cnn = I##nnn = 0 + +#define cimg_get2x2(img,x,y,z,v,I) \ + I[0] = (img)(x,y,z,v), I[1] = (img)(_n1##x,y,z,v), I[2] = (img)(x,_n1##y,z,v), I[3] = (img)(_n1##x,_n1##y,z,v) + +#define cimg_get3x3(img,x,y,z,v,I) \ + I[0] = (img)(_p1##x,_p1##y,z,v), I[1] = (img)(x,_p1##y,z,v), I[2] = (img)(_n1##x,_p1##y,z,v), I[3] = (img)(_p1##x,y,z,v), \ + I[4] = (img)(x,y,z,v), I[5] = (img)(_n1##x,y,z,v), I[6] = (img)(_p1##x,_n1##y,z,v), I[7] = (img)(x,_n1##y,z,v), \ + I[8] = (img)(_n1##x,_n1##y,z,v) + +#define cimg_get4x4(img,x,y,z,v,I) \ + I[0] = (img)(_p1##x,_p1##y,z,v), I[1] = (img)(x,_p1##y,z,v), I[2] = (img)(_n1##x,_p1##y,z,v), I[3] = (img)(_n2##x,_p1##y,z,v), \ + I[4] = (img)(_p1##x,y,z,v), I[5] = (img)(x,y,z,v), I[6] = (img)(_n1##x,y,z,v), I[7] = (img)(_n2##x,y,z,v), \ + I[8] = (img)(_p1##x,_n1##y,z,v), I[9] = (img)(x,_n1##y,z,v), I[10] = (img)(_n1##x,_n1##y,z,v), I[11] = (img)(_n2##x,_n1##y,z,v), \ + I[12] = (img)(_p1##x,_n2##y,z,v), I[13] = (img)(x,_n2##y,z,v), I[14] = (img)(_n1##x,_n2##y,z,v), I[15] = (img)(_n2##x,_n2##y,z,v) + +#define cimg_get5x5(img,x,y,z,v,I) \ + I[0] = (img)(_p2##x,_p2##y,z,v), I[1] = (img)(_p1##x,_p2##y,z,v), I[2] = (img)(x,_p2##y,z,v), I[3] = (img)(_n1##x,_p2##y,z,v), \ + I[4] = (img)(_n2##x,_p2##y,z,v), I[5] = (img)(_p2##x,_p1##y,z,v), I[6] = (img)(_p1##x,_p1##y,z,v), I[7] = (img)(x,_p1##y,z,v), \ + I[8] = (img)(_n1##x,_p1##y,z,v), I[9] = (img)(_n2##x,_p1##y,z,v), I[10] = (img)(_p2##x,y,z,v), I[11] = (img)(_p1##x,y,z,v), \ + I[12] = (img)(x,y,z,v), I[13] = (img)(_n1##x,y,z,v), I[14] = (img)(_n2##x,y,z,v), I[15] = (img)(_p2##x,_n1##y,z,v), \ + I[16] = (img)(_p1##x,_n1##y,z,v), I[17] = (img)(x,_n1##y,z,v), I[18] = (img)(_n1##x,_n1##y,z,v), I[19] = (img)(_n2##x,_n1##y,z,v), \ + I[20] = (img)(_p2##x,_n2##y,z,v), I[21] = (img)(_p1##x,_n2##y,z,v), I[22] = (img)(x,_n2##y,z,v), I[23] = (img)(_n1##x,_n2##y,z,v), \ + I[24] = (img)(_n2##x,_n2##y,z,v) + +#define cimg_get6x6(img,x,y,z,v,I) \ + I[0] = (img)(_p2##x,_p2##y,z,v), I[1] = (img)(_p1##x,_p2##y,z,v), I[2] = (img)(x,_p2##y,z,v), I[3] = (img)(_n1##x,_p2##y,z,v), \ + I[4] = (img)(_n2##x,_p2##y,z,v), I[5] = (img)(_n3##x,_p2##y,z,v), I[6] = (img)(_p2##x,_p1##y,z,v), I[7] = (img)(_p1##x,_p1##y,z,v), \ + I[8] = (img)(x,_p1##y,z,v), I[9] = (img)(_n1##x,_p1##y,z,v), I[10] = (img)(_n2##x,_p1##y,z,v), I[11] = (img)(_n3##x,_p1##y,z,v), \ + I[12] = (img)(_p2##x,y,z,v), I[13] = (img)(_p1##x,y,z,v), I[14] = (img)(x,y,z,v), I[15] = (img)(_n1##x,y,z,v), \ + I[16] = (img)(_n2##x,y,z,v), I[17] = (img)(_n3##x,y,z,v), I[18] = (img)(_p2##x,_n1##y,z,v), I[19] = (img)(_p1##x,_n1##y,z,v), \ + I[20] = (img)(x,_n1##y,z,v), I[21] = (img)(_n1##x,_n1##y,z,v), I[22] = (img)(_n2##x,_n1##y,z,v), I[23] = (img)(_n3##x,_n1##y,z,v), \ + I[24] = (img)(_p2##x,_n2##y,z,v), I[25] = (img)(_p1##x,_n2##y,z,v), I[26] = (img)(x,_n2##y,z,v), I[27] = (img)(_n1##x,_n2##y,z,v), \ + I[28] = (img)(_n2##x,_n2##y,z,v), I[29] = (img)(_n3##x,_n2##y,z,v), I[30] = (img)(_p2##x,_n3##y,z,v), I[31] = (img)(_p1##x,_n3##y,z,v), \ + I[32] = (img)(x,_n3##y,z,v), I[33] = (img)(_n1##x,_n3##y,z,v), I[34] = (img)(_n2##x,_n3##y,z,v), I[35] = (img)(_n3##x,_n3##y,z,v) + +#define cimg_get7x7(img,x,y,z,v,I) \ + I[0] = (img)(_p3##x,_p3##y,z,v), I[1] = (img)(_p2##x,_p3##y,z,v), I[2] = (img)(_p1##x,_p3##y,z,v), I[3] = (img)(x,_p3##y,z,v), \ + I[4] = (img)(_n1##x,_p3##y,z,v), I[5] = (img)(_n2##x,_p3##y,z,v), I[6] = (img)(_n3##x,_p3##y,z,v), I[7] = (img)(_p3##x,_p2##y,z,v), \ + I[8] = (img)(_p2##x,_p2##y,z,v), I[9] = (img)(_p1##x,_p2##y,z,v), I[10] = (img)(x,_p2##y,z,v), I[11] = (img)(_n1##x,_p2##y,z,v), \ + I[12] = (img)(_n2##x,_p2##y,z,v), I[13] = (img)(_n3##x,_p2##y,z,v), I[14] = (img)(_p3##x,_p1##y,z,v), I[15] = (img)(_p2##x,_p1##y,z,v), \ + I[16] = (img)(_p1##x,_p1##y,z,v), I[17] = (img)(x,_p1##y,z,v), I[18] = (img)(_n1##x,_p1##y,z,v), I[19] = (img)(_n2##x,_p1##y,z,v), \ + I[20] = (img)(_n3##x,_p1##y,z,v), I[21] = (img)(_p3##x,y,z,v), I[22] = (img)(_p2##x,y,z,v), I[23] = (img)(_p1##x,y,z,v), \ + I[24] = (img)(x,y,z,v), I[25] = (img)(_n1##x,y,z,v), I[26] = (img)(_n2##x,y,z,v), I[27] = (img)(_n3##x,y,z,v), \ + I[28] = (img)(_p3##x,_n1##y,z,v), I[29] = (img)(_p2##x,_n1##y,z,v), I[30] = (img)(_p1##x,_n1##y,z,v), I[31] = (img)(x,_n1##y,z,v), \ + I[32] = (img)(_n1##x,_n1##y,z,v), I[33] = (img)(_n2##x,_n1##y,z,v), I[34] = (img)(_n3##x,_n1##y,z,v), I[35] = (img)(_p3##x,_n2##y,z,v), \ + I[36] = (img)(_p2##x,_n2##y,z,v), I[37] = (img)(_p1##x,_n2##y,z,v), I[38] = (img)(x,_n2##y,z,v), I[39] = (img)(_n1##x,_n2##y,z,v), \ + I[40] = (img)(_n2##x,_n2##y,z,v), I[41] = (img)(_n3##x,_n2##y,z,v), I[42] = (img)(_p3##x,_n3##y,z,v), I[43] = (img)(_p2##x,_n3##y,z,v), \ + I[44] = (img)(_p1##x,_n3##y,z,v), I[45] = (img)(x,_n3##y,z,v), I[46] = (img)(_n1##x,_n3##y,z,v), I[47] = (img)(_n2##x,_n3##y,z,v), \ + I[48] = (img)(_n3##x,_n3##y,z,v) + +#define cimg_get8x8(img,x,y,z,v,I) \ + I[0] = (img)(_p3##x,_p3##y,z,v), I[1] = (img)(_p2##x,_p3##y,z,v), I[2] = (img)(_p1##x,_p3##y,z,v), I[3] = (img)(x,_p3##y,z,v), \ + I[4] = (img)(_n1##x,_p3##y,z,v), I[5] = (img)(_n2##x,_p3##y,z,v), I[6] = (img)(_n3##x,_p3##y,z,v), I[7] = (img)(_n4##x,_p3##y,z,v), \ + I[8] = (img)(_p3##x,_p2##y,z,v), I[9] = (img)(_p2##x,_p2##y,z,v), I[10] = (img)(_p1##x,_p2##y,z,v), I[11] = (img)(x,_p2##y,z,v), \ + I[12] = (img)(_n1##x,_p2##y,z,v), I[13] = (img)(_n2##x,_p2##y,z,v), I[14] = (img)(_n3##x,_p2##y,z,v), I[15] = (img)(_n4##x,_p2##y,z,v), \ + I[16] = (img)(_p3##x,_p1##y,z,v), I[17] = (img)(_p2##x,_p1##y,z,v), I[18] = (img)(_p1##x,_p1##y,z,v), I[19] = (img)(x,_p1##y,z,v), \ + I[20] = (img)(_n1##x,_p1##y,z,v), I[21] = (img)(_n2##x,_p1##y,z,v), I[22] = (img)(_n3##x,_p1##y,z,v), I[23] = (img)(_n4##x,_p1##y,z,v), \ + I[24] = (img)(_p3##x,y,z,v), I[25] = (img)(_p2##x,y,z,v), I[26] = (img)(_p1##x,y,z,v), I[27] = (img)(x,y,z,v), \ + I[28] = (img)(_n1##x,y,z,v), I[29] = (img)(_n2##x,y,z,v), I[30] = (img)(_n3##x,y,z,v), I[31] = (img)(_n4##x,y,z,v), \ + I[32] = (img)(_p3##x,_n1##y,z,v), I[33] = (img)(_p2##x,_n1##y,z,v), I[34] = (img)(_p1##x,_n1##y,z,v), I[35] = (img)(x,_n1##y,z,v), \ + I[36] = (img)(_n1##x,_n1##y,z,v), I[37] = (img)(_n2##x,_n1##y,z,v), I[38] = (img)(_n3##x,_n1##y,z,v), I[39] = (img)(_n4##x,_n1##y,z,v), \ + I[40] = (img)(_p3##x,_n2##y,z,v), I[41] = (img)(_p2##x,_n2##y,z,v), I[42] = (img)(_p1##x,_n2##y,z,v), I[43] = (img)(x,_n2##y,z,v), \ + I[44] = (img)(_n1##x,_n2##y,z,v), I[45] = (img)(_n2##x,_n2##y,z,v), I[46] = (img)(_n3##x,_n2##y,z,v), I[47] = (img)(_n4##x,_n2##y,z,v), \ + I[48] = (img)(_p3##x,_n3##y,z,v), I[49] = (img)(_p2##x,_n3##y,z,v), I[50] = (img)(_p1##x,_n3##y,z,v), I[51] = (img)(x,_n3##y,z,v), \ + I[52] = (img)(_n1##x,_n3##y,z,v), I[53] = (img)(_n2##x,_n3##y,z,v), I[54] = (img)(_n3##x,_n3##y,z,v), I[55] = (img)(_n4##x,_n3##y,z,v), \ + I[56] = (img)(_p3##x,_n4##y,z,v), I[57] = (img)(_p2##x,_n4##y,z,v), I[58] = (img)(_p1##x,_n4##y,z,v), I[59] = (img)(x,_n4##y,z,v), \ + I[60] = (img)(_n1##x,_n4##y,z,v), I[61] = (img)(_n2##x,_n4##y,z,v), I[62] = (img)(_n3##x,_n4##y,z,v), I[63] = (img)(_n4##x,_n4##y,z,v); + +#define cimg_get9x9(img,x,y,z,v,I) \ + I[0] = (img)(_p4##x,_p4##y,z,v), I[1] = (img)(_p3##x,_p4##y,z,v), I[2] = (img)(_p2##x,_p4##y,z,v), I[3] = (img)(_p1##x,_p4##y,z,v), \ + I[4] = (img)(x,_p4##y,z,v), I[5] = (img)(_n1##x,_p4##y,z,v), I[6] = (img)(_n2##x,_p4##y,z,v), I[7] = (img)(_n3##x,_p4##y,z,v), \ + I[8] = (img)(_n4##x,_p4##y,z,v), I[9] = (img)(_p4##x,_p3##y,z,v), I[10] = (img)(_p3##x,_p3##y,z,v), I[11] = (img)(_p2##x,_p3##y,z,v), \ + I[12] = (img)(_p1##x,_p3##y,z,v), I[13] = (img)(x,_p3##y,z,v), I[14] = (img)(_n1##x,_p3##y,z,v), I[15] = (img)(_n2##x,_p3##y,z,v), \ + I[16] = (img)(_n3##x,_p3##y,z,v), I[17] = (img)(_n4##x,_p3##y,z,v), I[18] = (img)(_p4##x,_p2##y,z,v), I[19] = (img)(_p3##x,_p2##y,z,v), \ + I[20] = (img)(_p2##x,_p2##y,z,v), I[21] = (img)(_p1##x,_p2##y,z,v), I[22] = (img)(x,_p2##y,z,v), I[23] = (img)(_n1##x,_p2##y,z,v), \ + I[24] = (img)(_n2##x,_p2##y,z,v), I[25] = (img)(_n3##x,_p2##y,z,v), I[26] = (img)(_n4##x,_p2##y,z,v), I[27] = (img)(_p4##x,_p1##y,z,v), \ + I[28] = (img)(_p3##x,_p1##y,z,v), I[29] = (img)(_p2##x,_p1##y,z,v), I[30] = (img)(_p1##x,_p1##y,z,v), I[31] = (img)(x,_p1##y,z,v), \ + I[32] = (img)(_n1##x,_p1##y,z,v), I[33] = (img)(_n2##x,_p1##y,z,v), I[34] = (img)(_n3##x,_p1##y,z,v), I[35] = (img)(_n4##x,_p1##y,z,v), \ + I[36] = (img)(_p4##x,y,z,v), I[37] = (img)(_p3##x,y,z,v), I[38] = (img)(_p2##x,y,z,v), I[39] = (img)(_p1##x,y,z,v), \ + I[40] = (img)(x,y,z,v), I[41] = (img)(_n1##x,y,z,v), I[42] = (img)(_n2##x,y,z,v), I[43] = (img)(_n3##x,y,z,v), \ + I[44] = (img)(_n4##x,y,z,v), I[45] = (img)(_p4##x,_n1##y,z,v), I[46] = (img)(_p3##x,_n1##y,z,v), I[47] = (img)(_p2##x,_n1##y,z,v), \ + I[48] = (img)(_p1##x,_n1##y,z,v), I[49] = (img)(x,_n1##y,z,v), I[50] = (img)(_n1##x,_n1##y,z,v), I[51] = (img)(_n2##x,_n1##y,z,v), \ + I[52] = (img)(_n3##x,_n1##y,z,v), I[53] = (img)(_n4##x,_n1##y,z,v), I[54] = (img)(_p4##x,_n2##y,z,v), I[55] = (img)(_p3##x,_n2##y,z,v), \ + I[56] = (img)(_p2##x,_n2##y,z,v), I[57] = (img)(_p1##x,_n2##y,z,v), I[58] = (img)(x,_n2##y,z,v), I[59] = (img)(_n1##x,_n2##y,z,v), \ + I[60] = (img)(_n2##x,_n2##y,z,v), I[61] = (img)(_n3##x,_n2##y,z,v), I[62] = (img)(_n4##x,_n2##y,z,v), I[63] = (img)(_p4##x,_n3##y,z,v), \ + I[64] = (img)(_p3##x,_n3##y,z,v), I[65] = (img)(_p2##x,_n3##y,z,v), I[66] = (img)(_p1##x,_n3##y,z,v), I[67] = (img)(x,_n3##y,z,v), \ + I[68] = (img)(_n1##x,_n3##y,z,v), I[69] = (img)(_n2##x,_n3##y,z,v), I[70] = (img)(_n3##x,_n3##y,z,v), I[71] = (img)(_n4##x,_n3##y,z,v), \ + I[72] = (img)(_p4##x,_n4##y,z,v), I[73] = (img)(_p3##x,_n4##y,z,v), I[74] = (img)(_p2##x,_n4##y,z,v), I[75] = (img)(_p1##x,_n4##y,z,v), \ + I[76] = (img)(x,_n4##y,z,v), I[77] = (img)(_n1##x,_n4##y,z,v), I[78] = (img)(_n2##x,_n4##y,z,v), I[79] = (img)(_n3##x,_n4##y,z,v), \ + I[80] = (img)(_n4##x,_n4##y,z,v) + +#define cimg_get2x2x2(img,x,y,z,v,I) \ + I[0] = (img)(x,y,z,v), I[1] = (img)(_n1##x,y,z,v), I[2] = (img)(x,_n1##y,z,v), I[3] = (img)(_n1##x,_n1##y,z,v), \ + I[4] = (img)(x,y,_n1##z,v), I[5] = (img)(_n1##x,y,_n1##z,v), I[6] = (img)(x,_n1##y,_n1##z,v), I[7] = (img)(_n1##x,_n1##y,_n1##z,v) + +#define cimg_get3x3x3(img,x,y,z,v,I) \ + I[0] = (img)(_p1##x,_p1##y,_p1##z,v), I[1] = (img)(x,_p1##y,_p1##z,v), I[2] = (img)(_n1##x,_p1##y,_p1##z,v), \ + I[3] = (img)(_p1##x,y,_p1##z,v), I[4] = (img)(x,y,_p1##z,v), I[5] = (img)(_n1##x,y,_p1##z,v), \ + I[6] = (img)(_p1##x,_n1##y,_p1##z,v), I[7] = (img)(x,_n1##y,_p1##z,v), I[8] = (img)(_n1##x,_n1##y,_p1##z,v), \ + I[9] = (img)(_p1##x,_p1##y,z,v), I[10] = (img)(x,_p1##y,z,v), I[11] = (img)(_n1##x,_p1##y,z,v), \ + I[12] = (img)(_p1##x,y,z,v), I[13] = (img)(x,y,z,v), I[14] = (img)(_n1##x,y,z,v), \ + I[15] = (img)(_p1##x,_n1##y,z,v), I[16] = (img)(x,_n1##y,z,v), I[17] = (img)(_n1##x,_n1##y,z,v), \ + I[18] = (img)(_p1##x,_p1##y,_n1##z,v), I[19] = (img)(x,_p1##y,_n1##z,v), I[20] = (img)(_n1##x,_p1##y,_n1##z,v), \ + I[21] = (img)(_p1##x,y,_n1##z,v), I[22] = (img)(x,y,_n1##z,v), I[23] = (img)(_n1##x,y,_n1##z,v), \ + I[24] = (img)(_p1##x,_n1##y,_n1##z,v), I[25] = (img)(x,_n1##y,_n1##z,v), I[26] = (img)(_n1##x,_n1##y,_n1##z,v) + +// Define various image loops. +// +// These macros generally avoid the use of iterators, but you are not forced to used them ! +// +#define cimg_for(img,ptr,T_ptr) for (T_ptr *ptr = (img).data + (img).size(); (ptr--)>(img).data; ) +#define cimg_foroff(img,off) for (unsigned int off = 0, _max##off = (unsigned int)(img).size(); off<_max##off; ++off) +#define cimglist_for(list,l) for (unsigned int l=0; l<(list).size; ++l) +#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn + +#define cimg_for1(bound,i) for (int i = 0; i<(int)(bound); ++i) +#define cimg_forX(img,x) cimg_for1((img).width,x) +#define cimg_forY(img,y) cimg_for1((img).height,y) +#define cimg_forZ(img,z) cimg_for1((img).depth,z) +#define cimg_forV(img,v) cimg_for1((img).dim,v) +#define cimg_forXY(img,x,y) cimg_forY(img,y) cimg_forX(img,x) +#define cimg_forXZ(img,x,z) cimg_forZ(img,z) cimg_forX(img,x) +#define cimg_forYZ(img,y,z) cimg_forZ(img,z) cimg_forY(img,y) +#define cimg_forXV(img,x,v) cimg_forV(img,v) cimg_forX(img,x) +#define cimg_forYV(img,y,v) cimg_forV(img,v) cimg_forY(img,y) +#define cimg_forZV(img,z,v) cimg_forV(img,v) cimg_forZ(img,z) +#define cimg_forXYZ(img,x,y,z) cimg_forZ(img,z) cimg_forXY(img,x,y) +#define cimg_forXYV(img,x,y,v) cimg_forV(img,v) cimg_forXY(img,x,y) +#define cimg_forXZV(img,x,z,v) cimg_forV(img,v) cimg_forXZ(img,x,z) +#define cimg_forYZV(img,y,z,v) cimg_forV(img,v) cimg_forYZ(img,y,z) +#define cimg_forXYZV(img,x,y,z,v) cimg_forV(img,v) cimg_forXYZ(img,x,y,z) + +#define cimg_for_in1(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), _max##i = (int)(i1)<(int)(bound)?(int)(i1):(int)(bound)-1; i<=_max##i; ++i) +#define cimg_for_inX(img,x0,x1,x) cimg_for_in1((img).width,x0,x1,x) +#define cimg_for_inY(img,y0,y1,y) cimg_for_in1((img).height,y0,y1,y) +#define cimg_for_inZ(img,z0,z1,z) cimg_for_in1((img).depth,z0,z1,z) +#define cimg_for_inV(img,v0,v1,v) cimg_for_in1((img).dim,v0,v1,v) +#define cimg_for_inXY(img,x0,y0,x1,y1,x,y) cimg_for_inY(img,y0,y1,y) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inXZ(img,x0,z0,x1,z1,x,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inXV(img,x0,v0,x1,v1,x,v) cimg_for_inV(img,v0,v1,v) cimg_for_inX(img,x0,x1,x) +#define cimg_for_inYZ(img,y0,z0,y1,z1,y,z) cimg_for_inZ(img,x0,z1,z) cimg_for_inY(img,y0,y1,y) +#define cimg_for_inYV(img,y0,v0,y1,v1,y,v) cimg_for_inV(img,v0,v1,v) cimg_for_inY(img,y0,y1,y) +#define cimg_for_inZV(img,z0,v0,z1,v1,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inZ(img,z0,z1,z) +#define cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inXY(img,x0,y0,x1,y1,x,y) +#define cimg_for_inXYV(img,x0,y0,v0,x1,y1,v1,x,y,v) cimg_for_inV(img,v0,v1,v) cimg_for_inXY(img,x0,y0,x1,y1,x,y) +#define cimg_for_inXZV(img,x0,z0,v0,x1,z1,v1,x,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inXZ(img,x0,z0,x1,z1,x,z) +#define cimg_for_inYZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inYZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_inXYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) +#define cimg_for_insideX(img,x,n) cimg_for_inX(img,n,(img).width-1-(n),x) +#define cimg_for_insideY(img,y,n) cimg_for_inY(img,n,(img).height-1-(n),y) +#define cimg_for_insideZ(img,z,n) cimg_for_inZ(img,n,(img).depth-1-(n),z) +#define cimg_for_insideV(img,v,n) cimg_for_inV(img,n,(img).dim-1-(n),v) +#define cimg_for_insideXY(img,x,y,n) cimg_for_inXY(img,n,n,(img).width-1-(n),(img).height-1-(n),x,y) +#define cimg_for_insideXYZ(img,x,y,z,n) cimg_for_inXYZ(img,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),x,y,z) +#define cimg_for_insideXYZV(img,x,y,z,v,n) cimg_for_inXYZ(img,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),x,y,z) + +#define cimg_for_out1(boundi,i0,i1,i) \ + for (int i = (int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); ++i, i = i==(int)(i0)?(int)(i1)+1:i) +#define cimg_for_out2(boundi,boundj,i0,j0,i1,j1,i,j) \ + for (int j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \ + ++i, i = _n1j?i:(i==(int)(i0)?(int)(i1)+1:i)) +#define cimg_for_out3(boundi,boundj,boundk,i0,j0,k0,i1,j1,k1,i,j,k) \ + for (int k = 0; k<(int)(boundk); ++k) \ + for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \ + ++i, i = _n1j || _n1k?i:(i==(int)(i0)?(int)(i1)+1:i)) +#define cimg_for_out4(boundi,boundj,boundk,boundl,i0,j0,k0,l0,i1,j1,k1,l1,i,j,k,l) \ + for (int l = 0; l<(int)(boundl); ++l) \ + for (int _n1l = (int)(l<(int)(l0) || l>(int)(l1)), k = 0; k<(int)(boundk); ++k) \ + for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \ + for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k || _n1l?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \ + ++i, i = _n1j || _n1k || _n1l?i:(i==(int)(i0)?(int)(i1)+1:i)) +#define cimg_for_outX(img,x0,x1,x) cimg_for_out1((img).width,x0,x1,x) +#define cimg_for_outY(img,y0,y1,y) cimg_for_out1((img).height,y0,y1,y) +#define cimg_for_outZ(img,z0,z1,z) cimg_for_out1((img).depth,z0,z1,z) +#define cimg_for_outV(img,v0,v1,v) cimg_for_out1((img).dim,v0,v1,v) +#define cimg_for_outXY(img,x0,y0,x1,y1,x,y) cimg_for_out2((img).width,(img).height,x0,y0,x1,y1,x,y) +#define cimg_for_outXZ(img,x0,z0,x1,z1,x,z) cimg_for_out2((img).width,(img).depth,x0,z0,x1,z1,x,z) +#define cimg_for_outXV(img,x0,v0,x1,v1,x,v) cimg_for_out2((img).width,(img).dim,x0,v0,x1,v1,x,v) +#define cimg_for_outYZ(img,y0,z0,y1,z1,y,z) cimg_for_out2((img).height,(img).depth,y0,z0,y1,z1,y,z) +#define cimg_for_outYV(img,y0,v0,y1,v1,y,v) cimg_for_out2((img).height,(img).dim,y0,v0,y1,v1,y,v) +#define cimg_for_outZV(img,z0,v0,z1,v1,z,v) cimg_for_out2((img).depth,(img).dim,z0,v0,z1,v1,z,v) +#define cimg_for_outXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_out3((img).width,(img).height,(img).depth,x0,y0,z0,x1,y1,z1,x,y,z) +#define cimg_for_outXYV(img,x0,y0,v0,x1,y1,v1,x,y,v) cimg_for_out3((img).width,(img).height,(img).dim,x0,y0,v0,x1,y1,v1,x,y,v) +#define cimg_for_outXZV(img,x0,z0,v0,x1,z1,v1,x,z,v) cimg_for_out3((img).width,(img).depth,(img).dim,x0,z0,v0,x1,z1,v1,x,z,v) +#define cimg_for_outYZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_out3((img).height,(img).depth,(img).dim,y0,z0,v0,y1,z1,v1,y,z,v) +#define cimg_for_outXYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) \ + cimg_for_out4((img).width,(img).height,(img).depth,(img).dim,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) +#define cimg_for_borderX(img,x,n) cimg_for_outX(img,n,(img).width-1-(n),x) +#define cimg_for_borderY(img,y,n) cimg_for_outY(img,n,(img).height-1-(n),y) +#define cimg_for_borderZ(img,z,n) cimg_for_outZ(img,n,(img).depth-1-(n),z) +#define cimg_for_borderV(img,v,n) cimg_for_outV(img,n,(img).dim-1-(n),v) +#define cimg_for_borderXY(img,x,y,n) cimg_for_outXY(img,n,n,(img).width-1-(n),(img).height-1-(n),x,y) +#define cimg_for_borderXYZ(img,x,y,z,n) cimg_for_outXYZ(img,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),x,y,z) +#define cimg_for_borderXYZV(img,x,y,z,v,n) \ + cimg_for_outXYZV(img,n,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),(img).dim-1-(n),x,y,z,v) + +#define cimg_for_spiralXY(img,x,y) \ + for (int x = 0, y = 0, _n1##x = 1, _n1##y = (int)((img).width*(img).height); _n1##y; \ + --_n1##y, _n1##x += (_n1##x>>2)-((!(_n1##x&3)?--y:((_n1##x&3)==1?(img).width-1-++x:((_n1##x&3)==2?(img).height-1-++y:--x))))?0:1) + +#define cimg_for_lineXY(x,y,x0,y0,x1,y1) \ + for (int x = (int)(x0), y = (int)(y0), _sx = 1, _sy = 1, _steep = 0, \ + _dx=(x1)>(x0)?(int)(x1)-(int)(x0):(_sx=-1,(int)(x0)-(int)(x1)), \ + _dy=(y1)>(y0)?(int)(y1)-(int)(y0):(_sy=-1,(int)(y0)-(int)(y1)), \ + _counter = _dx, \ + _err = _dx>_dy?(_dy>>1):((_steep=1),(_counter=_dy),(_dx>>1)); \ + _counter>=0; \ + --_counter, x+=_steep? \ + (y+=_sy,(_err-=_dx)<0?_err+=_dy,_sx:0): \ + (y+=(_err-=_dy)<0?_err+=_dx,_sy:0,_sx)) + +#define cimg_for2(bound,i) \ + for (int i = 0, _n1##i = 1>=(bound)?(int)(bound)-1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + ++i, ++_n1##i) +#define cimg_for2X(img,x) cimg_for2((img).width,x) +#define cimg_for2Y(img,y) cimg_for2((img).height,y) +#define cimg_for2Z(img,z) cimg_for2((img).depth,z) +#define cimg_for2V(img,v) cimg_for2((img).dim,v) +#define cimg_for2XY(img,x,y) cimg_for2Y(img,y) cimg_for2X(img,x) +#define cimg_for2XZ(img,x,z) cimg_for2Z(img,z) cimg_for2X(img,x) +#define cimg_for2XV(img,x,v) cimg_for2V(img,v) cimg_for2X(img,x) +#define cimg_for2YZ(img,y,z) cimg_for2Z(img,z) cimg_for2Y(img,y) +#define cimg_for2YV(img,y,v) cimg_for2V(img,v) cimg_for2Y(img,y) +#define cimg_for2ZV(img,z,v) cimg_for2V(img,v) cimg_for2Z(img,z) +#define cimg_for2XYZ(img,x,y,z) cimg_for2Z(img,z) cimg_for2XY(img,x,y) +#define cimg_for2XZV(img,x,z,v) cimg_for2V(img,v) cimg_for2XZ(img,x,z) +#define cimg_for2YZV(img,y,z,v) cimg_for2V(img,v) cimg_for2YZ(img,y,z) +#define cimg_for2XYZV(img,x,y,z,v) cimg_for2V(img,v) cimg_for2XYZ(img,x,y,z) + +#define cimg_for_in2(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1; \ + i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \ + ++i, ++_n1##i) +#define cimg_for_in2X(img,x0,x1,x) cimg_for_in2((img).width,x0,x1,x) +#define cimg_for_in2Y(img,y0,y1,y) cimg_for_in2((img).height,y0,y1,y) +#define cimg_for_in2Z(img,z0,z1,z) cimg_for_in2((img).depth,z0,z1,z) +#define cimg_for_in2V(img,v0,v1,v) cimg_for_in2((img).dim,v0,v1,v) +#define cimg_for_in2XY(img,x0,y0,x1,y1,x,y) cimg_for_in2Y(img,y0,y1,y) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2XZ(img,x0,z0,x1,z1,x,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2XV(img,x0,v0,x1,v1,x,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2X(img,x0,x1,x) +#define cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2Y(img,y0,y1,y) +#define cimg_for_in2YV(img,y0,v0,y1,v1,y,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2Y(img,y0,y1,y) +#define cimg_for_in2ZV(img,z0,v0,z1,v1,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2Z(img,z0,z1,z) +#define cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in2XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in2YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in2XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound)-1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i) +#define cimg_for3X(img,x) cimg_for3((img).width,x) +#define cimg_for3Y(img,y) cimg_for3((img).height,y) +#define cimg_for3Z(img,z) cimg_for3((img).depth,z) +#define cimg_for3V(img,v) cimg_for3((img).dim,v) +#define cimg_for3XY(img,x,y) cimg_for3Y(img,y) cimg_for3X(img,x) +#define cimg_for3XZ(img,x,z) cimg_for3Z(img,z) cimg_for3X(img,x) +#define cimg_for3XV(img,x,v) cimg_for3V(img,v) cimg_for3X(img,x) +#define cimg_for3YZ(img,y,z) cimg_for3Z(img,z) cimg_for3Y(img,y) +#define cimg_for3YV(img,y,v) cimg_for3V(img,v) cimg_for3Y(img,y) +#define cimg_for3ZV(img,z,v) cimg_for3V(img,v) cimg_for3Z(img,z) +#define cimg_for3XYZ(img,x,y,z) cimg_for3Z(img,z) cimg_for3XY(img,x,y) +#define cimg_for3XZV(img,x,z,v) cimg_for3V(img,v) cimg_for3XZ(img,x,z) +#define cimg_for3YZV(img,y,z,v) cimg_for3V(img,v) cimg_for3YZ(img,y,z) +#define cimg_for3XYZV(img,x,y,z,v) cimg_for3V(img,v) cimg_for3XYZ(img,x,y,z) + +#define cimg_for_in3(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p1##i = i-1<0?0:i-1, \ + _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1; \ + i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \ + _p1##i = i++, ++_n1##i) +#define cimg_for_in3X(img,x0,x1,x) cimg_for_in3((img).width,x0,x1,x) +#define cimg_for_in3Y(img,y0,y1,y) cimg_for_in3((img).height,y0,y1,y) +#define cimg_for_in3Z(img,z0,z1,z) cimg_for_in3((img).depth,z0,z1,z) +#define cimg_for_in3V(img,v0,v1,v) cimg_for_in3((img).dim,v0,v1,v) +#define cimg_for_in3XY(img,x0,y0,x1,y1,x,y) cimg_for_in3Y(img,y0,y1,y) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3XZ(img,x0,z0,x1,z1,x,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3XV(img,x0,v0,x1,v1,x,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3X(img,x0,x1,x) +#define cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3Y(img,y0,y1,y) +#define cimg_for_in3YV(img,y0,v0,y1,v1,y,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3Y(img,y0,y1,y) +#define cimg_for_in3ZV(img,z0,v0,z1,v1,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3Z(img,z0,z1,z) +#define cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in3XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in3YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in3XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for4(bound,i) \ + for (int i = 0, _p1##i = 0, _n1##i = 1>=(bound)?(int)(bound)-1:1, \ + _n2##i = 2>=(bound)?(int)(bound)-1:2; \ + _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \ + _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for4X(img,x) cimg_for4((img).width,x) +#define cimg_for4Y(img,y) cimg_for4((img).height,y) +#define cimg_for4Z(img,z) cimg_for4((img).depth,z) +#define cimg_for4V(img,v) cimg_for4((img).dim,v) +#define cimg_for4XY(img,x,y) cimg_for4Y(img,y) cimg_for4X(img,x) +#define cimg_for4XZ(img,x,z) cimg_for4Z(img,z) cimg_for4X(img,x) +#define cimg_for4XV(img,x,v) cimg_for4V(img,v) cimg_for4X(img,x) +#define cimg_for4YZ(img,y,z) cimg_for4Z(img,z) cimg_for4Y(img,y) +#define cimg_for4YV(img,y,v) cimg_for4V(img,v) cimg_for4Y(img,y) +#define cimg_for4ZV(img,z,v) cimg_for4V(img,v) cimg_for4Z(img,z) +#define cimg_for4XYZ(img,x,y,z) cimg_for4Z(img,z) cimg_for4XY(img,x,y) +#define cimg_for4XZV(img,x,z,v) cimg_for4V(img,v) cimg_for4XZ(img,x,z) +#define cimg_for4YZV(img,y,z,v) cimg_for4V(img,v) cimg_for4YZ(img,y,z) +#define cimg_for4XYZV(img,x,y,z,v) cimg_for4V(img,v) cimg_for4XYZ(img,x,y,z) + +#define cimg_for_in4(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p1##i = i-1<0?0:i-1, \ + _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \ + _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2; \ + i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \ + _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for_in4X(img,x0,x1,x) cimg_for_in4((img).width,x0,x1,x) +#define cimg_for_in4Y(img,y0,y1,y) cimg_for_in4((img).height,y0,y1,y) +#define cimg_for_in4Z(img,z0,z1,z) cimg_for_in4((img).depth,z0,z1,z) +#define cimg_for_in4V(img,v0,v1,v) cimg_for_in4((img).dim,v0,v1,v) +#define cimg_for_in4XY(img,x0,y0,x1,y1,x,y) cimg_for_in4Y(img,y0,y1,y) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4XZ(img,x0,z0,x1,z1,x,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4XV(img,x0,v0,x1,v1,x,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4X(img,x0,x1,x) +#define cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4Y(img,y0,y1,y) +#define cimg_for_in4YV(img,y0,v0,y1,v1,y,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4Y(img,y0,y1,y) +#define cimg_for_in4ZV(img,z0,v0,z1,v1,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4Z(img,z0,z1,z) +#define cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in4XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in4YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in4XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for5(bound,i) \ + for (int i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound)-1:1, \ + _n2##i = 2>=(bound)?(int)(bound)-1:2; \ + _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for5X(img,x) cimg_for5((img).width,x) +#define cimg_for5Y(img,y) cimg_for5((img).height,y) +#define cimg_for5Z(img,z) cimg_for5((img).depth,z) +#define cimg_for5V(img,v) cimg_for5((img).dim,v) +#define cimg_for5XY(img,x,y) cimg_for5Y(img,y) cimg_for5X(img,x) +#define cimg_for5XZ(img,x,z) cimg_for5Z(img,z) cimg_for5X(img,x) +#define cimg_for5XV(img,x,v) cimg_for5V(img,v) cimg_for5X(img,x) +#define cimg_for5YZ(img,y,z) cimg_for5Z(img,z) cimg_for5Y(img,y) +#define cimg_for5YV(img,y,v) cimg_for5V(img,v) cimg_for5Y(img,y) +#define cimg_for5ZV(img,z,v) cimg_for5V(img,v) cimg_for5Z(img,z) +#define cimg_for5XYZ(img,x,y,z) cimg_for5Z(img,z) cimg_for5XY(img,x,y) +#define cimg_for5XZV(img,x,z,v) cimg_for5V(img,v) cimg_for5XZ(img,x,z) +#define cimg_for5YZV(img,y,z,v) cimg_for5V(img,v) cimg_for5YZ(img,y,z) +#define cimg_for5XYZV(img,x,y,z,v) cimg_for5V(img,v) cimg_for5XYZ(img,x,y,z) + +#define cimg_for_in5(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p2##i = i-2<0?0:i-2, \ + _p1##i = i-1<0?0:i-1, \ + _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \ + _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2; \ + i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i) +#define cimg_for_in5X(img,x0,x1,x) cimg_for_in5((img).width,x0,x1,x) +#define cimg_for_in5Y(img,y0,y1,y) cimg_for_in5((img).height,y0,y1,y) +#define cimg_for_in5Z(img,z0,z1,z) cimg_for_in5((img).depth,z0,z1,z) +#define cimg_for_in5V(img,v0,v1,v) cimg_for_in5((img).dim,v0,v1,v) +#define cimg_for_in5XY(img,x0,y0,x1,y1,x,y) cimg_for_in5Y(img,y0,y1,y) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5XZ(img,x0,z0,x1,z1,x,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5XV(img,x0,v0,x1,v1,x,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5X(img,x0,x1,x) +#define cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5Y(img,y0,y1,y) +#define cimg_for_in5YV(img,y0,v0,y1,v1,y,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5Y(img,y0,y1,y) +#define cimg_for_in5ZV(img,z0,v0,z1,v1,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5Z(img,z0,z1,z) +#define cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in5XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in5YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in5XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for6(bound,i) \ + for (int i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound)-1:1, \ + _n2##i = 2>=(bound)?(int)(bound)-1:2, \ + _n3##i = 3>=(bound)?(int)(bound)-1:3; \ + _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for6X(img,x) cimg_for6((img).width,x) +#define cimg_for6Y(img,y) cimg_for6((img).height,y) +#define cimg_for6Z(img,z) cimg_for6((img).depth,z) +#define cimg_for6V(img,v) cimg_for6((img).dim,v) +#define cimg_for6XY(img,x,y) cimg_for6Y(img,y) cimg_for6X(img,x) +#define cimg_for6XZ(img,x,z) cimg_for6Z(img,z) cimg_for6X(img,x) +#define cimg_for6XV(img,x,v) cimg_for6V(img,v) cimg_for6X(img,x) +#define cimg_for6YZ(img,y,z) cimg_for6Z(img,z) cimg_for6Y(img,y) +#define cimg_for6YV(img,y,v) cimg_for6V(img,v) cimg_for6Y(img,y) +#define cimg_for6ZV(img,z,v) cimg_for6V(img,v) cimg_for6Z(img,z) +#define cimg_for6XYZ(img,x,y,z) cimg_for6Z(img,z) cimg_for6XY(img,x,y) +#define cimg_for6XZV(img,x,z,v) cimg_for6V(img,v) cimg_for6XZ(img,x,z) +#define cimg_for6YZV(img,y,z,v) cimg_for6V(img,v) cimg_for6YZ(img,y,z) +#define cimg_for6XYZV(img,x,y,z,v) cimg_for6V(img,v) cimg_for6XYZ(img,x,y,z) + +#define cimg_for_in6(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p2##i = i-2<0?0:i-2, \ + _p1##i = i-1<0?0:i-1, \ + _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \ + _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \ + _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3; \ + i<=(int)(i1) && (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \ + _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for_in6X(img,x0,x1,x) cimg_for_in6((img).width,x0,x1,x) +#define cimg_for_in6Y(img,y0,y1,y) cimg_for_in6((img).height,y0,y1,y) +#define cimg_for_in6Z(img,z0,z1,z) cimg_for_in6((img).depth,z0,z1,z) +#define cimg_for_in6V(img,v0,v1,v) cimg_for_in6((img).dim,v0,v1,v) +#define cimg_for_in6XY(img,x0,y0,x1,y1,x,y) cimg_for_in6Y(img,y0,y1,y) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6XZ(img,x0,z0,x1,z1,x,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6XV(img,x0,v0,x1,v1,x,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6X(img,x0,x1,x) +#define cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6Y(img,y0,y1,y) +#define cimg_for_in6YV(img,y0,v0,y1,v1,y,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6Y(img,y0,y1,y) +#define cimg_for_in6ZV(img,z0,v0,z1,v1,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6Z(img,z0,z1,z) +#define cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in6XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in6YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in6XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for7(bound,i) \ + for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound)-1:1, \ + _n2##i = 2>=(bound)?(int)(bound)-1:2, \ + _n3##i = 3>=(bound)?(int)(bound)-1:3; \ + _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for7X(img,x) cimg_for7((img).width,x) +#define cimg_for7Y(img,y) cimg_for7((img).height,y) +#define cimg_for7Z(img,z) cimg_for7((img).depth,z) +#define cimg_for7V(img,v) cimg_for7((img).dim,v) +#define cimg_for7XY(img,x,y) cimg_for7Y(img,y) cimg_for7X(img,x) +#define cimg_for7XZ(img,x,z) cimg_for7Z(img,z) cimg_for7X(img,x) +#define cimg_for7XV(img,x,v) cimg_for7V(img,v) cimg_for7X(img,x) +#define cimg_for7YZ(img,y,z) cimg_for7Z(img,z) cimg_for7Y(img,y) +#define cimg_for7YV(img,y,v) cimg_for7V(img,v) cimg_for7Y(img,y) +#define cimg_for7ZV(img,z,v) cimg_for7V(img,v) cimg_for7Z(img,z) +#define cimg_for7XYZ(img,x,y,z) cimg_for7Z(img,z) cimg_for7XY(img,x,y) +#define cimg_for7XZV(img,x,z,v) cimg_for7V(img,v) cimg_for7XZ(img,x,z) +#define cimg_for7YZV(img,y,z,v) cimg_for7V(img,v) cimg_for7YZ(img,y,z) +#define cimg_for7XYZV(img,x,y,z,v) cimg_for7V(img,v) cimg_for7XYZ(img,x,y,z) + +#define cimg_for_in7(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p3##i = i-3<0?0:i-3, \ + _p2##i = i-2<0?0:i-2, \ + _p1##i = i-1<0?0:i-1, \ + _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \ + _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \ + _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3; \ + i<=(int)(i1) && (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i) +#define cimg_for_in7X(img,x0,x1,x) cimg_for_in7((img).width,x0,x1,x) +#define cimg_for_in7Y(img,y0,y1,y) cimg_for_in7((img).height,y0,y1,y) +#define cimg_for_in7Z(img,z0,z1,z) cimg_for_in7((img).depth,z0,z1,z) +#define cimg_for_in7V(img,v0,v1,v) cimg_for_in7((img).dim,v0,v1,v) +#define cimg_for_in7XY(img,x0,y0,x1,y1,x,y) cimg_for_in7Y(img,y0,y1,y) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7XZ(img,x0,z0,x1,z1,x,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7XV(img,x0,v0,x1,v1,x,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7X(img,x0,x1,x) +#define cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7Y(img,y0,y1,y) +#define cimg_for_in7YV(img,y0,v0,y1,v1,y,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7Y(img,y0,y1,y) +#define cimg_for_in7ZV(img,z0,v0,z1,v1,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7Z(img,z0,z1,z) +#define cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in7XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in7YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in7XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for8(bound,i) \ + for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound)-1:1, \ + _n2##i = 2>=(bound)?(int)(bound)-1:2, \ + _n3##i = 3>=(bound)?(int)(bound)-1:3, \ + _n4##i = 4>=(bound)?(int)(bound)-1:4; \ + _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for8X(img,x) cimg_for8((img).width,x) +#define cimg_for8Y(img,y) cimg_for8((img).height,y) +#define cimg_for8Z(img,z) cimg_for8((img).depth,z) +#define cimg_for8V(img,v) cimg_for8((img).dim,v) +#define cimg_for8XY(img,x,y) cimg_for8Y(img,y) cimg_for8X(img,x) +#define cimg_for8XZ(img,x,z) cimg_for8Z(img,z) cimg_for8X(img,x) +#define cimg_for8XV(img,x,v) cimg_for8V(img,v) cimg_for8X(img,x) +#define cimg_for8YZ(img,y,z) cimg_for8Z(img,z) cimg_for8Y(img,y) +#define cimg_for8YV(img,y,v) cimg_for8V(img,v) cimg_for8Y(img,y) +#define cimg_for8ZV(img,z,v) cimg_for8V(img,v) cimg_for8Z(img,z) +#define cimg_for8XYZ(img,x,y,z) cimg_for8Z(img,z) cimg_for8XY(img,x,y) +#define cimg_for8XZV(img,x,z,v) cimg_for8V(img,v) cimg_for8XZ(img,x,z) +#define cimg_for8YZV(img,y,z,v) cimg_for8V(img,v) cimg_for8YZ(img,y,z) +#define cimg_for8XYZV(img,x,y,z,v) cimg_for8V(img,v) cimg_for8XYZ(img,x,y,z) + +#define cimg_for_in8(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p3##i = i-3<0?0:i-3, \ + _p2##i = i-2<0?0:i-2, \ + _p1##i = i-1<0?0:i-1, \ + _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \ + _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \ + _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3, \ + _n4##i = i+4>=(int)(bound)?(int)(bound)-1:i+4; \ + i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \ + _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for_in8X(img,x0,x1,x) cimg_for_in8((img).width,x0,x1,x) +#define cimg_for_in8Y(img,y0,y1,y) cimg_for_in8((img).height,y0,y1,y) +#define cimg_for_in8Z(img,z0,z1,z) cimg_for_in8((img).depth,z0,z1,z) +#define cimg_for_in8V(img,v0,v1,v) cimg_for_in8((img).dim,v0,v1,v) +#define cimg_for_in8XY(img,x0,y0,x1,y1,x,y) cimg_for_in8Y(img,y0,y1,y) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8XZ(img,x0,z0,x1,z1,x,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8XV(img,x0,v0,x1,v1,x,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8X(img,x0,x1,x) +#define cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8Y(img,y0,y1,y) +#define cimg_for_in8YV(img,y0,v0,y1,v1,y,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8Y(img,y0,y1,y) +#define cimg_for_in8ZV(img,z0,v0,z1,v1,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8Z(img,z0,z1,z) +#define cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in8XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in8YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in8XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for9(bound,i) \ + for (int i = 0, _p4##i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \ + _n1##i = 1>=(int)(bound)?(int)(bound)-1:1, \ + _n2##i = 2>=(int)(bound)?(int)(bound)-1:2, \ + _n3##i = 3>=(int)(bound)?(int)(bound)-1:3, \ + _n4##i = 4>=(int)(bound)?(int)(bound)-1:4; \ + _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i); \ + _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for9X(img,x) cimg_for9((img).width,x) +#define cimg_for9Y(img,y) cimg_for9((img).height,y) +#define cimg_for9Z(img,z) cimg_for9((img).depth,z) +#define cimg_for9V(img,v) cimg_for9((img).dim,v) +#define cimg_for9XY(img,x,y) cimg_for9Y(img,y) cimg_for9X(img,x) +#define cimg_for9XZ(img,x,z) cimg_for9Z(img,z) cimg_for9X(img,x) +#define cimg_for9XV(img,x,v) cimg_for9V(img,v) cimg_for9X(img,x) +#define cimg_for9YZ(img,y,z) cimg_for9Z(img,z) cimg_for9Y(img,y) +#define cimg_for9YV(img,y,v) cimg_for9V(img,v) cimg_for9Y(img,y) +#define cimg_for9ZV(img,z,v) cimg_for9V(img,v) cimg_for9Z(img,z) +#define cimg_for9XYZ(img,x,y,z) cimg_for9Z(img,z) cimg_for9XY(img,x,y) +#define cimg_for9XZV(img,x,z,v) cimg_for9V(img,v) cimg_for9XZ(img,x,z) +#define cimg_for9YZV(img,y,z,v) cimg_for9V(img,v) cimg_for9YZ(img,y,z) +#define cimg_for9XYZV(img,x,y,z,v) cimg_for9V(img,v) cimg_for9XYZ(img,x,y,z) + +#define cimg_for_in9(bound,i0,i1,i) \ + for (int i = (int)(i0)<0?0:(int)(i0), \ + _p4##i = i-4<0?0:i-4, \ + _p3##i = i-3<0?0:i-3, \ + _p2##i = i-2<0?0:i-2, \ + _p1##i = i-1<0?0:i-1, \ + _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \ + _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \ + _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3, \ + _n4##i = i+4>=(int)(bound)?(int)(bound)-1:i+4; \ + i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \ + i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \ + _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i) +#define cimg_for_in9X(img,x0,x1,x) cimg_for_in9((img).width,x0,x1,x) +#define cimg_for_in9Y(img,y0,y1,y) cimg_for_in9((img).height,y0,y1,y) +#define cimg_for_in9Z(img,z0,z1,z) cimg_for_in9((img).depth,z0,z1,z) +#define cimg_for_in9V(img,v0,v1,v) cimg_for_in9((img).dim,v0,v1,v) +#define cimg_for_in9XY(img,x0,y0,x1,y1,x,y) cimg_for_in9Y(img,y0,y1,y) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9XZ(img,x0,z0,x1,z1,x,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9XV(img,x0,v0,x1,v1,x,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9X(img,x0,x1,x) +#define cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9Y(img,y0,y1,y) +#define cimg_for_in9YV(img,y0,v0,y1,v1,y,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9Y(img,y0,y1,y) +#define cimg_for_in9ZV(img,z0,v0,z1,v1,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9Z(img,z0,z1,z) +#define cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9XY(img,x0,y0,x1,y1,x,y) +#define cimg_for_in9XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9XZ(img,x0,y0,x1,y1,x,z) +#define cimg_for_in9YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) +#define cimg_for_in9XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) + +#define cimg_for2x2(img,x,y,z,v,I) \ + cimg_for2((img).height,y) for (int x = 0, \ + _n1##x = (int)( \ + (I[0] = (img)(0,y,z,v)), \ + (I[2] = (img)(0,_n1##y,z,v)), \ + 1>=(img).width?(int)((img).width)-1:1); \ + (_n1##x<(int)((img).width) && ( \ + (I[1] = (img)(_n1##x,y,z,v)), \ + (I[3] = (img)(_n1##x,_n1##y,z,v)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], \ + I[2] = I[3], \ + ++x, ++_n1##x) + +#define cimg_for_in2x2(img,x0,y0,x1,y1,x,y,z,v,I) \ + cimg_for_in2((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _n1##x = (int)( \ + (I[0] = (img)(x,y,z,v)), \ + (I[2] = (img)(x,_n1##y,z,v)), \ + x+1>=(int)(img).width?(int)((img).width)-1:x+1); \ + x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \ + (I[1] = (img)(_n1##x,y,z,v)), \ + (I[3] = (img)(_n1##x,_n1##y,z,v)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], \ + I[2] = I[3], \ + ++x, ++_n1##x) + +#define cimg_for3x3(img,x,y,z,v,I) \ + cimg_for3((img).height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (img)(0,_p1##y,z,v)), \ + (I[3] = I[4] = (img)(0,y,z,v)), \ + (I[6] = I[7] = (img)(0,_n1##y,z,v)), \ + 1>=(img).width?(int)((img).width)-1:1); \ + (_n1##x<(int)((img).width) && ( \ + (I[2] = (img)(_n1##x,_p1##y,z,v)), \ + (I[5] = (img)(_n1##x,y,z,v)), \ + (I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for_in3x3(img,x0,y0,x1,y1,x,y,z,v,I) \ + cimg_for_in3((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x-1<0?0:x-1, \ + _n1##x = (int)( \ + (I[0] = (img)(_p1##x,_p1##y,z,v)), \ + (I[3] = (img)(_p1##x,y,z,v)), \ + (I[6] = (img)(_p1##x,_n1##y,z,v)), \ + (I[1] = (img)(x,_p1##y,z,v)), \ + (I[4] = (img)(x,y,z,v)), \ + (I[7] = (img)(x,_n1##y,z,v)), \ + x+1>=(int)(img).width?(int)((img).width)-1:x+1); \ + x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \ + (I[2] = (img)(_n1##x,_p1##y,z,v)), \ + (I[5] = (img)(_n1##x,y,z,v)), \ + (I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for4x4(img,x,y,z,v,I) \ + cimg_for4((img).height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = 1>=(img).width?(int)((img).width)-1:1, \ + _n2##x = (int)( \ + (I[0] = I[1] = (img)(0,_p1##y,z,v)), \ + (I[4] = I[5] = (img)(0,y,z,v)), \ + (I[8] = I[9] = (img)(0,_n1##y,z,v)), \ + (I[12] = I[13] = (img)(0,_n2##y,z,v)), \ + (I[2] = (img)(_n1##x,_p1##y,z,v)), \ + (I[6] = (img)(_n1##x,y,z,v)), \ + (I[10] = (img)(_n1##x,_n1##y,z,v)), \ + (I[14] = (img)(_n1##x,_n2##y,z,v)), \ + 2>=(img).width?(int)((img).width)-1:2); \ + (_n2##x<(int)((img).width) && ( \ + (I[3] = (img)(_n2##x,_p1##y,z,v)), \ + (I[7] = (img)(_n2##x,y,z,v)), \ + (I[11] = (img)(_n2##x,_n1##y,z,v)), \ + (I[15] = (img)(_n2##x,_n2##y,z,v)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], \ + I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for_in4x4(img,x0,y0,x1,y1,x,y,z,v,I) \ + cimg_for_in4((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x-1<0?0:x-1, \ + _n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \ + _n2##x = (int)( \ + (I[0] = (img)(_p1##x,_p1##y,z,v)), \ + (I[4] = (img)(_p1##x,y,z,v)), \ + (I[8] = (img)(_p1##x,_n1##y,z,v)), \ + (I[12] = (img)(_p1##x,_n2##y,z,v)), \ + (I[1] = (img)(x,_p1##y,z,v)), \ + (I[5] = (img)(x,y,z,v)), \ + (I[9] = (img)(x,_n1##y,z,v)), \ + (I[13] = (img)(x,_n2##y,z,v)), \ + (I[2] = (img)(_n1##x,_p1##y,z,v)), \ + (I[6] = (img)(_n1##x,y,z,v)), \ + (I[10] = (img)(_n1##x,_n1##y,z,v)), \ + (I[14] = (img)(_n1##x,_n2##y,z,v)), \ + x+2>=(int)(img).width?(int)((img).width)-1:x+2); \ + x<=(int)(x1) && ((_n2##x<(int)((img).width) && ( \ + (I[3] = (img)(_n2##x,_p1##y,z,v)), \ + (I[7] = (img)(_n2##x,y,z,v)), \ + (I[11] = (img)(_n2##x,_n1##y,z,v)), \ + (I[15] = (img)(_n2##x,_n2##y,z,v)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], \ + I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for5x5(img,x,y,z,v,I) \ + cimg_for5((img).height,y) for (int x = 0, \ + _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img).width?(int)((img).width)-1:1, \ + _n2##x = (int)( \ + (I[0] = I[1] = I[2] = (img)(0,_p2##y,z,v)), \ + (I[5] = I[6] = I[7] = (img)(0,_p1##y,z,v)), \ + (I[10] = I[11] = I[12] = (img)(0,y,z,v)), \ + (I[15] = I[16] = I[17] = (img)(0,_n1##y,z,v)), \ + (I[20] = I[21] = I[22] = (img)(0,_n2##y,z,v)), \ + (I[3] = (img)(_n1##x,_p2##y,z,v)), \ + (I[8] = (img)(_n1##x,_p1##y,z,v)), \ + (I[13] = (img)(_n1##x,y,z,v)), \ + (I[18] = (img)(_n1##x,_n1##y,z,v)), \ + (I[23] = (img)(_n1##x,_n2##y,z,v)), \ + 2>=(img).width?(int)((img).width)-1:2); \ + (_n2##x<(int)((img).width) && ( \ + (I[4] = (img)(_n2##x,_p2##y,z,v)), \ + (I[9] = (img)(_n2##x,_p1##y,z,v)), \ + (I[14] = (img)(_n2##x,y,z,v)), \ + (I[19] = (img)(_n2##x,_n1##y,z,v)), \ + (I[24] = (img)(_n2##x,_n2##y,z,v)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \ + I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \ + I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \ + I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \ + I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for_in5x5(img,x0,y0,x1,y1,x,y,z,v,I) \ + cimg_for_in5((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p2##x = x-2<0?0:x-2, \ + _p1##x = x-1<0?0:x-1, \ + _n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \ + _n2##x = (int)( \ + (I[0] = (img)(_p2##x,_p2##y,z,v)), \ + (I[5] = (img)(_p2##x,_p1##y,z,v)), \ + (I[10] = (img)(_p2##x,y,z,v)), \ + (I[15] = (img)(_p2##x,_n1##y,z,v)), \ + (I[20] = (img)(_p2##x,_n2##y,z,v)), \ + (I[1] = (img)(_p1##x,_p2##y,z,v)), \ + (I[6] = (img)(_p1##x,_p1##y,z,v)), \ + (I[11] = (img)(_p1##x,y,z,v)), \ + (I[16] = (img)(_p1##x,_n1##y,z,v)), \ + (I[21] = (img)(_p1##x,_n2##y,z,v)), \ + (I[2] = (img)(x,_p2##y,z,v)), \ + (I[7] = (img)(x,_p1##y,z,v)), \ + (I[12] = (img)(x,y,z,v)), \ + (I[17] = (img)(x,_n1##y,z,v)), \ + (I[22] = (img)(x,_n2##y,z,v)), \ + (I[3] = (img)(_n1##x,_p2##y,z,v)), \ + (I[8] = (img)(_n1##x,_p1##y,z,v)), \ + (I[13] = (img)(_n1##x,y,z,v)), \ + (I[18] = (img)(_n1##x,_n1##y,z,v)), \ + (I[23] = (img)(_n1##x,_n2##y,z,v)), \ + x+2>=(int)(img).width?(int)((img).width)-1:x+2); \ + x<=(int)(x1) && ((_n2##x<(int)((img).width) && ( \ + (I[4] = (img)(_n2##x,_p2##y,z,v)), \ + (I[9] = (img)(_n2##x,_p1##y,z,v)), \ + (I[14] = (img)(_n2##x,y,z,v)), \ + (I[19] = (img)(_n2##x,_n1##y,z,v)), \ + (I[24] = (img)(_n2##x,_n2##y,z,v)),1)) || \ + _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \ + I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \ + I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \ + I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \ + I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x) + +#define cimg_for6x6(img,x,y,z,v,I) \ + cimg_for6((img).height,y) for (int x = 0, \ + _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img).width?(int)((img).width)-1:1, \ + _n2##x = 2>=(img).width?(int)((img).width)-1:2, \ + _n3##x = (int)( \ + (I[0] = I[1] = I[2] = (img)(0,_p2##y,z,v)), \ + (I[6] = I[7] = I[8] = (img)(0,_p1##y,z,v)), \ + (I[12] = I[13] = I[14] = (img)(0,y,z,v)), \ + (I[18] = I[19] = I[20] = (img)(0,_n1##y,z,v)), \ + (I[24] = I[25] = I[26] = (img)(0,_n2##y,z,v)), \ + (I[30] = I[31] = I[32] = (img)(0,_n3##y,z,v)), \ + (I[3] = (img)(_n1##x,_p2##y,z,v)), \ + (I[9] = (img)(_n1##x,_p1##y,z,v)), \ + (I[15] = (img)(_n1##x,y,z,v)), \ + (I[21] = (img)(_n1##x,_n1##y,z,v)), \ + (I[27] = (img)(_n1##x,_n2##y,z,v)), \ + (I[33] = (img)(_n1##x,_n3##y,z,v)), \ + (I[4] = (img)(_n2##x,_p2##y,z,v)), \ + (I[10] = (img)(_n2##x,_p1##y,z,v)), \ + (I[16] = (img)(_n2##x,y,z,v)), \ + (I[22] = (img)(_n2##x,_n1##y,z,v)), \ + (I[28] = (img)(_n2##x,_n2##y,z,v)), \ + (I[34] = (img)(_n2##x,_n3##y,z,v)), \ + 3>=(img).width?(int)((img).width)-1:3); \ + (_n3##x<(int)((img).width) && ( \ + (I[5] = (img)(_n3##x,_p2##y,z,v)), \ + (I[11] = (img)(_n3##x,_p1##y,z,v)), \ + (I[17] = (img)(_n3##x,y,z,v)), \ + (I[23] = (img)(_n3##x,_n1##y,z,v)), \ + (I[29] = (img)(_n3##x,_n2##y,z,v)), \ + (I[35] = (img)(_n3##x,_n3##y,z,v)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \ + I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for_in6x6(img,x0,y0,x1,y1,x,y,z,v,I) \ + cimg_for_in6((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)x0, \ + _p2##x = x-2<0?0:x-2, \ + _p1##x = x-1<0?0:x-1, \ + _n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \ + _n2##x = x+2>=(int)(img).width?(int)((img).width)-1:x+2, \ + _n3##x = (int)( \ + (I[0] = (img)(_p2##x,_p2##y,z,v)), \ + (I[6] = (img)(_p2##x,_p1##y,z,v)), \ + (I[12] = (img)(_p2##x,y,z,v)), \ + (I[18] = (img)(_p2##x,_n1##y,z,v)), \ + (I[24] = (img)(_p2##x,_n2##y,z,v)), \ + (I[30] = (img)(_p2##x,_n3##y,z,v)), \ + (I[1] = (img)(_p1##x,_p2##y,z,v)), \ + (I[7] = (img)(_p1##x,_p1##y,z,v)), \ + (I[13] = (img)(_p1##x,y,z,v)), \ + (I[19] = (img)(_p1##x,_n1##y,z,v)), \ + (I[25] = (img)(_p1##x,_n2##y,z,v)), \ + (I[31] = (img)(_p1##x,_n3##y,z,v)), \ + (I[2] = (img)(x,_p2##y,z,v)), \ + (I[8] = (img)(x,_p1##y,z,v)), \ + (I[14] = (img)(x,y,z,v)), \ + (I[20] = (img)(x,_n1##y,z,v)), \ + (I[26] = (img)(x,_n2##y,z,v)), \ + (I[32] = (img)(x,_n3##y,z,v)), \ + (I[3] = (img)(_n1##x,_p2##y,z,v)), \ + (I[9] = (img)(_n1##x,_p1##y,z,v)), \ + (I[15] = (img)(_n1##x,y,z,v)), \ + (I[21] = (img)(_n1##x,_n1##y,z,v)), \ + (I[27] = (img)(_n1##x,_n2##y,z,v)), \ + (I[33] = (img)(_n1##x,_n3##y,z,v)), \ + (I[4] = (img)(_n2##x,_p2##y,z,v)), \ + (I[10] = (img)(_n2##x,_p1##y,z,v)), \ + (I[16] = (img)(_n2##x,y,z,v)), \ + (I[22] = (img)(_n2##x,_n1##y,z,v)), \ + (I[28] = (img)(_n2##x,_n2##y,z,v)), \ + (I[34] = (img)(_n2##x,_n3##y,z,v)), \ + x+3>=(int)(img).width?(int)((img).width)-1:x+3); \ + x<=(int)(x1) && ((_n3##x<(int)((img).width) && ( \ + (I[5] = (img)(_n3##x,_p2##y,z,v)), \ + (I[11] = (img)(_n3##x,_p1##y,z,v)), \ + (I[17] = (img)(_n3##x,y,z,v)), \ + (I[23] = (img)(_n3##x,_n1##y,z,v)), \ + (I[29] = (img)(_n3##x,_n2##y,z,v)), \ + (I[35] = (img)(_n3##x,_n3##y,z,v)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \ + I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \ + I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \ + _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for7x7(img,x,y,z,v,I) \ + cimg_for7((img).height,y) for (int x = 0, \ + _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=(img).width?(int)((img).width)-1:1, \ + _n2##x = 2>=(img).width?(int)((img).width)-1:2, \ + _n3##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = (img)(0,_p3##y,z,v)), \ + (I[7] = I[8] = I[9] = I[10] = (img)(0,_p2##y,z,v)), \ + (I[14] = I[15] = I[16] = I[17] = (img)(0,_p1##y,z,v)), \ + (I[21] = I[22] = I[23] = I[24] = (img)(0,y,z,v)), \ + (I[28] = I[29] = I[30] = I[31] = (img)(0,_n1##y,z,v)), \ + (I[35] = I[36] = I[37] = I[38] = (img)(0,_n2##y,z,v)), \ + (I[42] = I[43] = I[44] = I[45] = (img)(0,_n3##y,z,v)), \ + (I[4] = (img)(_n1##x,_p3##y,z,v)), \ + (I[11] = (img)(_n1##x,_p2##y,z,v)), \ + (I[18] = (img)(_n1##x,_p1##y,z,v)), \ + (I[25] = (img)(_n1##x,y,z,v)), \ + (I[32] = (img)(_n1##x,_n1##y,z,v)), \ + (I[39] = (img)(_n1##x,_n2##y,z,v)), \ + (I[46] = (img)(_n1##x,_n3##y,z,v)), \ + (I[5] = (img)(_n2##x,_p3##y,z,v)), \ + (I[12] = (img)(_n2##x,_p2##y,z,v)), \ + (I[19] = (img)(_n2##x,_p1##y,z,v)), \ + (I[26] = (img)(_n2##x,y,z,v)), \ + (I[33] = (img)(_n2##x,_n1##y,z,v)), \ + (I[40] = (img)(_n2##x,_n2##y,z,v)), \ + (I[47] = (img)(_n2##x,_n3##y,z,v)), \ + 3>=(img).width?(int)((img).width)-1:3); \ + (_n3##x<(int)((img).width) && ( \ + (I[6] = (img)(_n3##x,_p3##y,z,v)), \ + (I[13] = (img)(_n3##x,_p2##y,z,v)), \ + (I[20] = (img)(_n3##x,_p1##y,z,v)), \ + (I[27] = (img)(_n3##x,y,z,v)), \ + (I[34] = (img)(_n3##x,_n1##y,z,v)), \ + (I[41] = (img)(_n3##x,_n2##y,z,v)), \ + (I[48] = (img)(_n3##x,_n3##y,z,v)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \ + I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \ + I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \ + I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \ + I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \ + I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \ + I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for_in7x7(img,x0,y0,x1,y1,x,y,z,v,I) \ + cimg_for_in7((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p3##x = x-3<0?0:x-3, \ + _p2##x = x-2<0?0:x-2, \ + _p1##x = x-1<0?0:x-1, \ + _n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \ + _n2##x = x+2>=(int)(img).width?(int)((img).width)-1:x+2, \ + _n3##x = (int)( \ + (I[0] = (img)(_p3##x,_p3##y,z,v)), \ + (I[7] = (img)(_p3##x,_p2##y,z,v)), \ + (I[14] = (img)(_p3##x,_p1##y,z,v)), \ + (I[21] = (img)(_p3##x,y,z,v)), \ + (I[28] = (img)(_p3##x,_n1##y,z,v)), \ + (I[35] = (img)(_p3##x,_n2##y,z,v)), \ + (I[42] = (img)(_p3##x,_n3##y,z,v)), \ + (I[1] = (img)(_p2##x,_p3##y,z,v)), \ + (I[8] = (img)(_p2##x,_p2##y,z,v)), \ + (I[15] = (img)(_p2##x,_p1##y,z,v)), \ + (I[22] = (img)(_p2##x,y,z,v)), \ + (I[29] = (img)(_p2##x,_n1##y,z,v)), \ + (I[36] = (img)(_p2##x,_n2##y,z,v)), \ + (I[43] = (img)(_p2##x,_n3##y,z,v)), \ + (I[2] = (img)(_p1##x,_p3##y,z,v)), \ + (I[9] = (img)(_p1##x,_p2##y,z,v)), \ + (I[16] = (img)(_p1##x,_p1##y,z,v)), \ + (I[23] = (img)(_p1##x,y,z,v)), \ + (I[30] = (img)(_p1##x,_n1##y,z,v)), \ + (I[37] = (img)(_p1##x,_n2##y,z,v)), \ + (I[44] = (img)(_p1##x,_n3##y,z,v)), \ + (I[3] = (img)(x,_p3##y,z,v)), \ + (I[10] = (img)(x,_p2##y,z,v)), \ + (I[17] = (img)(x,_p1##y,z,v)), \ + (I[24] = (img)(x,y,z,v)), \ + (I[31] = (img)(x,_n1##y,z,v)), \ + (I[38] = (img)(x,_n2##y,z,v)), \ + (I[45] = (img)(x,_n3##y,z,v)), \ + (I[4] = (img)(_n1##x,_p3##y,z,v)), \ + (I[11] = (img)(_n1##x,_p2##y,z,v)), \ + (I[18] = (img)(_n1##x,_p1##y,z,v)), \ + (I[25] = (img)(_n1##x,y,z,v)), \ + (I[32] = (img)(_n1##x,_n1##y,z,v)), \ + (I[39] = (img)(_n1##x,_n2##y,z,v)), \ + (I[46] = (img)(_n1##x,_n3##y,z,v)), \ + (I[5] = (img)(_n2##x,_p3##y,z,v)), \ + (I[12] = (img)(_n2##x,_p2##y,z,v)), \ + (I[19] = (img)(_n2##x,_p1##y,z,v)), \ + (I[26] = (img)(_n2##x,y,z,v)), \ + (I[33] = (img)(_n2##x,_n1##y,z,v)), \ + (I[40] = (img)(_n2##x,_n2##y,z,v)), \ + (I[47] = (img)(_n2##x,_n3##y,z,v)), \ + x+3>=(int)(img).width?(int)((img).width)-1:x+3); \ + x<=(int)(x1) && ((_n3##x<(int)((img).width) && ( \ + (I[6] = (img)(_n3##x,_p3##y,z,v)), \ + (I[13] = (img)(_n3##x,_p2##y,z,v)), \ + (I[20] = (img)(_n3##x,_p1##y,z,v)), \ + (I[27] = (img)(_n3##x,y,z,v)), \ + (I[34] = (img)(_n3##x,_n1##y,z,v)), \ + (I[41] = (img)(_n3##x,_n2##y,z,v)), \ + (I[48] = (img)(_n3##x,_n3##y,z,v)),1)) || \ + _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \ + I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \ + I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \ + I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \ + I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \ + I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \ + I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x) + +#define cimg_for8x8(img,x,y,z,v,I) \ + cimg_for8((img).height,y) for (int x = 0, \ + _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=((img).width)?(int)((img).width)-1:1, \ + _n2##x = 2>=((img).width)?(int)((img).width)-1:2, \ + _n3##x = 3>=((img).width)?(int)((img).width)-1:3, \ + _n4##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = (img)(0,_p3##y,z,v)), \ + (I[8] = I[9] = I[10] = I[11] = (img)(0,_p2##y,z,v)), \ + (I[16] = I[17] = I[18] = I[19] = (img)(0,_p1##y,z,v)), \ + (I[24] = I[25] = I[26] = I[27] = (img)(0,y,z,v)), \ + (I[32] = I[33] = I[34] = I[35] = (img)(0,_n1##y,z,v)), \ + (I[40] = I[41] = I[42] = I[43] = (img)(0,_n2##y,z,v)), \ + (I[48] = I[49] = I[50] = I[51] = (img)(0,_n3##y,z,v)), \ + (I[56] = I[57] = I[58] = I[59] = (img)(0,_n4##y,z,v)), \ + (I[4] = (img)(_n1##x,_p3##y,z,v)), \ + (I[12] = (img)(_n1##x,_p2##y,z,v)), \ + (I[20] = (img)(_n1##x,_p1##y,z,v)), \ + (I[28] = (img)(_n1##x,y,z,v)), \ + (I[36] = (img)(_n1##x,_n1##y,z,v)), \ + (I[44] = (img)(_n1##x,_n2##y,z,v)), \ + (I[52] = (img)(_n1##x,_n3##y,z,v)), \ + (I[60] = (img)(_n1##x,_n4##y,z,v)), \ + (I[5] = (img)(_n2##x,_p3##y,z,v)), \ + (I[13] = (img)(_n2##x,_p2##y,z,v)), \ + (I[21] = (img)(_n2##x,_p1##y,z,v)), \ + (I[29] = (img)(_n2##x,y,z,v)), \ + (I[37] = (img)(_n2##x,_n1##y,z,v)), \ + (I[45] = (img)(_n2##x,_n2##y,z,v)), \ + (I[53] = (img)(_n2##x,_n3##y,z,v)), \ + (I[61] = (img)(_n2##x,_n4##y,z,v)), \ + (I[6] = (img)(_n3##x,_p3##y,z,v)), \ + (I[14] = (img)(_n3##x,_p2##y,z,v)), \ + (I[22] = (img)(_n3##x,_p1##y,z,v)), \ + (I[30] = (img)(_n3##x,y,z,v)), \ + (I[38] = (img)(_n3##x,_n1##y,z,v)), \ + (I[46] = (img)(_n3##x,_n2##y,z,v)), \ + (I[54] = (img)(_n3##x,_n3##y,z,v)), \ + (I[62] = (img)(_n3##x,_n4##y,z,v)), \ + 4>=((img).width)?(int)((img).width)-1:4); \ + (_n4##x<(int)((img).width) && ( \ + (I[7] = (img)(_n4##x,_p3##y,z,v)), \ + (I[15] = (img)(_n4##x,_p2##y,z,v)), \ + (I[23] = (img)(_n4##x,_p1##y,z,v)), \ + (I[31] = (img)(_n4##x,y,z,v)), \ + (I[39] = (img)(_n4##x,_n1##y,z,v)), \ + (I[47] = (img)(_n4##x,_n2##y,z,v)), \ + (I[55] = (img)(_n4##x,_n3##y,z,v)), \ + (I[63] = (img)(_n4##x,_n4##y,z,v)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for_in8x8(img,x0,y0,x1,y1,x,y,z,v,I) \ + cimg_for_in8((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p3##x = x-3<0?0:x-3, \ + _p2##x = x-2<0?0:x-2, \ + _p1##x = x-1<0?0:x-1, \ + _n1##x = x+1>=(int)((img).width)?(int)((img).width)-1:x+1, \ + _n2##x = x+2>=(int)((img).width)?(int)((img).width)-1:x+2, \ + _n3##x = x+3>=(int)((img).width)?(int)((img).width)-1:x+3, \ + _n4##x = (int)( \ + (I[0] = (img)(_p3##x,_p3##y,z,v)), \ + (I[8] = (img)(_p3##x,_p2##y,z,v)), \ + (I[16] = (img)(_p3##x,_p1##y,z,v)), \ + (I[24] = (img)(_p3##x,y,z,v)), \ + (I[32] = (img)(_p3##x,_n1##y,z,v)), \ + (I[40] = (img)(_p3##x,_n2##y,z,v)), \ + (I[48] = (img)(_p3##x,_n3##y,z,v)), \ + (I[56] = (img)(_p3##x,_n4##y,z,v)), \ + (I[1] = (img)(_p2##x,_p3##y,z,v)), \ + (I[9] = (img)(_p2##x,_p2##y,z,v)), \ + (I[17] = (img)(_p2##x,_p1##y,z,v)), \ + (I[25] = (img)(_p2##x,y,z,v)), \ + (I[33] = (img)(_p2##x,_n1##y,z,v)), \ + (I[41] = (img)(_p2##x,_n2##y,z,v)), \ + (I[49] = (img)(_p2##x,_n3##y,z,v)), \ + (I[57] = (img)(_p2##x,_n4##y,z,v)), \ + (I[2] = (img)(_p1##x,_p3##y,z,v)), \ + (I[10] = (img)(_p1##x,_p2##y,z,v)), \ + (I[18] = (img)(_p1##x,_p1##y,z,v)), \ + (I[26] = (img)(_p1##x,y,z,v)), \ + (I[34] = (img)(_p1##x,_n1##y,z,v)), \ + (I[42] = (img)(_p1##x,_n2##y,z,v)), \ + (I[50] = (img)(_p1##x,_n3##y,z,v)), \ + (I[58] = (img)(_p1##x,_n4##y,z,v)), \ + (I[3] = (img)(x,_p3##y,z,v)), \ + (I[11] = (img)(x,_p2##y,z,v)), \ + (I[19] = (img)(x,_p1##y,z,v)), \ + (I[27] = (img)(x,y,z,v)), \ + (I[35] = (img)(x,_n1##y,z,v)), \ + (I[43] = (img)(x,_n2##y,z,v)), \ + (I[51] = (img)(x,_n3##y,z,v)), \ + (I[59] = (img)(x,_n4##y,z,v)), \ + (I[4] = (img)(_n1##x,_p3##y,z,v)), \ + (I[12] = (img)(_n1##x,_p2##y,z,v)), \ + (I[20] = (img)(_n1##x,_p1##y,z,v)), \ + (I[28] = (img)(_n1##x,y,z,v)), \ + (I[36] = (img)(_n1##x,_n1##y,z,v)), \ + (I[44] = (img)(_n1##x,_n2##y,z,v)), \ + (I[52] = (img)(_n1##x,_n3##y,z,v)), \ + (I[60] = (img)(_n1##x,_n4##y,z,v)), \ + (I[5] = (img)(_n2##x,_p3##y,z,v)), \ + (I[13] = (img)(_n2##x,_p2##y,z,v)), \ + (I[21] = (img)(_n2##x,_p1##y,z,v)), \ + (I[29] = (img)(_n2##x,y,z,v)), \ + (I[37] = (img)(_n2##x,_n1##y,z,v)), \ + (I[45] = (img)(_n2##x,_n2##y,z,v)), \ + (I[53] = (img)(_n2##x,_n3##y,z,v)), \ + (I[61] = (img)(_n2##x,_n4##y,z,v)), \ + (I[6] = (img)(_n3##x,_p3##y,z,v)), \ + (I[14] = (img)(_n3##x,_p2##y,z,v)), \ + (I[22] = (img)(_n3##x,_p1##y,z,v)), \ + (I[30] = (img)(_n3##x,y,z,v)), \ + (I[38] = (img)(_n3##x,_n1##y,z,v)), \ + (I[46] = (img)(_n3##x,_n2##y,z,v)), \ + (I[54] = (img)(_n3##x,_n3##y,z,v)), \ + (I[62] = (img)(_n3##x,_n4##y,z,v)), \ + x+4>=(int)((img).width)?(int)((img).width)-1:x+4); \ + x<=(int)(x1) && ((_n4##x<(int)((img).width) && ( \ + (I[7] = (img)(_n4##x,_p3##y,z,v)), \ + (I[15] = (img)(_n4##x,_p2##y,z,v)), \ + (I[23] = (img)(_n4##x,_p1##y,z,v)), \ + (I[31] = (img)(_n4##x,y,z,v)), \ + (I[39] = (img)(_n4##x,_n1##y,z,v)), \ + (I[47] = (img)(_n4##x,_n2##y,z,v)), \ + (I[55] = (img)(_n4##x,_n3##y,z,v)), \ + (I[63] = (img)(_n4##x,_n4##y,z,v)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \ + I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \ + I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \ + I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \ + I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \ + I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \ + I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \ + I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \ + _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for9x9(img,x,y,z,v,I) \ + cimg_for9((img).height,y) for (int x = 0, \ + _p4##x = 0, _p3##x = 0, _p2##x = 0, _p1##x = 0, \ + _n1##x = 1>=((img).width)?(int)((img).width)-1:1, \ + _n2##x = 2>=((img).width)?(int)((img).width)-1:2, \ + _n3##x = 3>=((img).width)?(int)((img).width)-1:3, \ + _n4##x = (int)( \ + (I[0] = I[1] = I[2] = I[3] = I[4] = (img)(0,_p4##y,z,v)), \ + (I[9] = I[10] = I[11] = I[12] = I[13] = (img)(0,_p3##y,z,v)), \ + (I[18] = I[19] = I[20] = I[21] = I[22] = (img)(0,_p2##y,z,v)), \ + (I[27] = I[28] = I[29] = I[30] = I[31] = (img)(0,_p1##y,z,v)), \ + (I[36] = I[37] = I[38] = I[39] = I[40] = (img)(0,y,z,v)), \ + (I[45] = I[46] = I[47] = I[48] = I[49] = (img)(0,_n1##y,z,v)), \ + (I[54] = I[55] = I[56] = I[57] = I[58] = (img)(0,_n2##y,z,v)), \ + (I[63] = I[64] = I[65] = I[66] = I[67] = (img)(0,_n3##y,z,v)), \ + (I[72] = I[73] = I[74] = I[75] = I[76] = (img)(0,_n4##y,z,v)), \ + (I[5] = (img)(_n1##x,_p4##y,z,v)), \ + (I[14] = (img)(_n1##x,_p3##y,z,v)), \ + (I[23] = (img)(_n1##x,_p2##y,z,v)), \ + (I[32] = (img)(_n1##x,_p1##y,z,v)), \ + (I[41] = (img)(_n1##x,y,z,v)), \ + (I[50] = (img)(_n1##x,_n1##y,z,v)), \ + (I[59] = (img)(_n1##x,_n2##y,z,v)), \ + (I[68] = (img)(_n1##x,_n3##y,z,v)), \ + (I[77] = (img)(_n1##x,_n4##y,z,v)), \ + (I[6] = (img)(_n2##x,_p4##y,z,v)), \ + (I[15] = (img)(_n2##x,_p3##y,z,v)), \ + (I[24] = (img)(_n2##x,_p2##y,z,v)), \ + (I[33] = (img)(_n2##x,_p1##y,z,v)), \ + (I[42] = (img)(_n2##x,y,z,v)), \ + (I[51] = (img)(_n2##x,_n1##y,z,v)), \ + (I[60] = (img)(_n2##x,_n2##y,z,v)), \ + (I[69] = (img)(_n2##x,_n3##y,z,v)), \ + (I[78] = (img)(_n2##x,_n4##y,z,v)), \ + (I[7] = (img)(_n3##x,_p4##y,z,v)), \ + (I[16] = (img)(_n3##x,_p3##y,z,v)), \ + (I[25] = (img)(_n3##x,_p2##y,z,v)), \ + (I[34] = (img)(_n3##x,_p1##y,z,v)), \ + (I[43] = (img)(_n3##x,y,z,v)), \ + (I[52] = (img)(_n3##x,_n1##y,z,v)), \ + (I[61] = (img)(_n3##x,_n2##y,z,v)), \ + (I[70] = (img)(_n3##x,_n3##y,z,v)), \ + (I[79] = (img)(_n3##x,_n4##y,z,v)), \ + 4>=((img).width)?(int)((img).width)-1:4); \ + (_n4##x<(int)((img).width) && ( \ + (I[8] = (img)(_n4##x,_p4##y,z,v)), \ + (I[17] = (img)(_n4##x,_p3##y,z,v)), \ + (I[26] = (img)(_n4##x,_p2##y,z,v)), \ + (I[35] = (img)(_n4##x,_p1##y,z,v)), \ + (I[44] = (img)(_n4##x,y,z,v)), \ + (I[53] = (img)(_n4##x,_n1##y,z,v)), \ + (I[62] = (img)(_n4##x,_n2##y,z,v)), \ + (I[71] = (img)(_n4##x,_n3##y,z,v)), \ + (I[80] = (img)(_n4##x,_n4##y,z,v)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], \ + I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \ + I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], \ + I[45] = I[46], I[46] = I[47], I[47] = I[48], I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], \ + I[54] = I[55], I[55] = I[56], I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], \ + I[63] = I[64], I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \ + I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], I[79] = I[80], \ + _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for_in9x9(img,x0,y0,x1,y1,x,y,z,v,I) \ + cimg_for_in9((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p4##x = x-4<0?0:x-4, \ + _p3##x = x-3<0?0:x-3, \ + _p2##x = x-2<0?0:x-2, \ + _p1##x = x-1<0?0:x-1, \ + _n1##x = x+1>=(int)((img).width)?(int)((img).width)-1:x+1, \ + _n2##x = x+2>=(int)((img).width)?(int)((img).width)-1:x+2, \ + _n3##x = x+3>=(int)((img).width)?(int)((img).width)-1:x+3, \ + _n4##x = (int)( \ + (I[0] = (img)(_p4##x,_p4##y,z,v)), \ + (I[9] = (img)(_p4##x,_p3##y,z,v)), \ + (I[18] = (img)(_p4##x,_p2##y,z,v)), \ + (I[27] = (img)(_p4##x,_p1##y,z,v)), \ + (I[36] = (img)(_p4##x,y,z,v)), \ + (I[45] = (img)(_p4##x,_n1##y,z,v)), \ + (I[54] = (img)(_p4##x,_n2##y,z,v)), \ + (I[63] = (img)(_p4##x,_n3##y,z,v)), \ + (I[72] = (img)(_p4##x,_n4##y,z,v)), \ + (I[1] = (img)(_p3##x,_p4##y,z,v)), \ + (I[10] = (img)(_p3##x,_p3##y,z,v)), \ + (I[19] = (img)(_p3##x,_p2##y,z,v)), \ + (I[28] = (img)(_p3##x,_p1##y,z,v)), \ + (I[37] = (img)(_p3##x,y,z,v)), \ + (I[46] = (img)(_p3##x,_n1##y,z,v)), \ + (I[55] = (img)(_p3##x,_n2##y,z,v)), \ + (I[64] = (img)(_p3##x,_n3##y,z,v)), \ + (I[73] = (img)(_p3##x,_n4##y,z,v)), \ + (I[2] = (img)(_p2##x,_p4##y,z,v)), \ + (I[11] = (img)(_p2##x,_p3##y,z,v)), \ + (I[20] = (img)(_p2##x,_p2##y,z,v)), \ + (I[29] = (img)(_p2##x,_p1##y,z,v)), \ + (I[38] = (img)(_p2##x,y,z,v)), \ + (I[47] = (img)(_p2##x,_n1##y,z,v)), \ + (I[56] = (img)(_p2##x,_n2##y,z,v)), \ + (I[65] = (img)(_p2##x,_n3##y,z,v)), \ + (I[74] = (img)(_p2##x,_n4##y,z,v)), \ + (I[3] = (img)(_p1##x,_p4##y,z,v)), \ + (I[12] = (img)(_p1##x,_p3##y,z,v)), \ + (I[21] = (img)(_p1##x,_p2##y,z,v)), \ + (I[30] = (img)(_p1##x,_p1##y,z,v)), \ + (I[39] = (img)(_p1##x,y,z,v)), \ + (I[48] = (img)(_p1##x,_n1##y,z,v)), \ + (I[57] = (img)(_p1##x,_n2##y,z,v)), \ + (I[66] = (img)(_p1##x,_n3##y,z,v)), \ + (I[75] = (img)(_p1##x,_n4##y,z,v)), \ + (I[4] = (img)(x,_p4##y,z,v)), \ + (I[13] = (img)(x,_p3##y,z,v)), \ + (I[22] = (img)(x,_p2##y,z,v)), \ + (I[31] = (img)(x,_p1##y,z,v)), \ + (I[40] = (img)(x,y,z,v)), \ + (I[49] = (img)(x,_n1##y,z,v)), \ + (I[58] = (img)(x,_n2##y,z,v)), \ + (I[67] = (img)(x,_n3##y,z,v)), \ + (I[76] = (img)(x,_n4##y,z,v)), \ + (I[5] = (img)(_n1##x,_p4##y,z,v)), \ + (I[14] = (img)(_n1##x,_p3##y,z,v)), \ + (I[23] = (img)(_n1##x,_p2##y,z,v)), \ + (I[32] = (img)(_n1##x,_p1##y,z,v)), \ + (I[41] = (img)(_n1##x,y,z,v)), \ + (I[50] = (img)(_n1##x,_n1##y,z,v)), \ + (I[59] = (img)(_n1##x,_n2##y,z,v)), \ + (I[68] = (img)(_n1##x,_n3##y,z,v)), \ + (I[77] = (img)(_n1##x,_n4##y,z,v)), \ + (I[6] = (img)(_n2##x,_p4##y,z,v)), \ + (I[15] = (img)(_n2##x,_p3##y,z,v)), \ + (I[24] = (img)(_n2##x,_p2##y,z,v)), \ + (I[33] = (img)(_n2##x,_p1##y,z,v)), \ + (I[42] = (img)(_n2##x,y,z,v)), \ + (I[51] = (img)(_n2##x,_n1##y,z,v)), \ + (I[60] = (img)(_n2##x,_n2##y,z,v)), \ + (I[69] = (img)(_n2##x,_n3##y,z,v)), \ + (I[78] = (img)(_n2##x,_n4##y,z,v)), \ + (I[7] = (img)(_n3##x,_p4##y,z,v)), \ + (I[16] = (img)(_n3##x,_p3##y,z,v)), \ + (I[25] = (img)(_n3##x,_p2##y,z,v)), \ + (I[34] = (img)(_n3##x,_p1##y,z,v)), \ + (I[43] = (img)(_n3##x,y,z,v)), \ + (I[52] = (img)(_n3##x,_n1##y,z,v)), \ + (I[61] = (img)(_n3##x,_n2##y,z,v)), \ + (I[70] = (img)(_n3##x,_n3##y,z,v)), \ + (I[79] = (img)(_n3##x,_n4##y,z,v)), \ + x+4>=(int)((img).width)?(int)((img).width)-1:x+4); \ + x<=(int)(x1) && ((_n4##x<(int)((img).width) && ( \ + (I[8] = (img)(_n4##x,_p4##y,z,v)), \ + (I[17] = (img)(_n4##x,_p3##y,z,v)), \ + (I[26] = (img)(_n4##x,_p2##y,z,v)), \ + (I[35] = (img)(_n4##x,_p1##y,z,v)), \ + (I[44] = (img)(_n4##x,y,z,v)), \ + (I[53] = (img)(_n4##x,_n1##y,z,v)), \ + (I[62] = (img)(_n4##x,_n2##y,z,v)), \ + (I[71] = (img)(_n4##x,_n3##y,z,v)), \ + (I[80] = (img)(_n4##x,_n4##y,z,v)),1)) || \ + _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \ + I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], \ + I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \ + I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], \ + I[45] = I[46], I[46] = I[47], I[47] = I[48], I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], \ + I[54] = I[55], I[55] = I[56], I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], \ + I[63] = I[64], I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \ + I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], I[79] = I[80], \ + _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x) + +#define cimg_for2x2x2(img,x,y,z,v,I) \ + cimg_for2((img).depth,z) cimg_for2((img).height,y) for (int x = 0, \ + _n1##x = (int)( \ + (I[0] = (img)(0,y,z,v)), \ + (I[2] = (img)(0,_n1##y,z,v)), \ + (I[4] = (img)(0,y,_n1##z,v)), \ + (I[6] = (img)(0,_n1##y,_n1##z,v)), \ + 1>=(img).width?(int)((img).width)-1:1); \ + (_n1##x<(int)((img).width) && ( \ + (I[1] = (img)(_n1##x,y,z,v)), \ + (I[3] = (img)(_n1##x,_n1##y,z,v)), \ + (I[5] = (img)(_n1##x,y,_n1##z,v)), \ + (I[7] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \ + ++x, ++_n1##x) + +#define cimg_for_in2x2x2(img,x0,y0,z0,x1,y1,z1,x,y,z,v,I) \ + cimg_for_in2((img).depth,z0,z1,z) cimg_for_in2((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _n1##x = (int)( \ + (I[0] = (img)(x,y,z,v)), \ + (I[2] = (img)(x,_n1##y,z,v)), \ + (I[4] = (img)(x,y,_n1##z,v)), \ + (I[6] = (img)(x,_n1##y,_n1##z,v)), \ + x+1>=(int)(img).width?(int)((img).width)-1:x+1); \ + x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \ + (I[1] = (img)(_n1##x,y,z,v)), \ + (I[3] = (img)(_n1##x,_n1##y,z,v)), \ + (I[5] = (img)(_n1##x,y,_n1##z,v)), \ + (I[7] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \ + ++x, ++_n1##x) + +#define cimg_for3x3x3(img,x,y,z,v,I) \ + cimg_for3((img).depth,z) cimg_for3((img).height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (img)(0,_p1##y,_p1##z,v)), \ + (I[3] = I[4] = (img)(0,y,_p1##z,v)), \ + (I[6] = I[7] = (img)(0,_n1##y,_p1##z,v)), \ + (I[9] = I[10] = (img)(0,_p1##y,z,v)), \ + (I[12] = I[13] = (img)(0,y,z,v)), \ + (I[15] = I[16] = (img)(0,_n1##y,z,v)), \ + (I[18] = I[19] = (img)(0,_p1##y,_n1##z,v)), \ + (I[21] = I[22] = (img)(0,y,_n1##z,v)), \ + (I[24] = I[25] = (img)(0,_n1##y,_n1##z,v)), \ + 1>=(img).width?(int)((img).width)-1:1); \ + (_n1##x<(int)((img).width) && ( \ + (I[2] = (img)(_n1##x,_p1##y,_p1##z,v)), \ + (I[5] = (img)(_n1##x,y,_p1##z,v)), \ + (I[8] = (img)(_n1##x,_n1##y,_p1##z,v)), \ + (I[11] = (img)(_n1##x,_p1##y,z,v)), \ + (I[14] = (img)(_n1##x,y,z,v)), \ + (I[17] = (img)(_n1##x,_n1##y,z,v)), \ + (I[20] = (img)(_n1##x,_p1##y,_n1##z,v)), \ + (I[23] = (img)(_n1##x,y,_n1##z,v)), \ + (I[26] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \ + _p1##x = x++, ++_n1##x) + +#define cimg_for_in3x3x3(img,x0,y0,z0,x1,y1,z1,x,y,z,v,I) \ + cimg_for_in3((img).depth,z0,z1,z) cimg_for_in3((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ + _p1##x = x-1<0?0:x-1, \ + _n1##x = (int)( \ + (I[0] = (img)(_p1##x,_p1##y,_p1##z,v)), \ + (I[3] = (img)(_p1##x,y,_p1##z,v)), \ + (I[6] = (img)(_p1##x,_n1##y,_p1##z,v)), \ + (I[9] = (img)(_p1##x,_p1##y,z,v)), \ + (I[12] = (img)(_p1##x,y,z,v)), \ + (I[15] = (img)(_p1##x,_n1##y,z,v)), \ + (I[18] = (img)(_p1##x,_p1##y,_n1##z,v)), \ + (I[21] = (img)(_p1##x,y,_n1##z,v)), \ + (I[24] = (img)(_p1##x,_n1##y,_n1##z,v)), \ + (I[1] = (img)(x,_p1##y,_p1##z,v)), \ + (I[4] = (img)(x,y,_p1##z,v)), \ + (I[7] = (img)(x,_n1##y,_p1##z,v)), \ + (I[10] = (img)(x,_p1##y,z,v)), \ + (I[13] = (img)(x,y,z,v)), \ + (I[16] = (img)(x,_n1##y,z,v)), \ + (I[19] = (img)(x,_p1##y,_n1##z,v)), \ + (I[22] = (img)(x,y,_n1##z,v)), \ + (I[25] = (img)(x,_n1##y,_n1##z,v)), \ + x+1>=(int)(img).width?(int)((img).width)-1:x+1); \ + x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \ + (I[2] = (img)(_n1##x,_p1##y,_p1##z,v)), \ + (I[5] = (img)(_n1##x,y,_p1##z,v)), \ + (I[8] = (img)(_n1##x,_n1##y,_p1##z,v)), \ + (I[11] = (img)(_n1##x,_p1##y,z,v)), \ + (I[14] = (img)(_n1##x,y,z,v)), \ + (I[17] = (img)(_n1##x,_n1##y,z,v)), \ + (I[20] = (img)(_n1##x,_p1##y,_n1##z,v)), \ + (I[23] = (img)(_n1##x,y,_n1##z,v)), \ + (I[26] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \ + x==--_n1##x); \ + I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \ + I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \ + I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \ + _p1##x = x++, ++_n1##x) + +/*------------------------------------------------ + # + # + # Definition of the cimg_library:: namespace + # + # + -------------------------------------------------*/ +//! This namespace encompasses all classes and functions of the %CImg library. +/** + This namespace is defined to avoid functions and class names collisions + that could happen with the include of other C++ header files. + Anyway, it should not happen often and you should reasonnably start most of your + %CImg-based programs with + \code + #include "CImg.h" + using namespace cimg_library; + \endcode + to simplify the declaration of %CImg Library variables afterwards. +**/ +namespace cimg_library { + + // Declare the only four classes of the CImg Library. + // + template struct CImg; + template struct CImgList; + struct CImgDisplay; + struct CImgException; + + // (Pre)declare the cimg namespace. + // This is not the complete namespace declaration. It only contains some + // necessary stuffs to ensure a correct declaration order of classes and functions + // defined afterwards. + // + namespace cimg { + +#ifdef cimg_use_vt100 + const char t_normal[] = { 0x1b,'[','0',';','0',';','0','m','\0' }; + const char t_red[] = { 0x1b,'[','4',';','3','1',';','5','9','m','\0' }; + const char t_bold[] = { 0x1b,'[','1','m','\0' }; + const char t_purple[] = { 0x1b,'[','0',';','3','5',';','5','9','m','\0' }; + const char t_green[] = { 0x1b,'[','0',';','3','2',';','5','9','m','\0' }; +#else + const char t_normal[] = { '\0' }; + const char *const t_red = cimg::t_normal, *const t_bold = cimg::t_normal, + *const t_purple = cimg::t_normal, *const t_green = cimg::t_normal; +#endif + + inline void info(); + + //! Get/set the current CImg exception mode. + /** + The way error messages are handled by CImg can be changed dynamically, using this function. + Possible values are : + - 0 to hide debug messages (quiet mode, but exceptions are still thrown). + - 1 to display debug messages on standard error (console). + - 2 to display debug messages in modal windows (default behavior). + - 3 to do as 1 + add extra warnings (may slow down the code !). + - 4 to do as 2 + add extra warnings (may slow down the code !). + **/ + inline unsigned int& exception_mode() { static unsigned int mode = cimg_debug; return mode; } + + inline int dialog(const char *title, const char *msg, const char *button1_txt="OK", + const char *button2_txt=0, const char *button3_txt=0, + const char *button4_txt=0, const char *button5_txt=0, + const char *button6_txt=0, const bool centering=false); + } + + /*---------------------------------------------- + # + # Definition of the CImgException structures + # + ----------------------------------------------*/ + //! Instances of this class are thrown when errors occur during a %CImg library function call. + /** + \section ex1 Overview + + CImgException is the base class of %CImg exceptions. + Exceptions are thrown by the %CImg Library when an error occured in a %CImg library function call. + CImgException is seldom thrown itself. Children classes that specify the kind of error encountered + are generally used instead. These sub-classes are : + + - \b CImgInstanceException : Thrown when the instance associated to the called %CImg function is not + correctly defined. Generally, this exception is thrown when one tries to process \a empty images. The example + below will throw a \a CImgInstanceException. + \code + CImg img; // Construct an empty image. + img.blur(10); // Try to blur the image. + \endcode + + - \b CImgArgumentException : Thrown when one of the arguments given to the called %CImg function is not correct. + Generally, this exception is thrown when arguments passed to the function are outside an admissible range of values. + The example below will throw a \a CImgArgumentException. + \code + CImg img(100,100,1,3); // Define a 100x100 color image with float pixels. + img = 0; // Try to fill pixels from the 0 pointer (invalid argument to operator=() ). + \endcode + + - \b CImgIOException : Thrown when an error occured when trying to load or save image files. + The example below will throw a \a CImgIOException. + \code + CImg img("file_doesnt_exist.jpg"); // Try to load a file that doesn't exist. + \endcode + + - \b CImgDisplayException : Thrown when an error occured when trying to display an image in a window. + This exception is thrown when image display request cannot be satisfied. + + The parent class CImgException may be thrown itself when errors that cannot be classified in one of + the above type occur. It is recommended not to throw CImgExceptions yourself, since there are normally + reserved to %CImg Library functions. + \b CImgInstanceException, \b CImgArgumentException, \b CImgIOException and \b CImgDisplayException are simple + subclasses of CImgException and are thus not detailled more in this reference documentation. + + \section ex2 Exception handling + + When an error occurs, the %CImg Library first displays the error in a modal window. + Then, it throws an instance of the corresponding exception class, generally leading the program to stop + (this is the default behavior). + You can bypass this default behavior by handling the exceptions yourself, + using a code block try { ... } catch() { ... }. + In this case, you can avoid the apparition of the modal window, by + defining the environment variable cimg_debug to 0 before including the %CImg header file. + The example below shows how to cleanly handle %CImg Library exceptions : + \code + #define cimg_debug 0 // Disable modal window in CImg exceptions. + #define "CImg.h" + int main() { + try { + ...; // Here, do what you want. + } + catch (CImgInstanceException &e) { + std::fprintf(stderr,"CImg Library Error : %s",e.message); // Display your own error message + ... // Do what you want now. + } + } + \endcode + **/ + struct CImgException { +#define _cimg_exception_err(etype,disp_flag) \ + cimg_std::va_list ap; va_start(ap,format); cimg_std::vsprintf(message,format,ap); va_end(ap); \ + switch (cimg::exception_mode()) { \ + case 0 : break; \ + case 2 : case 4 : try { cimg::dialog(etype,message,"Abort"); } catch (CImgException&) { \ + cimg_std::fprintf(cimg_stdout,"\n%s# %s%s :\n%s\n\n",cimg::t_red,etype,cimg::t_normal,message); \ + } break; \ + default : cimg_std::fprintf(cimg_stdout,"\n%s# %s%s :\n%s\n\n",cimg::t_red,etype,cimg::t_normal,message); \ + } \ + if (cimg::exception_mode()>=3) cimg_library::cimg::info(); + + char message[1024]; //!< Message associated with the error that thrown the exception. + CImgException() { message[0]='\0'; } + CImgException(const char *format, ...) { _cimg_exception_err("CImgException",true); } + }; + + // The \ref CImgInstanceException class is used to throw an exception related + // to a non suitable instance encountered in a library function call. + struct CImgInstanceException: public CImgException { + CImgInstanceException(const char *format, ...) { _cimg_exception_err("CImgInstanceException",true); } + }; + + // The \ref CImgArgumentException class is used to throw an exception related + // to invalid arguments encountered in a library function call. + struct CImgArgumentException: public CImgException { + CImgArgumentException(const char *format, ...) { _cimg_exception_err("CImgArgumentException",true); } + }; + + // The \ref CImgIOException class is used to throw an exception related + // to Input/Output file problems encountered in a library function call. + struct CImgIOException: public CImgException { + CImgIOException(const char *format, ...) { _cimg_exception_err("CImgIOException",true); } + }; + + // The CImgDisplayException class is used to throw an exception related to display problems + // encountered in a library function call. + struct CImgDisplayException: public CImgException { + CImgDisplayException(const char *format, ...) { _cimg_exception_err("CImgDisplayException",false); } + }; + + // The CImgWarningException class is used to throw an exception for warnings + // encountered in a library function call. + struct CImgWarningException: public CImgException { + CImgWarningException(const char *format, ...) { _cimg_exception_err("CImgWarningException",false); } + }; + + /*------------------------------------- + # + # Definition of the namespace 'cimg' + # + --------------------------------------*/ + //! Namespace that encompasses \a low-level functions and variables of the %CImg Library. + /** + Most of the functions and variables within this namespace are used by the library for low-level processing. + Nevertheless, documented variables and functions of this namespace may be used safely in your own source code. + + \warning Never write using namespace cimg_library::cimg; in your source code, since a lot of functions of the + cimg:: namespace have prototypes similar to standard C functions that could defined in the global namespace ::. + **/ + namespace cimg { + + // Define the traits that will be used to determine the best data type to work with. + // + template struct type { + static const char* string() { + static const char* s[] = { "unknown", "unknown8", "unknown16", "unknown24", + "unknown32", "unknown40", "unknown48", "unknown56", + "unknown64", "unknown72", "unknown80", "unknown88", + "unknown96", "unknown104", "unknown112", "unknown120", + "unknown128" }; + return s[(sizeof(T)<17)?sizeof(T):0]; + } + static bool is_float() { return false; } + static T min() { return (T)-1>0?(T)0:(T)-1<<(8*sizeof(T)-1); } + static T max() { return (T)-1>0?(T)-1:~((T)-1<<(8*sizeof(T)-1)); } + static const char* format() { return "%s"; } + static const char* format(const T val) { static const char *s = "unknown"; return s; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "bool"; return s; } + static bool is_float() { return false; } + static bool min() { return false; } + static bool max() { return true; } + static const char* format() { return "%s"; } + static const char* format(const bool val) { static const char* s[] = { "false", "true" }; return s[val?1:0]; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned char"; return s; } + static bool is_float() { return false; } + static unsigned char min() { return 0; } + static unsigned char max() { return (unsigned char)~0U; } + static const char* format() { return "%u"; } + static unsigned int format(const unsigned char val) { return (unsigned int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "char"; return s; } + static bool is_float() { return false; } + static char min() { return (char)(-1L<<(8*sizeof(char)-1)); } + static char max() { return ~((char)(-1L<<(8*sizeof(char)-1))); } + static const char* format() { return "%d"; } + static int format(const char val) { return (int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "signed char"; return s; } + static bool is_float() { return false; } + static signed char min() { return (signed char)(-1L<<(8*sizeof(signed char)-1)); } + static signed char max() { return ~((signed char)(-1L<<(8*sizeof(signed char)-1))); } + static const char* format() { return "%d"; } + static unsigned int format(const signed char val) { return (int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned short"; return s; } + static bool is_float() { return false; } + static unsigned short min() { return 0; } + static unsigned short max() { return (unsigned short)~0U; } + static const char* format() { return "%u"; } + static unsigned int format(const unsigned short val) { return (unsigned int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "short"; return s; } + static bool is_float() { return false; } + static short min() { return (short)(-1L<<(8*sizeof(short)-1)); } + static short max() { return ~((short)(-1L<<(8*sizeof(short)-1))); } + static const char* format() { return "%d"; } + static int format(const short val) { return (int)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned int"; return s; } + static bool is_float() { return false; } + static unsigned int min() { return 0; } + static unsigned int max() { return (unsigned int)~0U; } + static const char* format() { return "%u"; } + static unsigned int format(const unsigned int val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "int"; return s; } + static bool is_float() { return false; } + static int min() { return (int)(-1L<<(8*sizeof(int)-1)); } + static int max() { return ~((int)(-1L<<(8*sizeof(int)-1))); } + static const char* format() { return "%d"; } + static int format(const int val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "unsigned long"; return s; } + static bool is_float() { return false; } + static unsigned long min() { return 0; } + static unsigned long max() { return (unsigned long)~0UL; } + static const char* format() { return "%lu"; } + static unsigned long format(const unsigned long val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "long"; return s; } + static bool is_float() { return false; } + static long min() { return (long)(-1L<<(8*sizeof(long)-1)); } + static long max() { return ~((long)(-1L<<(8*sizeof(long)-1))); } + static const char* format() { return "%ld"; } + static long format(const long val) { return val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "float"; return s; } + static bool is_float() { return true; } + static float min() { return -3.4E38f; } + static float max() { return 3.4E38f; } + static const char* format() { return "%g"; } + static double format(const float val) { return (double)val; } + }; + + template<> struct type { + static const char* string() { static const char *const s = "double"; return s; } + static bool is_float() { return true; } + static double min() { return -1.7E308; } + static double max() { return 1.7E308; } + static const char* format() { return "%g"; } + static double format(const double val) { return val; } + }; + + template struct superset { typedef T type; }; + template<> struct superset { typedef unsigned char type; }; + template<> struct superset { typedef char type; }; + template<> struct superset { typedef signed char type; }; + template<> struct superset { typedef unsigned short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef unsigned long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef unsigned long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef short type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef unsigned int type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef unsigned long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef int type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef unsigned long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef long type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef float type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef double type; }; + + template struct superset2 { + typedef typename superset::type>::type type; + }; + + template struct superset3 { + typedef typename superset::type>::type type; + }; + + template struct last { typedef t2 type; }; + +#define _cimg_Tuchar typename cimg::superset::type +#define _cimg_Tint typename cimg::superset::type +#define _cimg_Tfloat typename cimg::superset::type +#define _cimg_Tdouble typename cimg::superset::type +#define _cimg_Tt typename cimg::superset::type + + // Define internal library variables. + // +#if cimg_display==1 + struct X11info { + volatile unsigned int nb_wins; + pthread_t* event_thread; + CImgDisplay* wins[1024]; + Display* display; + unsigned int nb_bits; + GC* gc; + bool blue_first; + bool byte_order; + bool shm_enabled; +#ifdef cimg_use_xrandr + XRRScreenSize *resolutions; + Rotation curr_rotation; + unsigned int curr_resolution; + unsigned int nb_resolutions; +#endif + X11info():nb_wins(0),event_thread(0),display(0), + nb_bits(0),gc(0),blue_first(false),byte_order(false),shm_enabled(false) { +#ifdef cimg_use_xrandr + resolutions = 0; + curr_rotation = 0; + curr_resolution = nb_resolutions = 0; +#endif + } + }; +#if defined(cimg_module) + X11info& X11attr(); +#elif defined(cimg_main) + X11info& X11attr() { static X11info val; return val; } +#else + inline X11info& X11attr() { static X11info val; return val; } +#endif + +#elif cimg_display==2 + struct Win32info { + HANDLE wait_event; + Win32info() { wait_event = CreateEvent(0,FALSE,FALSE,0); } + }; +#if defined(cimg_module) + Win32info& Win32attr(); +#elif defined(cimg_main) + Win32info& Win32attr() { static Win32info val; return val; } +#else + inline Win32info& Win32attr() { static Win32info val; return val; } +#endif + +#elif cimg_display==3 + struct CarbonInfo { + MPCriticalRegionID windowListCR; // Protects access to the list of windows + int windowCount; // Count of displays used on the screen + pthread_t event_thread; // The background event thread + MPSemaphoreID sync_event; // Event used to perform tasks synchronizations + MPSemaphoreID wait_event; // Event used to notify that new events occured on the display + MPQueueID com_queue; // The message queue + CarbonInfo(): windowCount(0),event_thread(0),sync_event(0),com_queue(0) { + if (MPCreateCriticalRegion(&windowListCR) != noErr) // Create the critical region + throw CImgDisplayException("MPCreateCriticalRegion failed."); + if (MPCreateSemaphore(1, 0, &sync_event) != noErr) // Create the inter-thread sync object + throw CImgDisplayException("MPCreateSemaphore failed."); + if (MPCreateSemaphore(1, 0, &wait_event) != noErr) // Create the event sync object + throw CImgDisplayException("MPCreateSemaphore failed."); + if (MPCreateQueue(&com_queue) != noErr) // Create the shared queue + throw CImgDisplayException("MPCreateQueue failed."); + } + ~CarbonInfo() { + if (event_thread != 0) { // Terminates the resident thread, if needed + pthread_cancel(event_thread); + pthread_join(event_thread, NULL); + event_thread = 0; + } + if (MPDeleteCriticalRegion(windowListCR) != noErr) // Delete the critical region + throw CImgDisplayException("MPDeleteCriticalRegion failed."); + if (MPDeleteSemaphore(wait_event) != noErr) // Delete the event sync event + throw CImgDisplayException("MPDeleteEvent failed."); + if (MPDeleteSemaphore(sync_event) != noErr) // Delete the inter-thread sync event + throw CImgDisplayException("MPDeleteEvent failed."); + if (MPDeleteQueue(com_queue) != noErr) // Delete the shared queue + throw CImgDisplayException("MPDeleteQueue failed."); + } + }; +#if defined(cimg_module) + CarbonInfo& CarbonAttr(); +#elif defined(cimg_main) + CarbonInfo CarbonAttr() { static CarbonInfo val; return val; } +#else + inline CarbonInfo& CarbonAttr() { static CarbonInfo val; return val; } +#endif +#endif + +#if cimg_display==1 + // Keycodes for X11-based graphical systems. + // + const unsigned int keyESC = XK_Escape; + const unsigned int keyF1 = XK_F1; + const unsigned int keyF2 = XK_F2; + const unsigned int keyF3 = XK_F3; + const unsigned int keyF4 = XK_F4; + const unsigned int keyF5 = XK_F5; + const unsigned int keyF6 = XK_F6; + const unsigned int keyF7 = XK_F7; + const unsigned int keyF8 = XK_F8; + const unsigned int keyF9 = XK_F9; + const unsigned int keyF10 = XK_F10; + const unsigned int keyF11 = XK_F11; + const unsigned int keyF12 = XK_F12; + const unsigned int keyPAUSE = XK_Pause; + const unsigned int key1 = XK_1; + const unsigned int key2 = XK_2; + const unsigned int key3 = XK_3; + const unsigned int key4 = XK_4; + const unsigned int key5 = XK_5; + const unsigned int key6 = XK_6; + const unsigned int key7 = XK_7; + const unsigned int key8 = XK_8; + const unsigned int key9 = XK_9; + const unsigned int key0 = XK_0; + const unsigned int keyBACKSPACE = XK_BackSpace; + const unsigned int keyINSERT = XK_Insert; + const unsigned int keyHOME = XK_Home; + const unsigned int keyPAGEUP = XK_Page_Up; + const unsigned int keyTAB = XK_Tab; + const unsigned int keyQ = XK_q; + const unsigned int keyW = XK_w; + const unsigned int keyE = XK_e; + const unsigned int keyR = XK_r; + const unsigned int keyT = XK_t; + const unsigned int keyY = XK_y; + const unsigned int keyU = XK_u; + const unsigned int keyI = XK_i; + const unsigned int keyO = XK_o; + const unsigned int keyP = XK_p; + const unsigned int keyDELETE = XK_Delete; + const unsigned int keyEND = XK_End; + const unsigned int keyPAGEDOWN = XK_Page_Down; + const unsigned int keyCAPSLOCK = XK_Caps_Lock; + const unsigned int keyA = XK_a; + const unsigned int keyS = XK_s; + const unsigned int keyD = XK_d; + const unsigned int keyF = XK_f; + const unsigned int keyG = XK_g; + const unsigned int keyH = XK_h; + const unsigned int keyJ = XK_j; + const unsigned int keyK = XK_k; + const unsigned int keyL = XK_l; + const unsigned int keyENTER = XK_Return; + const unsigned int keySHIFTLEFT = XK_Shift_L; + const unsigned int keyZ = XK_z; + const unsigned int keyX = XK_x; + const unsigned int keyC = XK_c; + const unsigned int keyV = XK_v; + const unsigned int keyB = XK_b; + const unsigned int keyN = XK_n; + const unsigned int keyM = XK_m; + const unsigned int keySHIFTRIGHT = XK_Shift_R; + const unsigned int keyARROWUP = XK_Up; + const unsigned int keyCTRLLEFT = XK_Control_L; + const unsigned int keyAPPLEFT = XK_Super_L; + const unsigned int keyALT = XK_Alt_L; + const unsigned int keySPACE = XK_space; + const unsigned int keyALTGR = XK_Alt_R; + const unsigned int keyAPPRIGHT = XK_Super_R; + const unsigned int keyMENU = XK_Menu; + const unsigned int keyCTRLRIGHT = XK_Control_R; + const unsigned int keyARROWLEFT = XK_Left; + const unsigned int keyARROWDOWN = XK_Down; + const unsigned int keyARROWRIGHT = XK_Right; + const unsigned int keyPAD0 = XK_KP_0; + const unsigned int keyPAD1 = XK_KP_1; + const unsigned int keyPAD2 = XK_KP_2; + const unsigned int keyPAD3 = XK_KP_3; + const unsigned int keyPAD4 = XK_KP_4; + const unsigned int keyPAD5 = XK_KP_5; + const unsigned int keyPAD6 = XK_KP_6; + const unsigned int keyPAD7 = XK_KP_7; + const unsigned int keyPAD8 = XK_KP_8; + const unsigned int keyPAD9 = XK_KP_9; + const unsigned int keyPADADD = XK_KP_Add; + const unsigned int keyPADSUB = XK_KP_Subtract; + const unsigned int keyPADMUL = XK_KP_Multiply; + const unsigned int keyPADDIV = XK_KP_Divide; + +#elif cimg_display==2 + // Keycodes for Windows. + // + const unsigned int keyESC = VK_ESCAPE; + const unsigned int keyF1 = VK_F1; + const unsigned int keyF2 = VK_F2; + const unsigned int keyF3 = VK_F3; + const unsigned int keyF4 = VK_F4; + const unsigned int keyF5 = VK_F5; + const unsigned int keyF6 = VK_F6; + const unsigned int keyF7 = VK_F7; + const unsigned int keyF8 = VK_F8; + const unsigned int keyF9 = VK_F9; + const unsigned int keyF10 = VK_F10; + const unsigned int keyF11 = VK_F11; + const unsigned int keyF12 = VK_F12; + const unsigned int keyPAUSE = VK_PAUSE; + const unsigned int key1 = '1'; + const unsigned int key2 = '2'; + const unsigned int key3 = '3'; + const unsigned int key4 = '4'; + const unsigned int key5 = '5'; + const unsigned int key6 = '6'; + const unsigned int key7 = '7'; + const unsigned int key8 = '8'; + const unsigned int key9 = '9'; + const unsigned int key0 = '0'; + const unsigned int keyBACKSPACE = VK_BACK; + const unsigned int keyINSERT = VK_INSERT; + const unsigned int keyHOME = VK_HOME; + const unsigned int keyPAGEUP = VK_PRIOR; + const unsigned int keyTAB = VK_TAB; + const unsigned int keyQ = 'Q'; + const unsigned int keyW = 'W'; + const unsigned int keyE = 'E'; + const unsigned int keyR = 'R'; + const unsigned int keyT = 'T'; + const unsigned int keyY = 'Y'; + const unsigned int keyU = 'U'; + const unsigned int keyI = 'I'; + const unsigned int keyO = 'O'; + const unsigned int keyP = 'P'; + const unsigned int keyDELETE = VK_DELETE; + const unsigned int keyEND = VK_END; + const unsigned int keyPAGEDOWN = VK_NEXT; + const unsigned int keyCAPSLOCK = VK_CAPITAL; + const unsigned int keyA = 'A'; + const unsigned int keyS = 'S'; + const unsigned int keyD = 'D'; + const unsigned int keyF = 'F'; + const unsigned int keyG = 'G'; + const unsigned int keyH = 'H'; + const unsigned int keyJ = 'J'; + const unsigned int keyK = 'K'; + const unsigned int keyL = 'L'; + const unsigned int keyENTER = VK_RETURN; + const unsigned int keySHIFTLEFT = VK_SHIFT; + const unsigned int keyZ = 'Z'; + const unsigned int keyX = 'X'; + const unsigned int keyC = 'C'; + const unsigned int keyV = 'V'; + const unsigned int keyB = 'B'; + const unsigned int keyN = 'N'; + const unsigned int keyM = 'M'; + const unsigned int keySHIFTRIGHT = VK_SHIFT; + const unsigned int keyARROWUP = VK_UP; + const unsigned int keyCTRLLEFT = VK_CONTROL; + const unsigned int keyAPPLEFT = VK_LWIN; + const unsigned int keyALT = VK_LMENU; + const unsigned int keySPACE = VK_SPACE; + const unsigned int keyALTGR = VK_CONTROL; + const unsigned int keyAPPRIGHT = VK_RWIN; + const unsigned int keyMENU = VK_APPS; + const unsigned int keyCTRLRIGHT = VK_CONTROL; + const unsigned int keyARROWLEFT = VK_LEFT; + const unsigned int keyARROWDOWN = VK_DOWN; + const unsigned int keyARROWRIGHT = VK_RIGHT; + const unsigned int keyPAD0 = 0x60; + const unsigned int keyPAD1 = 0x61; + const unsigned int keyPAD2 = 0x62; + const unsigned int keyPAD3 = 0x63; + const unsigned int keyPAD4 = 0x64; + const unsigned int keyPAD5 = 0x65; + const unsigned int keyPAD6 = 0x66; + const unsigned int keyPAD7 = 0x67; + const unsigned int keyPAD8 = 0x68; + const unsigned int keyPAD9 = 0x69; + const unsigned int keyPADADD = VK_ADD; + const unsigned int keyPADSUB = VK_SUBTRACT; + const unsigned int keyPADMUL = VK_MULTIPLY; + const unsigned int keyPADDIV = VK_DIVIDE; + +#elif cimg_display==3 + // Keycodes for MacOSX, when using the Carbon framework. + // + const unsigned int keyESC = kEscapeCharCode; + const unsigned int keyF1 = 2U; + const unsigned int keyF2 = 3U; + const unsigned int keyF3 = 4U; + const unsigned int keyF4 = 5U; + const unsigned int keyF5 = 6U; + const unsigned int keyF6 = 7U; + const unsigned int keyF7 = 8U; + const unsigned int keyF8 = 9U; + const unsigned int keyF9 = 10U; + const unsigned int keyF10 = 11U; + const unsigned int keyF11 = 12U; + const unsigned int keyF12 = 13U; + const unsigned int keyPAUSE = 14U; + const unsigned int key1 = '1'; + const unsigned int key2 = '2'; + const unsigned int key3 = '3'; + const unsigned int key4 = '4'; + const unsigned int key5 = '5'; + const unsigned int key6 = '6'; + const unsigned int key7 = '7'; + const unsigned int key8 = '8'; + const unsigned int key9 = '9'; + const unsigned int key0 = '0'; + const unsigned int keyBACKSPACE = kBackspaceCharCode; + const unsigned int keyINSERT = 26U; + const unsigned int keyHOME = kHomeCharCode; + const unsigned int keyPAGEUP = kPageUpCharCode; + const unsigned int keyTAB = kTabCharCode; + const unsigned int keyQ = 'q'; + const unsigned int keyW = 'w'; + const unsigned int keyE = 'e'; + const unsigned int keyR = 'r'; + const unsigned int keyT = 't'; + const unsigned int keyY = 'y'; + const unsigned int keyU = 'u'; + const unsigned int keyI = 'i'; + const unsigned int keyO = 'o'; + const unsigned int keyP = 'p'; + const unsigned int keyDELETE = kDeleteCharCode; + const unsigned int keyEND = kEndCharCode; + const unsigned int keyPAGEDOWN = kPageDownCharCode; + const unsigned int keyCAPSLOCK = 43U; + const unsigned int keyA = 'a'; + const unsigned int keyS = 's'; + const unsigned int keyD = 'd'; + const unsigned int keyF = 'f'; + const unsigned int keyG = 'g'; + const unsigned int keyH = 'h'; + const unsigned int keyJ = 'j'; + const unsigned int keyK = 'k'; + const unsigned int keyL = 'l'; + const unsigned int keyENTER = kEnterCharCode; + const unsigned int keySHIFTLEFT = 54U; //Macintosh modifier key, emulated + const unsigned int keyZ = 'z'; + const unsigned int keyX = 'x'; + const unsigned int keyC = 'c'; + const unsigned int keyV = 'v'; + const unsigned int keyB = 'b'; + const unsigned int keyN = 'n'; + const unsigned int keyM = 'm'; + const unsigned int keySHIFTRIGHT = 62U; //Macintosh modifier key, emulated + const unsigned int keyARROWUP = kUpArrowCharCode; + const unsigned int keyCTRLLEFT = 64U; //Macintosh modifier key, emulated + const unsigned int keyAPPLEFT = 65U; //Macintosh modifier key, emulated + const unsigned int keyALT = 66U; + const unsigned int keySPACE = kSpaceCharCode; + const unsigned int keyALTGR = 67U; //Macintosh modifier key, emulated + const unsigned int keyAPPRIGHT = 68U; //Aliased on keyAPPLEFT + const unsigned int keyMENU = 69U; + const unsigned int keyCTRLRIGHT = 70U; //Macintosh modifier key, emulated + const unsigned int keyARROWLEFT = kLeftArrowCharCode; + const unsigned int keyARROWDOWN = kDownArrowCharCode; + const unsigned int keyARROWRIGHT = kRightArrowCharCode; + const unsigned int keyPAD0 = 74U; + const unsigned int keyPAD1 = 75U; + const unsigned int keyPAD2 = 76U; + const unsigned int keyPAD3 = 77U; + const unsigned int keyPAD4 = 78U; + const unsigned int keyPAD5 = 79U; + const unsigned int keyPAD6 = 80U; + const unsigned int keyPAD7 = 81U; + const unsigned int keyPAD8 = 82U; + const unsigned int keyPAD9 = 83U; + const unsigned int keyPADADD = 84U; + const unsigned int keyPADSUB = 85U; + const unsigned int keyPADMUL = 86U; + const unsigned int keyPADDIV = 87U; + +#else + // Define unknow keycodes when no display are available. + // (should rarely be used then !). + // + const unsigned int keyESC = 1U; + const unsigned int keyF1 = 2U; + const unsigned int keyF2 = 3U; + const unsigned int keyF3 = 4U; + const unsigned int keyF4 = 5U; + const unsigned int keyF5 = 6U; + const unsigned int keyF6 = 7U; + const unsigned int keyF7 = 8U; + const unsigned int keyF8 = 9U; + const unsigned int keyF9 = 10U; + const unsigned int keyF10 = 11U; + const unsigned int keyF11 = 12U; + const unsigned int keyF12 = 13U; + const unsigned int keyPAUSE = 14U; + const unsigned int key1 = 15U; + const unsigned int key2 = 16U; + const unsigned int key3 = 17U; + const unsigned int key4 = 18U; + const unsigned int key5 = 19U; + const unsigned int key6 = 20U; + const unsigned int key7 = 21U; + const unsigned int key8 = 22U; + const unsigned int key9 = 23U; + const unsigned int key0 = 24U; + const unsigned int keyBACKSPACE = 25U; + const unsigned int keyINSERT = 26U; + const unsigned int keyHOME = 27U; + const unsigned int keyPAGEUP = 28U; + const unsigned int keyTAB = 29U; + const unsigned int keyQ = 30U; + const unsigned int keyW = 31U; + const unsigned int keyE = 32U; + const unsigned int keyR = 33U; + const unsigned int keyT = 34U; + const unsigned int keyY = 35U; + const unsigned int keyU = 36U; + const unsigned int keyI = 37U; + const unsigned int keyO = 38U; + const unsigned int keyP = 39U; + const unsigned int keyDELETE = 40U; + const unsigned int keyEND = 41U; + const unsigned int keyPAGEDOWN = 42U; + const unsigned int keyCAPSLOCK = 43U; + const unsigned int keyA = 44U; + const unsigned int keyS = 45U; + const unsigned int keyD = 46U; + const unsigned int keyF = 47U; + const unsigned int keyG = 48U; + const unsigned int keyH = 49U; + const unsigned int keyJ = 50U; + const unsigned int keyK = 51U; + const unsigned int keyL = 52U; + const unsigned int keyENTER = 53U; + const unsigned int keySHIFTLEFT = 54U; + const unsigned int keyZ = 55U; + const unsigned int keyX = 56U; + const unsigned int keyC = 57U; + const unsigned int keyV = 58U; + const unsigned int keyB = 59U; + const unsigned int keyN = 60U; + const unsigned int keyM = 61U; + const unsigned int keySHIFTRIGHT = 62U; + const unsigned int keyARROWUP = 63U; + const unsigned int keyCTRLLEFT = 64U; + const unsigned int keyAPPLEFT = 65U; + const unsigned int keyALT = 66U; + const unsigned int keySPACE = 67U; + const unsigned int keyALTGR = 68U; + const unsigned int keyAPPRIGHT = 69U; + const unsigned int keyMENU = 70U; + const unsigned int keyCTRLRIGHT = 71U; + const unsigned int keyARROWLEFT = 72U; + const unsigned int keyARROWDOWN = 73U; + const unsigned int keyARROWRIGHT = 74U; + const unsigned int keyPAD0 = 75U; + const unsigned int keyPAD1 = 76U; + const unsigned int keyPAD2 = 77U; + const unsigned int keyPAD3 = 78U; + const unsigned int keyPAD4 = 79U; + const unsigned int keyPAD5 = 80U; + const unsigned int keyPAD6 = 81U; + const unsigned int keyPAD7 = 82U; + const unsigned int keyPAD8 = 83U; + const unsigned int keyPAD9 = 84U; + const unsigned int keyPADADD = 85U; + const unsigned int keyPADSUB = 86U; + const unsigned int keyPADMUL = 87U; + const unsigned int keyPADDIV = 88U; +#endif + + const double valuePI = 3.14159265358979323846; //!< Definition of the mathematical constant PI + + // Definition of a 7x11 font, used to return a default font for drawing text. + const unsigned int font7x11[7*11*256/32] = { + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x90,0x0,0x7f0000,0x40000,0x0,0x0,0x4010c0a4,0x82000040,0x11848402,0x18480050,0x80430292,0x8023,0x9008000, + 0x40218140,0x4000040,0x21800402,0x18000051,0x1060500,0x8083,0x10000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x24002,0x4031,0x80000000,0x10000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x81c0400,0x40020000,0x80070080,0x40440e00,0x0,0x0,0x1,0x88180000,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x200000,0x0,0x0,0x80000,0x0,0x0,0x20212140,0x5000020,0x22400204,0x240000a0,0x40848500,0x4044,0x80010038,0x20424285,0xa000020, + 0x42428204,0x2428e0a0,0x82090a14,0x4104,0x85022014,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10240a7,0x88484040,0x40800000,0x270c3,0x87811e0e, + 0x7c70e000,0x78,0x3c23c1ef,0x1f3e1e89,0xf1c44819,0xa23cf0f3,0xc3cff120,0xc18307f4,0x4040400,0x20000,0x80080080,0x40200,0x0, + 0x40000,0x2,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8188,0x50603800,0xf3c00000,0x1c004003,0xc700003e,0x18180,0xc993880,0x10204081, + 0x2071ef9,0xf3e7cf9f,0x3e7c7911,0xe3c78f1e,0x7d1224,0x48906048,0x0,0x4000000,0x0,0x9000,0x0,0x0,0x2000,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x10240aa,0x14944080,0x23610000,0x68940,0x40831010,0x8891306,0x802044,0x44522208,0x90202088,0x40448819,0xb242890a,0x24011111, + 0x49448814,0x4040a00,0xe2c3c7,0x8e3f3cb9,0xc1c44216,0xee38b0f2,0xe78f9120,0xc18507e2,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x101c207,0x88a04001,0x9c00000,0x2200a041,0x8200113a,0x8240,0x50a3110,0x2850a142,0x850c2081,0x2040204,0x8104592,0x142850a1, + 0x42cd1224,0x4888bc48,0x70e1c387,0xe3b3c70,0xe1c38e1c,0x38707171,0xc3870e1c,0x10791224,0x48906c41,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x10003ee,0x15140080,0x21810000,0x48840,0x40851020,0x8911306,0x31fd804,0x9c522408,0x90204088,0x4045081a,0xba42890a,0x24011111, + 0x49285024,0x2041b00,0x132408,0x910844c8,0x4044821b,0x7244c913,0x24041111,0x49488822,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x28204,0x85006001,0x6a414000,0x3a004043,0xc700113a,0x8245,0x50a3a00,0x2850a142,0x850c4081,0x2040204,0x81045d2,0x142850a1, + 0x24951224,0x48852250,0x8102040,0x81054089,0x12244204,0x8108992,0x24489122,0x991224,0x4888b222,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x1000143,0xa988080,0x2147c01f,0x88840,0x83091c2c,0x1070f000,0xc000608,0xa48bc408,0x9e3c46f8,0x40460816,0xaa42f10b,0xc3811111, + 0x35102044,0x1041100,0xf22408,0x9f084488,0x40470212,0x62448912,0x6041111,0x55308846,0x8061c80,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x1028704,0x8f805801,0x4be28fdf,0x220001f0,0x111a,0x60000182,0x82c5c710,0x44891224,0x489640f1,0xe3c78204,0x810e552,0x142850a1, + 0x18a51224,0x48822250,0x78f1e3c7,0x8f1f40f9,0xf3e7c204,0x8108912,0x24489122,0x7ea91224,0x4888a222,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x10007e2,0x85648080,0x20010000,0x88841,0x8f8232,0x20881000,0xc1fc610,0xbefa2408,0x90204288,0x40450816,0xa642810a,0x4041110a, + 0x36282084,0x1042080,0x1122408,0x90084488,0x40450212,0x62448912,0x184110a,0x55305082,0x8042700,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x1028207,0x82004801,0x68050040,0x1c000040,0x110a,0x60000001,0x45484d10,0x7cf9f3e7,0xcf944081,0x2040204,0x8104532,0x142850a1, + 0x18a51224,0x48822248,0x89122448,0x91244081,0x2040204,0x8108912,0x24489122,0xc91224,0x48852214,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x282, + 0x89630080,0x20010c00,0x30108842,0x810222,0x20882306,0x3001800,0x408a2208,0x90202288,0x40448814,0xa642810a,0x2041110a,0x26442104, + 0x840000,0x1122408,0x90084488,0x40448212,0x62448912,0x84130a,0x36485102,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x101c208,0x4f802801, + 0x8028040,0x40,0x130a,0x2,0x85e897a0,0x44891224,0x489c2081,0x2040204,0x8104532,0x142850a1,0x24cd1224,0x48823c44,0x89122448, + 0x91244081,0x2040204,0x8108912,0x24489122,0xc93264,0xc9852214,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100028f,0x109f0080,0x20010c00, + 0x303071f3,0xc7011c1c,0x4071c306,0x802010,0x3907c1ef,0x1f201e89,0xf3844f90,0xa23c80f2,0x17810e04,0x228223f4,0x840000,0xfbc3c7, + 0x8f083c88,0x40444212,0x6238f0f2,0x7039d04,0x228423e2,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1008780,0x2201800,0xf0014000,0x1f0, + 0x1d0a,0x5,0x851e140,0x83060c18,0x30671ef9,0xf3e7cf9f,0x3e7c7911,0xe3c78f1e,0x42f8e1c3,0x8702205c,0x7cf9f3e7,0xcf9b3c78,0xf1e3c204, + 0x8107111,0xc3870e1c,0x10f1d3a7,0x4e823c08,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x40,0x40000400,0x200000,0x0,0x2,0x0,0x0,0x0,0x0,0x18, + 0x0,0x4,0x44007f,0x0,0x400,0x400000,0x8010,0x0,0x6002,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000000,0x200800,0x0,0x0,0x100a, + 0x400000,0x44,0x0,0x400,0x0,0x0,0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x0,0x62018,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x31,0x80000800, + 0x400000,0x0,0x4,0x0,0x0,0x0,0x0,0xc,0x0,0x7,0x3c0000,0x0,0x3800,0x3800000,0x8010,0x0,0x1c001,0x881c0000,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x207000,0x0,0x0,0x100a,0xc00000,0x3c,0x0,0xc00,0x0,0x0,0x0,0x0,0x0,0x0,0x1800,0x0,0x0,0x0,0x0,0x1c2070 + }; + + // Definition of a 10x13 font (used in dialog boxes). + const unsigned int font10x13[256*10*13/32] = { + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80100c0, + 0x68000300,0x801,0xc00010,0x100c000,0x68100,0x100c0680,0x2,0x403000,0x1000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc,0x0,0x0,0x0,0x0,0x0,0x4020120, + 0x58120480,0x402,0x1205008,0x2012050,0x58080,0x20120581,0x40000001,0x804812,0x2000000,0x0,0x300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x140,0x80000,0x200402,0x800000,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x7010,0x7000000,0x8000200,0x20000,0xc0002000,0x8008,0x0,0x0,0x0,0x0,0x808,0x4000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x80000000,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x0,0x480,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x80100c0,0x68000480,0x1001, + 0xc00010,0x1018000,0x68100,0x100c0680,0x4,0x403000,0x1020000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20140,0x28081883,0x200801, + 0x2a00000,0x10,0x1c0201c0,0x70040f80,0xc0f81c07,0x0,0x70,0x3e0303c0,0x3c3c0f83,0xe03c2107,0xe08810,0x18c31070,0x3c0703c0, + 0x783e0842,0x22222208,0x83e04010,0x1008000,0x4000200,0x20001,0x2002,0x408008,0x0,0x0,0x100000,0x0,0x1008,0x2000000,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20080,0x38000880,0x8078140f,0x81c00000,0x3e000,0xc020180,0x60080001,0xe0000002,0xc00042,0x108e2010, + 0xc0300c0,0x300c0303,0xf83c3e0f,0x83e0f81c,0x701c070,0x3c0c41c0,0x701c0701,0xc0001d08,0x42108421,0x8820088,0x4020120,0x58140480, + 0x802,0x1205008,0x3014050,0xc058080,0x20120581,0x40000002,0x804814,0x2020050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20140, + 0x281e2484,0x80200801,0x1c02000,0x10,0x22060220,0x880c0801,0x82208,0x80000001,0x20008,0x41030220,0x40220802,0x402102,0x209010, + 0x18c31088,0x22088220,0x80080842,0x22222208,0x80204010,0x1014000,0x200,0x20001,0x2000,0x8008,0x0,0x0,0x100000,0x0,0x1008, + 0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x40000500,0x80800010,0x40200000,0x41000,0x12020040,0x10000003,0xa0000006, + 0x12000c4,0x31014000,0xc0300c0,0x300c0302,0x80402008,0x2008008,0x2008020,0x220c4220,0x88220882,0x20002208,0x42108421,0x8820088, + 0x0,0x300,0x0,0x0,0x0,0x14000000,0x0,0x200200,0x0,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0xfc282504,0x80001000, + 0x82a02000,0x20,0x22020020,0x8140802,0x102208,0x80801006,0x18008,0x9c848220,0x80210802,0x802102,0x20a010,0x15429104,0x22104220, + 0x80080842,0x22221405,0x404008,0x1022000,0x703c0,0x381e0701,0xc0783c02,0xc09008,0x1d83c070,0x3c078140,0x381c0882,0x21242208, + 0x81e01008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x201e0,0x40220500,0x80800027,0x20e02800,0x9c800,0x12020040, + 0x20000883,0xa0200002,0x120a044,0x11064010,0x12048120,0x48120484,0x80802008,0x2008008,0x2008020,0x210a4411,0x4411044,0x10884508, + 0x42108421,0x503c0b0,0x1c0701c0,0x701c0707,0x70381c07,0x1c07008,0x2008020,0x20f01c0,0x701c0701,0xc0201c08,0x82208822,0x883c088, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x50281903,0x20001000,0x80802000,0x20,0x22020040,0x30240f03,0xc0101c08,0x80801018, + 0x1fc06010,0xa48483c0,0x80210f03,0xe0803f02,0x20c010,0x15429104,0x22104220,0x70080841,0x41540805,0x804008,0x1041000,0x8220, + 0x40220881,0x882202,0x40a008,0x12422088,0x22088180,0x40100882,0x21241408,0x80201008,0x2031000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x20280,0x401c0200,0x700028,0x21205000,0x92800,0xc1fc080,0x10000883,0xa0200002,0x1205049,0x12c19010,0x12048120,0x48120484, + 0xf0803c0f,0x3c0f008,0x2008020,0x790a4411,0x4411044,0x10504908,0x42108421,0x5022088,0x2008020,0x8020080,0x88402208,0x82208808, + 0x2008020,0x1e088220,0x88220882,0x20002608,0x82208822,0x8822088,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x501c0264, + 0xa0001000,0x8001fc00,0x7000020,0x22020080,0x83e0082,0x20202207,0x80000020,0x1020,0xa4848220,0x80210802,0x9c2102,0x20c010, + 0x12425104,0x3c1043c0,0x8080841,0x41540802,0x804008,0x1000000,0x78220,0x40220f81,0x882202,0x40c008,0x12422088,0x22088100, + 0x60100881,0x41540805,0x406008,0x1849000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20280,0xf0140200,0x880028,0x20e0a03f,0x709c800, + 0x201c0,0x60000881,0xa0000007,0xc0284b,0x122eb020,0x12048120,0x48120487,0x80802008,0x2008008,0x2008020,0x21094411,0x4411044, + 0x10204908,0x42108421,0x2022088,0x1e0781e0,0x781e0787,0xf8403e0f,0x83e0f808,0x2008020,0x22088220,0x88220882,0x21fc2a08,0x82208822, + 0x5022050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0xf80a0294,0x40001000,0x80002000,0x20,0x22020100,0x8040082,0x20202200, + 0x80000018,0x1fc06020,0xa48fc220,0x80210802,0x842102,0x20a010,0x12425104,0x20104240,0x8080841,0x41541402,0x1004008,0x1000000, + 0x88220,0x40220801,0x882202,0x40a008,0x12422088,0x22088100,0x18100881,0x41540805,0x801008,0x2046000,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x20280,0x401c0f80,0x80880028,0x20005001,0x94800,0x20000,0x880,0xa0000000,0x5015,0x4215040,0x3f0fc3f0,0xfc3f0fc8, + 0x80802008,0x2008008,0x2008020,0x21094411,0x4411044,0x10505108,0x42108421,0x203c088,0x22088220,0x88220888,0x80402008,0x2008008, + 0x2008020,0x22088220,0x88220882,0x20002a08,0x82208822,0x5022050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xa00a0494,0x60001000, + 0x80002004,0x8020,0x22020200,0x88040882,0x20402201,0x801006,0x18000,0x9f084220,0x40220802,0x442102,0x209010,0x10423088,0x20088220, + 0x8080840,0x80882202,0x2004008,0x1000000,0x88220,0x40220881,0x882202,0x409008,0x12422088,0x22088100,0x8100880,0x80881402, + 0x1001008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20280,0x40220200,0x80700027,0x20002801,0x92800,0x1fc000,0x980, + 0xa0000000,0xa017,0x84417840,0x21084210,0x84210848,0x80402008,0x2008008,0x2008020,0x2208c220,0x88220882,0x20882208,0x42108421, + 0x2020088,0x22088220,0x88220888,0xc8402208,0x82208808,0x2008020,0x22088220,0x88220882,0x20203208,0x82208822,0x2022020,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0xa03c0463,0x90000801,0x2004,0x8040,0x1c0703e0,0x70040701,0xc0401c06,0x801001,0x20020, + 0x400843c0,0x3c3c0f82,0x3c2107,0x1c0881e,0x10423070,0x20070210,0xf0080780,0x80882202,0x3e04004,0x1000000,0x783c0,0x381e0701, + 0x782202,0x408808,0x12422070,0x3c078100,0x700c0780,0x80882202,0x1e01008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x201e0, + 0xf8000200,0x80080010,0x40000001,0x41000,0x0,0xe80,0xa0000000,0x21,0x8e21038,0x21084210,0x84210848,0xf83c3e0f,0x83e0f81c, + 0x701c070,0x3c08c1c0,0x701c0701,0xc0005c07,0x81e0781e,0x20200b0,0x1e0781e0,0x781e0787,0x30381c07,0x1c07008,0x2008020,0x1c0881c0, + 0x701c0701,0xc0201c07,0x81e0781e,0x203c020,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,0x801,0x4,0x40,0x0,0x0,0x0,0x1000, + 0x0,0x3c000000,0x0,0x0,0x0,0x0,0x10000,0x0,0x0,0x4004,0x1000000,0x0,0x0,0x80000,0x400000,0x0,0x20008000,0x0,0x4,0x1008,0x2000000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x0,0x8008000f,0x80000000,0x3e000,0x0,0x800,0xa0000400,0x0,0x0,0x0,0x0,0x80000,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100000,0x0,0x0,0x0,0x0,0x2000,0x0,0x4020040,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000, + 0x402,0x8,0x40,0x0,0x0,0x0,0x2000,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x0,0x7004,0x70000fc,0x0,0x0,0x700000,0x800000,0x0,0x20008000, + 0x0,0x4,0x808,0x4000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x0,0x80f00000,0x0,0x0,0x0,0x800,0xa0001800,0x0,0x0,0x0,0x0, + 0x300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600000,0x0,0x0,0x0,0x0,0x0,0x0,0x4020040 + }; + + // Definition of a 8x17 font. + const unsigned int font8x17[8*17*256/32] = { + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x2400,0x2400,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20081834,0x1c0000,0x20081800,0x20081800,0x342008, + 0x18340000,0x200818,0x80000,0x0,0x180000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4200000,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x380000,0x4000,0x2000c00,0x40100840,0x70000000,0x0,0x0,0x1c,0x10700000,0x7,0x0, + 0x1800,0x1800,0x0,0x0,0x0,0x14,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1010242c,0x14140000,0x10102414,0x10102414,0x2c1010,0x242c1400, + 0x101024,0x14100038,0x0,0x240000,0x0,0x0,0x30000000,0x0,0x0,0x4000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x12,0x0,0x8100000,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x80000,0x10004000,0x2001000,0x40000040,0x10000000,0x0,0x0,0x10,0x10100000,0x4, + 0x0,0x18000000,0x0,0x0,0x0,0x34002400,0x2400,0x0,0x0,0x0,0x3c,0x0,0x8000000,0x0,0x60607800,0x0,0x140000,0x0,0x0,0x0,0x0,0x0, + 0x44,0x10081834,0x240000,0x10081800,0x10081800,0x1c341008,0x18340000,0x100818,0x84000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x102812, + 0x8601c10,0x8100800,0x2,0x1c383e3e,0x67e1e7f,0x3e3c0000,0x38,0x1e087e1e,0x7c7f7f1e,0x417c1c42,0x4063611c,0x7e1c7e3e,0xfe414181, + 0x63827f10,0x40081000,0x8004000,0x2001000,0x40000040,0x10000000,0x0,0x10000000,0x10,0x10100000,0x3c000008,0x0,0x24003e00, + 0x3f007f00,0x0,0x0,0x2ce91800,0x1882,0x10101c,0xc2103c,0x143c3c00,0x3c00,0x18003c3c,0x10001f00,0x181c00,0x20200810,0x8080808, + 0x8083e1e,0x7f7f7f7f,0x7c7c7c7c,0x7c611c1c,0x1c1c1c00,0x1e414141,0x41824044,0x810242c,0x14180000,0x8102414,0x8102414,0x382c0810, + 0x242c1400,0x81024,0x14104014,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x102816,0x3e902010,0x10084910,0x4,0x22084343,0xa402102,0x41620000, + 0x44,0x33144121,0x42404021,0x41100444,0x40636122,0x43224361,0x10416381,0x22440310,0x20082800,0x4000,0x2001000,0x40000040, + 0x10000000,0x0,0x10000000,0x10,0x10100000,0x24000008,0x0,0x606100,0x68000300,0x8106c,0x34000000,0x4f0000,0x44,0x101020,0x441040, + 0x420200,0x4200,0x24000404,0x7d00,0x82200,0x20203010,0x14141414,0x14082821,0x40404040,0x10101010,0x42612222,0x22222200,0x23414141, + 0x41447e48,0x0,0x0,0x0,0x0,0x4000000,0x18,0x0,0x4000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10287f,0x49902010,0x10083e10,0x4,0x41080101, + 0x1a404002,0x41411818,0x1004004,0x21144140,0x41404040,0x41100448,0x40555141,0x41414140,0x10412281,0x14280610,0x20084400,0x1c7c1c, + 0x3e3c7c3a,0x5c703844,0x107f5c3c,0x7c3e3c3c,0x7e424281,0x66427e10,0x10100000,0x40100008,0x1010,0xa04000,0x48100610,0x100c3024, + 0x24000000,0x4f3c00,0x2c107e28,0x3820,0x42281060,0x9d1e12,0xbd00,0x24100818,0x427d00,0x82248,0x20200800,0x14141414,0x14142840, + 0x40404040,0x10101010,0x41514141,0x41414142,0x43414141,0x41284350,0x1c1c1c1c,0x1c1c6c1c,0x3c3c3c3c,0x70707070,0x3c5c3c3c, + 0x3c3c3c18,0x3e424242,0x42427c42,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x102824,0x48623010,0x10081c10,0x8,0x41080103,0x127c5e04, + 0x41411818,0xe7f3808,0x4f144140,0x41404040,0x41100450,0x40555141,0x41414160,0x1041225a,0x1c280410,0x1008c600,0x226622,0x66661066, + 0x62100848,0x10496266,0x66663242,0x10426681,0x24220260,0x100c0000,0xf8280008,0x1010,0x606000,0x48280428,0x28042014,0x48000000, + 0x494200,0x52280228,0x105420,0x3cee1058,0xa12236,0xa500,0x18101004,0x427d00,0x8226c,0x76767e10,0x14141414,0x14142840,0x40404040, + 0x10101010,0x41514141,0x41414124,0x45414141,0x41284150,0x22222222,0x22221222,0x66666666,0x10101010,0x66626666,0x66666600, + 0x66424242,0x42226622,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100024,0x381c4900,0x10086bfe,0x8,0x4908021c,0x22036304,0x3e630000, + 0x70000710,0x51227e40,0x417f7f43,0x7f100470,0x40554941,0x43417e3e,0x1041225a,0x8100810,0x10080000,0x24240,0x42421042,0x42100850, + 0x10494242,0x42422040,0x1042245a,0x18240410,0x10103900,0x407c003e,0x1818,0x1c3e10,0x4f7c087c,0x7c002010,0x48000000,0x4008, + 0x527c0410,0x105078,0x2410104c,0xa13e6c,0x7f00b900,0xfe3c3c,0x421d18,0x1c1c36,0x38383810,0x22222222,0x22144e40,0x7f7f7f7f, + 0x10101010,0xf1494141,0x41414118,0x49414141,0x4110435c,0x2020202,0x2021240,0x42424242,0x10101010,0x42424242,0x424242ff,0x4e424242, + 0x42244224,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000fe,0xe664d00,0x10080810,0x380010,0x41080c03,0x42014108,0x633d0000,0x70000710, + 0x51224140,0x41404041,0x41100448,0x40494541,0x7e414203,0x1041145a,0x14101010,0x10080000,0x3e4240,0x427e1042,0x42100870,0x10494242, + 0x4242203c,0x1042245a,0x18241810,0x10104600,0xf8f60008,0x1010,0x600320,0x48f610f6,0xf6000000,0x187eff,0x3c04,0x5ef61810,0x105020, + 0x24fe0064,0x9d006c,0x138ad00,0x100000,0x420518,0x36,0xc0c0c020,0x22222222,0x22224840,0x40404040,0x10101010,0x41454141,0x41414118, + 0x51414141,0x41107e46,0x3e3e3e3e,0x3e3e7e40,0x7e7e7e7e,0x10101010,0x42424242,0x42424200,0x5a424242,0x42244224,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x28,0x9094500,0x10080010,0x10,0x41081801,0x7f014118,0x41010000,0xe7f3800,0x513e4140,0x41404041,0x41100444, + 0x40414541,0x40414101,0x10411466,0x36103010,0x8080000,0x424240,0x42401042,0x42100848,0x10494242,0x42422002,0x10423c5a,0x18142010, + 0x10100000,0x407c0010,0x1010,0x260140,0x487c307c,0x7c000000,0x180000,0x202,0x507c2010,0x105020,0x3c10003c,0x423e36,0x1004200, + 0x100000,0x420500,0x3e6c,0x41e0440,0x3e3e3e3e,0x3e3e7840,0x40404040,0x10101010,0x41454141,0x41414124,0x61414141,0x41104042, + 0x42424242,0x42425040,0x40404040,0x10101010,0x42424242,0x42424218,0x72424242,0x42144214,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100048, + 0x49096200,0x8100010,0x18001810,0x22082043,0x2432310,0x61421818,0x1004010,0x4f634121,0x42404021,0x41104444,0x40414322,0x40234143, + 0x10411466,0x22106010,0x8080000,0x466622,0x66621066,0x42100844,0x10494266,0x66662042,0x10461824,0x24184010,0x10100000,0x24381010, + 0x34001018,0xda4320,0x68386038,0x38000000,0x0,0x4204,0x50384010,0x105420,0x4210100c,0x3c0012,0x3c00,0x0,0x460500,0x48,0xc020c44, + 0x63636363,0x63228821,0x40404040,0x10101010,0x42432222,0x22222242,0x62414141,0x41104042,0x46464646,0x46465022,0x62626262, + 0x10101010,0x66426666,0x66666618,0x66464646,0x46186618,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100048,0x3e063d00,0x8100000,0x18001820, + 0x1c3e7f3e,0x23c1e20,0x3e3c1818,0x10,0x20417e1e,0x7c7f401e,0x417c3842,0x7f41431c,0x401e40be,0x103e0866,0x41107f10,0x4080000, + 0x3a5c1c,0x3a3c103a,0x427c0842,0xe49423c,0x7c3e203c,0xe3a1824,0x66087e10,0x10100000,0x3c103010,0x245a1010,0x5a3e10,0x3f107f10, + 0x10000000,0x0,0x3c08,0x2e107e10,0x1038fc,0x101004,0x0,0x0,0xfe0000,0x7f0500,0x0,0x14041438,0x41414141,0x41418e1e,0x7f7f7f7f, + 0x7c7c7c7c,0x7c431c1c,0x1c1c1c00,0xbc3e3e3e,0x3e10405c,0x3a3a3a3a,0x3a3a6e1c,0x3c3c3c3c,0x7c7c7c7c,0x3c423c3c,0x3c3c3c00, + 0x7c3a3a3a,0x3a087c08,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8000000,0x4200000,0x10000020,0x0,0x0,0x10,0x0,0x30000000,0x0, + 0x0,0x0,0x60000,0x0,0x1c,0x4380000,0x0,0x2,0x800,0x0,0x40020000,0x0,0x8000c,0x10600000,0x2010,0x48000000,0x240000,0x0,0x0, + 0x0,0x0,0x0,0x1000,0x1078,0x0,0x0,0x0,0x400500,0x0,0x1e081e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x84008,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8000000,0x0,0x20000040,0x0,0x0,0x20,0x0,0x1e000000,0x0,0x0,0x0,0x20000,0x0, + 0x0,0x2000000,0x0,0x26,0x800,0x0,0x40020000,0x0,0x100000,0x10000000,0x2030,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000,0x1000,0x0, + 0x0,0x0,0x400000,0x8000000,0x41e0400,0x0,0x4,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x0,0x0,0x0,0x0,0x104010,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe,0x0,0x1c,0x7000,0x0,0x40020000,0x0,0x300000, + 0x0,0xe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000,0x0,0x0,0x0,0x400000,0x38000000,0x0,0x0,0x1c,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x1c,0x0,0x0,0x0,0x0,0x0,0x304030,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 }; + + // Definition of a 10x19 font. + const unsigned int font10x19[10*19*256/32] = { + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3600000,0x36000,0x0,0x0,0x0,0x0,0x6c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x180181c0,0xe81b0300,0x1801,0x81c06c18,0x181c06c,0xe8180,0x181c0e81,0xb0000006,0x60701b,0x1800000,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00000,0x1c000,0x0,0x0,0x0,0x0,0x6c,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0xc030360,0xb81b0480,0xc03,0x3606c0c,0x303606c,0xb80c0,0x30360b81,0xb0000003,0xc0d81b,0x3000000,0x0, + 0x300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x0,0x0,0x2200000, + 0x22000,0x0,0x0,0x0,0x0,0x0,0x0,0x30000,0x0,0xe0,0x38078000,0x0,0x480,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3000c080,0x480,0x3000, + 0xc0800030,0xc08000,0x300,0xc080000,0xc,0x302000,0xc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x41c01,0xe020060c, + 0x800000,0x4,0x1e0703e0,0xf8060fc1,0xe1fe1e07,0x80000000,0x78,0x307e0,0x3c7c1fe7,0xf83c408f,0x80f10440,0x18660878,0x7e0787e0, + 0x78ff9024,0xa0140a0,0x27f83840,0x700e000,0x18000400,0x8000,0x70004002,0x410078,0x0,0x0,0x0,0x0,0x1808,0xc000000,0xf000000, + 0xe000000,0x1400,0x1e0001f,0x8007f800,0x0,0x0,0x3a3b,0x61400000,0x14202,0x20000,0x38002020,0x3c1b00,0x3e00000,0xf8,0x1c0001c0, + 0x78060001,0xf800000e,0x1e00020,0x8004020,0xc0300c0,0x300c0301,0xf83c7f9f,0xe7f9fe3e,0xf83e0f8,0x7c1821e0,0x781e0781,0xe0001f10, + 0x24090240,0xa02400f8,0x18018140,0xe81b0480,0x1801,0x81406c18,0x181406c,0x190e8180,0x18140e81,0xb0000006,0x60501b,0x184006c, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x26042202,0x200c06,0x800000,0x8,0x210d0611,0x40e0803,0x10026188,0x40000000, + 0x8c,0xf030418,0xc6431004,0xc64082,0x110840,0x18660884,0x41084410,0x8c081024,0xa012110,0x40082020,0x101b000,0xc000400,0x8000, + 0x80004002,0x410008,0x0,0x0,0x100000,0x0,0x2008,0x2000000,0x18800000,0x10000000,0x2200,0x2300024,0x800,0x0,0x0,0x2e13,0x60800000, + 0x8104,0x20040,0x64001040,0x80401b07,0x80100000,0x1e000,0x22000020,0x40c0003,0xc8000002,0x3300020,0x8004020,0xc0300c0,0x300c0301, + 0x40c64010,0x4010008,0x2008020,0x43182210,0x84210842,0x10002190,0x24090240,0x9044018c,0xc030220,0xb81b0300,0xc03,0x2206c0c, + 0x302206c,0x1e0b80c0,0x30220b81,0xb0000003,0xc0881b,0x304006c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x241f2202, + 0x200802,0x4900000,0x8,0x21010408,0x20a0802,0x44090,0x20000000,0x4,0x11878408,0x80411004,0x804082,0x111040,0x1ce50986,0x40986409, + 0x81022,0x12012108,0x80102020,0x1031800,0x400,0x8000,0x80004000,0x10008,0x0,0x0,0x100000,0x0,0x2008,0x2000000,0x10000000, + 0x10000000,0x18,0x4000044,0x1000,0x30180,0xd81b0000,0x13,0xe0000000,0x88,0x40,0x400018c0,0x80400018,0x61f00000,0x61800,0x22020020, + 0x4000007,0xc8000002,0x2100020,0x8038000,0x1e0781e0,0x781e0301,0x40804010,0x4010008,0x2008020,0x41142619,0x86619866,0x18002190, + 0x24090240,0x8887e104,0x0,0x0,0x0,0x0,0x0,0x2000000,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x2434a202, + 0x200802,0x3e00000,0x10,0x40810008,0x21a0804,0x44090,0x20000000,0x80040004,0x20848409,0x409004,0x1004082,0x112040,0x14a50902, + 0x40902409,0x81022,0x11321208,0x80202010,0x1060c00,0x7c5e0,0x781e8783,0xf07a5f0e,0x1c10808,0xfc5f078,0x5e07a170,0x7c7e1024, + 0xa016190,0x27f82008,0x2000000,0x20000000,0x10000000,0x80200024,0x4000044,0x2000,0x18180,0xc8320000,0x12,0xa1f00037,0x7f888, + 0x1e0,0x40410880,0x80600017,0xa2100000,0x5e800,0x22020040,0x38001027,0xc8000002,0x2100020,0x8004020,0x12048120,0x48120482, + 0x41004010,0x4010008,0x2008020,0x40942409,0x2409024,0x9044390,0x24090240,0x88841918,0x1f07c1f0,0x7c1f07c3,0x70781e07,0x81e07838, + 0xe0380e0,0x1f17c1e0,0x781e0781,0xe0001f90,0x24090240,0x9025e102,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0xff241c41, + 0x1001,0x1c02000,0x10,0x40810008,0x6120f85,0xe0086190,0x20c03007,0x8007800c,0x27848419,0x409004,0x1004082,0x114040,0x14a48902, + 0x40902409,0x81022,0x11321205,0x602010,0x1000000,0x86610,0x84218840,0x80866182,0x411008,0x9261884,0x61086189,0x82101022,0x12012108, + 0x40082008,0x2000000,0x20030000,0x20000000,0x80200024,0x4000044,0x3006030,0xc018100,0x4c260000,0x12,0x26080048,0x83000850, + 0x20250,0x403e0500,0x8078002c,0x12302200,0x92400,0x1c0200c0,0x4001027,0xc8000002,0x3308820,0x8004020,0x12048120,0x48120482, + 0x41004010,0x4010008,0x2008020,0x40922409,0x2409024,0x8884690,0x24090240,0x85040920,0x21886218,0x86218860,0x88842108,0x42108408, + 0x2008020,0x21186210,0x84210842,0x10302190,0x24090240,0x88461084,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x4c240182, + 0x80001001,0x6b02000,0x20,0x4c810010,0x78220846,0x10081e10,0x20c0301c,0x1fe0e018,0x4d8487e1,0x409fe7,0xf9007f82,0x11a040, + 0x13248902,0x41102418,0xe0081022,0x11320c05,0x402008,0x1000000,0x2409,0x409020,0x81024082,0x412008,0x9240902,0x40902101,0x101022, + 0x11321208,0x40102008,0x2000000,0x7e0c8000,0xfc000003,0xf0fc0018,0x43802047,0x8c8040c8,0x32008300,0x44240000,0x0,0x4000048, + 0x8c801050,0x20440,0x40221dc0,0x808c0028,0x11d0667f,0x8009c400,0x1fc180,0x4001023,0xc8300002,0x1e0ccfb,0x3ec7b020,0x12048120, + 0x48120482,0x79007f9f,0xe7f9fe08,0x2008020,0xf0922409,0x2409024,0x8504490,0x24090240,0x85040920,0x802008,0x2008020,0x89004090, + 0x24090208,0x2008020,0x40902409,0x2409024,0x8304390,0x24090240,0x88440884,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000, + 0x481c0606,0xc8001001,0x802000,0x20,0x4c810020,0x4220024,0x8102108,0x60000070,0x3820,0x48884419,0x409004,0x10e4082,0x112040, + 0x13244902,0x7e1027e0,0x3c081021,0x21320c02,0x802008,0x1000000,0x7e409,0x409020,0x81024082,0x414008,0x9240902,0x40902101, + 0x80101022,0x11320c08,0x40202008,0x2038800,0x200bc000,0x20000000,0x80200003,0x80f04044,0xbc080bc,0x2f000200,0x0,0x0,0x6001048, + 0x8bc02020,0x20441,0xf8220200,0x80820028,0x1000cc00,0x80094400,0x201e0,0x78001021,0xc830000f,0x8000663c,0xf03c0c0,0x21084210, + 0x84210846,0x41004010,0x4010008,0x2008020,0x40912409,0x2409024,0x8204890,0x24090240,0x82040930,0x1f87e1f8,0x7e1f87e0,0x89004090, + 0x24090208,0x2008020,0x40902409,0x2409024,0x8004690,0x24090240,0x88440884,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000, + 0x480719c4,0x48001001,0x81fc00,0x7800020,0x40810040,0x2420024,0x8104087,0xa0000070,0x3820,0x48884409,0x409004,0x1024082,0x111040, + 0x13244902,0x40102410,0x2081021,0x214a1202,0x1802008,0x1000000,0x182409,0x409fe0,0x81024082,0x41a008,0x9240902,0x40902100, + 0xf8101021,0x214a0c04,0x80c0c008,0x1847000,0x7c1ee000,0x20000000,0x8020000c,0x8c044,0x1ee181ee,0x7b800000,0x707,0xf3ff0000, + 0x3e0084f,0x9ee0c020,0x20440,0x40221fc0,0xc2002c,0x13f11000,0x87892400,0x20000,0x1020,0x48000000,0x3f011c6,0x31cc6180,0x21084210, + 0x84210844,0x41004010,0x4010008,0x2008020,0x40912409,0x2409024,0x8505090,0x24090240,0x8204191c,0x60982609,0x82609823,0xf9007f9f, + 0xe7f9fe08,0x2008020,0x40902409,0x2409024,0x9fe4c90,0x24090240,0x84840848,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xfe048224, + 0x28001001,0x2000,0x40,0x40810080,0x27f8024,0x8104080,0x2000001c,0x1fe0e020,0x488fc409,0x409004,0x1024082,0x110840,0x10242902, + 0x40102408,0x2081021,0x214a1202,0x1002004,0x1000000,0x102409,0x409000,0x81024082,0x411008,0x9240902,0x40902100,0x6101021, + 0x214a0c04,0x81002008,0x2000000,0x201dc000,0x20000000,0x80200000,0x98044,0x1dc101dc,0x77000000,0x700,0x0,0x180448,0x1dc10020, + 0x20440,0x403e0200,0x620017,0xa000cc00,0x80052800,0x20000,0x1020,0x48000000,0x6606,0x206100,0x3f0fc3f0,0xfc3f0fc7,0xc1004010, + 0x4010008,0x2008020,0x4090a409,0x2409024,0x8886090,0x24090240,0x8207e106,0x40902409,0x2409024,0x81004010,0x4010008,0x2008020, + 0x40902409,0x2409024,0x8005890,0x24090240,0x84840848,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x98048224,0x30001001,0x2000, + 0x40,0x21010100,0x2020024,0x8204080,0x40000007,0x80078000,0x48884408,0x80411004,0x824082,0x110840,0x10242986,0x40086409,0x2081021, + 0xe14a2102,0x2002004,0x1000000,0x106409,0x409000,0x81024082,0x410808,0x9240902,0x40902100,0x2101021,0x214a1202,0x82002008, + 0x2000000,0x300f8000,0x20000000,0x80fc001d,0xe4088044,0xf8200f8,0x3e000000,0x300,0x0,0x80c48,0xf820020,0x20640,0x40410200, + 0x803c0018,0x60006600,0x61800,0x0,0x1020,0x48000000,0xcc0a,0x20a100,0x21084210,0x84210844,0x40804010,0x4010008,0x2008020, + 0x4110a619,0x86619866,0x19046110,0x24090240,0x82040102,0x41906419,0x6419064,0x81004010,0x4010008,0x2008020,0x40902409,0x2409024, + 0x8307090,0x24090240,0x82840828,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x90248222,0x30000802,0x200c,0xc080,0x21010301, + 0x4021042,0x10202108,0xc0c03000,0x80040020,0x4d902418,0xc6431004,0xc24082,0x6210440,0x10241884,0x40084409,0x86080840,0xc0842102, + 0x4002002,0x1000000,0x18e610,0x84218820,0x80864082,0x410408,0x9240884,0x61086101,0x6101860,0xc0842103,0x4002008,0x2000000, + 0x10850180,0x20330000,0x80200013,0x26184024,0x5040050,0x14000000,0x0,0x0,0x4180848,0x85040020,0x20350,0x40000200,0x800c0007, + 0x80002200,0x1e000,0x0,0x1860,0x48000000,0x880a,0x40a188,0x40902409,0x2409028,0x40c64010,0x4010008,0x2008020,0x43106210,0x84210842, + 0x10006108,0x42108421,0x2040102,0x6398e639,0x8e6398e4,0x88842088,0x22088208,0x2008020,0x21102210,0x84210842,0x10306118,0x66198661, + 0x83061030,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0x901f01c1,0xe8000802,0xc,0xc080,0x1e07c7f8,0xf8020f81,0xe0401e07, + 0x80c03000,0x20,0x279027e0,0x3c7c1fe4,0x3c408f,0x83c1027f,0x90241878,0x4007c404,0xf8080780,0xc0844082,0x7f82002,0x1000000, + 0xfa5e0,0x781e87c0,0x807a409f,0xc0410207,0x9240878,0x5e07a100,0xf80e0fa0,0xc0846183,0x7f82008,0x2000000,0xf020100,0x40321360, + 0x80200014,0xa3e0201f,0x8207f820,0x8000000,0x0,0x0,0x3e01037,0x207f820,0x201e1,0xfc000200,0x80040000,0x0,0x0,0x1fc000,0x17b0, + 0x48000000,0x12,0xc120f0,0x40902409,0x2409028,0x783c7f9f,0xe7f9fe3e,0xf83e0f8,0x7c1061e0,0x781e0781,0xe000be07,0x81e0781e, + 0x204017c,0x3e8fa3e8,0xfa3e8fa3,0x70781f07,0xc1f07c7f,0x1fc7f1fc,0x1e1021e0,0x781e0781,0xe0007e0f,0xa3e8fa3e,0x8305e030,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0xc06,0xc,0x100,0x0,0x0,0x0,0x3000,0x0,0x20000000,0x0,0x0,0x0,0x0,0xc000, + 0x0,0x0,0x2001,0x1000000,0x0,0x0,0x20000,0x400000,0x0,0x40002000,0x0,0x1,0x2008,0x2000000,0x100,0x40240000,0x80200008,0x40000000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x80040000,0x0,0x0,0x0,0x1000,0x48000000,0x1f,0x181f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1040010,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0x60c,0x18,0x0, + 0x0,0x0,0x0,0x6000,0x0,0x10000000,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x3800,0x7000000,0x0,0x0,0x840000,0x400000,0x0,0x40002000, + 0x0,0x2,0x2008,0x2000000,0x200,0x40440000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x80780000,0x0,0x0,0x0,0x1000,0x48000400, + 0x2,0x1e02000,0x0,0x0,0x80000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,0x0,0x0,0x0,0x0,0x0,0x0,0x2040020,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x4000,0x0,0xf000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x780000,0x3800000,0x0,0x40002000,0x0,0xe,0x1808,0xc000000,0x3,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000000, + 0x0,0x0,0x0,0x1000,0x1c00,0x0,0x0,0x0,0x0,0x380000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x380000,0x0,0x0,0x0,0x0,0x0,0x0,0xe0400e0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3fc, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 }; + + // Definition of a 12x24 font. + const unsigned int font12x24[12*24*256/32] = { + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x19,0x80000000,0x198000,0x0,0x0,0x0,0x0, + 0x0,0x198,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc001806,0xc81980,0x60000000,0xc001806,0x1980c00,0x18060198,0xc80c, + 0x180600,0xc8198000,0xc001,0x80601980,0x18000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf,0x0,0xf0000,0x0,0x0,0x0,0x0,0x0,0x198,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x600300f,0x1301980,0x90000000,0x600300f,0x1980600,0x300f0198,0x13006,0x300f01,0x30198000,0x6003, + 0xf01980,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x6,0x0,0x60000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7007,0x3c0000,0x3006019, + 0x80000000,0x90000000,0x3006019,0x80000300,0x60198000,0x3,0x601980,0x0,0x3006,0x1980000,0x60000000,0x0,0x0,0xe0000000,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000000, + 0x0,0x0,0x0,0x0,0x0,0xc800019,0x80000000,0x198000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc0,0x0,0x0,0x1001,0x420000,0x0,0x0,0x90000000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18000c06,0xc80001,0x10000000,0x18000c06,0x1800,0xc060000,0xc818,0xc0600,0xc8000000, + 0x18000,0xc0600000,0xc000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80660207,0x800f8060,0x300c004,0x0,0x6, + 0xe00703f,0x3f00383,0xf80f07fc,0x1f01f000,0x0,0xf8,0x607f,0x7c7e07,0xfe7fe0f8,0x6063fc1f,0x86066007,0xe7060f0,0x7f80f07f, + 0x81f8fff6,0x6606c03,0x70ee077f,0xe0786000,0xf0070000,0xc000060,0xc0,0x3e000,0x60006003,0x600fc00,0x0,0x0,0x0,0x0,0x0,0x3c0603, + 0xc0000000,0x7800000,0xf0000,0x0,0xf00001f,0x80001fe0,0x7fe000,0x0,0x0,0x0,0x168fe609,0x0,0x90e07,0x6000,0x3c000e,0x70000f8, + 0x1980001f,0x0,0x1f8,0xf00000f,0xf00180,0xfe000,0xe00e,0x1001,0x20060,0x6006006,0x600600,0x600fe07c,0x7fe7fe7f,0xe7fe3fc3, + 0xfc3fc3fc,0x7e07060f,0xf00f00,0xf00f0000,0xf360660,0x6606606e,0x76001e0,0xc00180f,0x1681981,0x10000000,0xc00180f,0x1980c00, + 0x180f0198,0x3801680c,0x180f01,0x68198000,0xc001,0x80f01980,0x18600198,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019, + 0x8044020c,0xc01f8060,0x2004004,0x0,0xc,0x3f81f07f,0x87f80383,0xf81f87fc,0x3f83f800,0x0,0x1fc,0x780607f,0x81fe7f87,0xfe7fe1fc, + 0x6063fc1f,0x860c6007,0xe7061f8,0x7fc1f87f,0xc3fcfff6,0x6606c03,0x30c6067f,0xe0783000,0xf00d8000,0x6000060,0xc0,0x7e000,0x60006003, + 0x600fc00,0x0,0x0,0xc00,0x0,0x0,0x7c0603,0xe0000000,0xfc00000,0x1f0000,0x0,0x900003f,0xc0003fe0,0x7fe000,0x0,0x0,0x0,0x1302660f, + 0x0,0xf0606,0x6004,0x7e0006,0x60601f8,0x19800001,0x80000000,0x1f8,0x19800010,0x81080300,0x3f2000,0x2011,0x1001,0x1c0060,0x6006006, + 0x600600,0x601fe1fe,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7f87061f,0x81f81f81,0xf81f8000,0x3fa60660,0x66066066,0x66003f0,0x6003009, + 0x1301981,0x10000000,0x6003009,0x1980600,0x30090198,0x1f013006,0x300901,0x30198000,0x6003,0x901980,0x30600198,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80cc0f8c,0xc0180060,0x6006044,0x40000000,0xc,0x3181b041,0xc41c0783,0x388018, + 0x71c71800,0x0,0x106,0x18c0f061,0xc38261c6,0x600384,0x60606001,0x86186007,0xe78630c,0x60e30c60,0xe7040606,0x630cc03,0x39c30c00, + 0xc0603000,0x3018c000,0x3000060,0xc0,0x60000,0x60000000,0x6000c00,0x0,0x0,0xc00,0x0,0x0,0x600600,0x60000000,0x18400000,0x180000, + 0x0,0x19800070,0x40003600,0xc000,0x0,0x0,0x0,0x25a06,0x0,0x6030c,0x4,0xe20007,0xe060180,0xf000,0x80000000,0xf0000,0x10800000, + 0x80080600,0x7f2000,0x2020,0x80001001,0x20000,0xf00f00f,0xf00f00,0x601b0382,0x60060060,0x6000600,0x60060060,0x61c78630,0xc30c30c3, + 0xc30c000,0x30e60660,0x66066063,0xc600738,0x3006019,0x80000000,0xe0000000,0x3006019,0x80000300,0x60198000,0x3e000003,0x601980, + 0x0,0x3006,0x1980000,0x60600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80cc1fcc,0xc0180060,0x6006035,0x80000000, + 0x18,0x71c03000,0xc00c0583,0x300018,0x60c60c00,0x0,0x6,0x3060f060,0xc30060c6,0x600300,0x60606001,0x86306007,0x9e78670e,0x60670e60, + 0x66000606,0x630c606,0x19830c01,0xc0601800,0x30306000,0x60,0xc0,0x60000,0x60000000,0x6000c00,0x0,0x0,0xc00,0x0,0x0,0x600600, + 0x60000000,0x18000000,0x300000,0x0,0x78060,0x6600,0x1c000,0x300c,0x39819c0,0x0,0x25a00,0x0,0x30c,0x4,0xc00003,0xc060180,0x30c1f, + 0x80000000,0x30c000,0x10800001,0x80700000,0x7f2000,0x2020,0x80001001,0x20060,0xf00f00f,0xf00f00,0xf01b0300,0x60060060,0x6000600, + 0x60060060,0x60c78670,0xe70e70e7,0xe70e000,0x70c60660,0x66066063,0xc7f8618,0x0,0x0,0x0,0x0,0x0,0x0,0x7000000,0x0,0x0,0x0, + 0x0,0x600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x87ff3a4c,0xc0180060,0x400600e,0x600000,0x18,0x60c03000, + 0xc00c0d83,0x700018,0x60c60c00,0x20,0x400006,0x3060f060,0xc6006066,0x600600,0x60606001,0x86606006,0x966c6606,0x60660660,0x66000606, + 0x630c666,0xf019801,0x80601800,0x30603000,0x1f06f,0xf01ec0,0xf03fe1ec,0x6703e01f,0x61c0c06,0xdc6701f0,0x6f01ec0c,0xe1f87fc6, + 0xc60cc03,0x71c60c7f,0xc0600600,0x60000000,0x30000000,0x300000,0x40040,0x88060,0x6600,0x18000,0x300c,0x1981980,0x0,0x2421f, + 0x80003ce0,0x7fc198,0x601f,0xc02021,0x980600c0,0x40230,0x80000000,0x402000,0x19806003,0x80006,0xc7f2000,0x2020,0x80001001, + 0x420060,0xf00f00f,0xf00f00,0xf01b0600,0x60060060,0x6000600,0x60060060,0x6066c660,0x66066066,0x6606208,0x60e60660,0x66066061, + 0x987fc670,0x1f01f01f,0x1f01f01,0xf039c0f0,0xf00f00f,0xf03e03,0xe03e03e0,0x1f06701f,0x1f01f01,0xf01f0060,0x1e660c60,0xc60c60c6, + 0xc6f060c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x7ff3207,0x8c0c0000,0xc00300e,0x600000,0x30,0x60c03000, + 0xc01c0983,0xf0600030,0x31860c06,0x6001e0,0x78000e,0x23e1f861,0xc6006066,0x600600,0x60606001,0x86c06006,0x966c6606,0x60660660, + 0xe7000606,0x630c666,0xf01f803,0x600c00,0x30000000,0x3f87f,0x83f83fc3,0xf83fe3fc,0x7f83e01f,0x6380c07,0xfe7f83f8,0x7f83fc0d, + 0xf3fc7fc6,0xc71cc03,0x3183187f,0xc0600600,0x60000000,0xff806000,0x300000,0x40040,0x88070,0x6600,0x60030060,0x6001818,0x1883180, + 0x0,0x2423f,0xc0007ff0,0x607fc1f8,0x603f,0x80c01fc1,0xf80601e0,0x5f220,0x80420000,0x5f2000,0xf006006,0x80006,0xc7f2000,0x2020, + 0x82107c07,0xc03c0060,0x1f81f81f,0x81f81f80,0xf03b0600,0x60060060,0x6000600,0x60060060,0x6066c660,0x66066066,0x660671c,0x61660660, + 0x66066061,0xf860e6c0,0x3f83f83f,0x83f83f83,0xf87fe3f8,0x3f83f83f,0x83f83e03,0xe03e03e0,0x3f87f83f,0x83f83f83,0xf83f8060, + 0x3fc60c60,0xc60c60c3,0x187f8318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x883200,0x300c0000,0xc003035,0x80600000, + 0x30,0x66c03001,0xc0f81983,0xf86f0030,0x1f071c06,0x600787,0xfe1e001c,0x6261987f,0x86006067,0xfe7fc600,0x7fe06001,0x87c06006, + 0xf6646606,0x60e6067f,0xc3e00606,0x61986f6,0x600f007,0x600c00,0x30000000,0x21c71,0x830831c3,0x1c06031c,0x71c06003,0x6700c06, + 0x6671c318,0x71831c0f,0x16040c06,0xc318606,0x1b031803,0x80600600,0x60000000,0x30009000,0x300000,0x40040,0x7003e,0x67e0,0x90070090, + 0x9001818,0x8c3100,0x0,0x60,0x4000e730,0x900380f0,0x6034,0x80c018c7,0xfe060338,0xb0121,0x80c60000,0x909000,0x6008,0x1080006, + 0xc3f2000,0x2011,0x3180060,0x60060e0,0x19819819,0x81981981,0x9833c600,0x7fe7fe7f,0xe7fe0600,0x60060060,0x60664660,0x66066066, + 0x66063b8,0x62660660,0x66066060,0xf06066c0,0x21c21c21,0xc21c21c2,0x1c466308,0x31c31c31,0xc31c0600,0x60060060,0x31871c31,0x83183183, + 0x18318000,0x71860c60,0xc60c60c3,0x18718318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x1981a00,0xe03e0000,0xc003044, + 0x40600000,0x60,0x66c03001,0x80f03182,0x1c7f8030,0x3f83fc06,0x601e07,0xfe078038,0x6661987f,0x86006067,0xfe7fc61e,0x7fe06001, + 0x87e06006,0x66666606,0x7fc6067f,0x81f80606,0x61986f6,0x6006006,0x600600,0x30000000,0xc60,0xc60060c6,0xc06060c,0x60c06003, + 0x6e00c06,0x6660c60c,0x60c60c0e,0x6000c06,0xc318666,0x1f031803,0x600600,0x603c2000,0x30016800,0x1fe0000,0x1f81f8,0x1c1f,0x804067e1, + 0x68060168,0x16800810,0xc42300,0x0,0x60,0x20c331,0x68030060,0x6064,0x3fc1040,0xf006031c,0xa011e,0x818c7fe0,0x909000,0x7fe1f, + 0x80f00006,0xc0f2060,0xf80e,0x18c0780,0x780781c0,0x19819819,0x81981981,0x9833c600,0x7fe7fe7f,0xe7fe0600,0x60060060,0xfc666660, + 0x66066066,0x66061f0,0x66660660,0x66066060,0x606066e0,0xc00c00,0xc00c00c0,0xc066600,0x60c60c60,0xc60c0600,0x60060060,0x60c60c60, + 0xc60c60c6,0xc60c000,0x61c60c60,0xc60c60c3,0x1860c318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x1980f81,0x80373000, + 0xc003004,0x7fe0001,0xf0000060,0x60c03003,0x183180,0xc71c060,0x3181ec00,0x7000,0xe070,0x66619860,0xc6006066,0x60061e,0x60606001, + 0x87606006,0x66626606,0x7f860661,0xc01c0606,0x6198696,0xf00600e,0x600600,0x30000000,0x1fc60,0xc60060c7,0xfc06060c,0x60c06003, + 0x7c00c06,0x6660c60c,0x60c60c0c,0x7f00c06,0xc3b8666,0xe01b007,0x3c00600,0x3c7fe000,0xff03ec00,0x1fe0000,0x40040,0xe001,0xc0806603, + 0xec0e03ec,0x3ec00010,0x0,0x60000000,0x7f,0x10c3f3,0xec070060,0x6064,0x3fc1040,0x6000030c,0xa0100,0x3187fe1,0xf09f1000,0x7fe00, + 0x6,0xc012060,0x0,0xc63c03,0xc03c0380,0x19819819,0x81981981,0x98330600,0x60060060,0x6000600,0x60060060,0xfc662660,0x66066066, + 0x66060e0,0x6c660660,0x66066060,0x6060e630,0x1fc1fc1f,0xc1fc1fc1,0xfc3fe600,0x7fc7fc7f,0xc7fc0600,0x60060060,0x60c60c60,0xc60c60c6, + 0xc60c7fe,0x62c60c60,0xc60c60c1,0xb060c1b0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0xffe02c6,0x3c633000,0xc003004, + 0x7fe0001,0xf00000c0,0x60c03006,0xc6180,0xc60c060,0x60c00c00,0x7000,0xe060,0x66639c60,0x66006066,0x600606,0x60606001,0x86306006, + 0x66636606,0x60060660,0xc0060606,0x61f8696,0xf00600c,0x600300,0x30000000,0x3fc60,0xc60060c7,0xfc06060c,0x60c06003,0x7c00c06, + 0x6660c60c,0x60c60c0c,0x1f80c06,0xc1b0666,0xe01b00e,0x3c00600,0x3c43c000,0x3007de00,0x600000,0x40040,0x30000,0x61006607,0xde0c07de, + 0x7de00000,0x0,0xf07fefff,0x1f,0x8008c3f7,0xde0e0060,0x6064,0xc01047,0xfe00018c,0xb013f,0x86300061,0xf0911000,0x6000,0x6, + 0xc012060,0x3f,0x8063c0cc,0x3cc0c700,0x39c39c39,0xc39c39c1,0x98630600,0x60060060,0x6000600,0x60060060,0x60663660,0x66066066, + 0x66061f0,0x78660660,0x66066060,0x607fc618,0x3fc3fc3f,0xc3fc3fc3,0xfc7fe600,0x7fc7fc7f,0xc7fc0600,0x60060060,0x60c60c60,0xc60c60c6, + 0xc60c7fe,0x64c60c60,0xc60c60c1,0xb060c1b0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0xffe0260,0x6661b000,0xc003000, + 0x600000,0xc0,0x60c0300c,0xc7fe0,0xc60c060,0x60c01c00,0x1e07,0xfe078060,0x6663fc60,0x66006066,0x600606,0x60606001,0x86386006, + 0x6636606,0x60060660,0xe0060606,0x60f039c,0x1b806018,0x600300,0x30000000,0x70c60,0xc60060c6,0x6060c,0x60c06003,0x7600c06, + 0x6660c60c,0x60c60c0c,0x1c0c06,0xc1b03fc,0xe01f01c,0xe00600,0x70000000,0x3007fc00,0x600000,0x40040,0x0,0x62006607,0xfc1807fc, + 0x7fc00000,0x0,0xf0000000,0x1,0xc004c307,0xfc1c0060,0x6064,0xc018c0,0x600000d8,0x5f200,0x3180060,0x50a000,0x6000,0x6,0xc012000, + 0x0,0xc601c0,0x4201c600,0x3fc3fc3f,0xc3fc3fc3,0xfc7f0600,0x60060060,0x6000600,0x60060060,0x60663660,0x66066066,0x66063b8, + 0x70660660,0x66066060,0x607f860c,0x70c70c70,0xc70c70c7,0xcc60600,0x60060060,0x6000600,0x60060060,0x60c60c60,0xc60c60c6,0xc60c000, + 0x68c60c60,0xc60c60c1,0xf060c1f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3300260,0x6661e000,0xc003000,0x600000, + 0x180,0x71c03018,0xc7fe0,0xc60c0c0,0x60c01800,0x787,0xfe1e0060,0x6663fc60,0x630060c6,0x600306,0x60606001,0x86186006,0x661e70e, + 0x60070c60,0x60060606,0x60f039c,0x19806038,0x600180,0x30000000,0x60c60,0xc60060c6,0x6060c,0x60c06003,0x6700c06,0x6660c60c, + 0x60c60c0c,0xc0c06,0xc1b039c,0x1f00e018,0x600600,0x60000000,0x1803f800,0x600000,0x40040,0x39e00,0x63006603,0xf83803f8,0x3f800000, + 0x0,0x60000000,0x0,0xc00cc303,0xf8180060,0x6064,0xc01fc0,0x60060070,0x40200,0x18c0060,0x402000,0x6000,0x6,0xc012000,0x0,0x18c0140, + 0x2014600,0x3fc3fc3f,0xc3fc3fc3,0xfc7f0300,0x60060060,0x6000600,0x60060060,0x60c61e70,0xe70e70e7,0xe70e71c,0x60e60660,0x66066060, + 0x6060060c,0x60c60c60,0xc60c60c6,0xcc60600,0x60060060,0x6000600,0x60060060,0x60c60c60,0xc60c60c6,0xc60c000,0x70c60c60,0xc60c60c0, + 0xe060c0e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x33022e0,0x6670c000,0xc003000,0x600600,0x60180,0x31803030, + 0x41c0184,0x1831c0c0,0x71c23806,0x6001e0,0x780000,0x62630c60,0xe38261c6,0x600386,0x60606043,0x860c6006,0x661e30c,0x60030c60, + 0x740e0607,0xe0f039c,0x31c06030,0x600180,0x30000000,0x61c71,0x830831c3,0x406031c,0x60c06003,0x6300c06,0x6660c318,0x71831c0c, + 0x41c0c07,0x1c0e039c,0x1b00e030,0x600600,0x60000000,0x1c41b00e,0x601cc0,0x401f8,0x45240,0xe1803601,0xb03001b0,0x1b000000, + 0x0,0x0,0x41,0xc008e711,0xb0300060,0x6034,0x80c02020,0x60060030,0x30c00,0xc60000,0x30c000,0x0,0x7,0x1c012000,0x0,0x3180240, + 0x6024608,0x30c30c30,0xc30c30c3,0xc630382,0x60060060,0x6000600,0x60060060,0x61c61e30,0xc30c30c3,0xc30c208,0x70c70e70,0xe70e70e0, + 0x6060068c,0x61c61c61,0xc61c61c6,0x1cc62308,0x30430430,0x43040600,0x60060060,0x31860c31,0x83183183,0x18318060,0x31c71c71, + 0xc71c71c0,0xe07180e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x2203fc0,0x663f6000,0x6006000,0x600600,0x60300, + 0x3f81fe7f,0xc7f80187,0xf83f80c0,0x3f83f006,0x600020,0x400060,0x33e6067f,0xc1fe7f87,0xfe6001fe,0x6063fc7f,0x60e7fe6,0x660e3f8, + 0x6001f860,0x37fc0603,0xfc06030c,0x30c0607f,0xe06000c0,0x30000000,0x7fc7f,0x83f83fc3,0xfc0603fc,0x60c7fe03,0x61807c6,0x6660c3f8, + 0x7f83fc0c,0x7f80fc3,0xfc0e039c,0x3180607f,0xc0600600,0x60000000,0xfc0e00c,0x601986,0x66040040,0x4527f,0xc0803fe0,0xe07fe0e0, + 0xe000000,0x0,0x0,0x7f,0x80107ff0,0xe07fc060,0x603f,0x83fe0000,0x60060018,0xf000,0x420000,0xf0000,0x7fe00,0x7,0xfe012000, + 0x0,0x2100640,0xc0643f8,0x60660660,0x66066067,0xec3e1fe,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7f860e3f,0x83f83f83,0xf83f8000, + 0x5fc3fc3f,0xc3fc3fc0,0x606006fc,0x7fc7fc7f,0xc7fc7fc7,0xfcffe3f8,0x3fc3fc3f,0xc3fc7fe7,0xfe7fe7fe,0x3f860c3f,0x83f83f83, + 0xf83f8060,0x7f83fc3f,0xc3fc3fc0,0x607f8060,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x2201f80,0x3c1e7000,0x6006000, + 0x600,0x60300,0xe01fe7f,0xc3f00183,0xe01f0180,0x1f01e006,0x600000,0x60,0x3006067f,0x807c7e07,0xfe6000f8,0x6063fc3e,0x6067fe6, + 0x660e0f0,0x6000f060,0x3bf80601,0xf806030c,0x60e0607f,0xe06000c0,0x30000000,0x1ec6f,0xf01ec0,0xf80601ec,0x60c7fe03,0x61c03c6, + 0x6660c1f0,0x6f01ec0c,0x3f007c1,0xcc0e030c,0x71c0c07f,0xc0600600,0x60000000,0x7804018,0xe01186,0x66040040,0x39e3f,0x80401fe0, + 0x407fe040,0x4000000,0x0,0x0,0x3f,0x203ce0,0x407fc060,0x601f,0x3fe0000,0x60060018,0x0,0x0,0x0,0x7fe00,0x6,0xe6012000,0x0, + 0x7e0,0x1807e1f0,0x60660660,0x66066066,0x6c3e07c,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7e060e0f,0xf00f00,0xf00f0000,0x8f01f81f, + 0x81f81f80,0x60600670,0x1ec1ec1e,0xc1ec1ec1,0xec79c0f0,0xf80f80f,0x80f87fe7,0xfe7fe7fe,0x1f060c1f,0x1f01f01,0xf01f0000,0x4f01cc1c, + 0xc1cc1cc0,0xc06f00c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x6006000,0x600,0x600,0x0,0x0,0x0,0x0, + 0x600000,0x0,0x18000000,0x0,0x0,0x0,0x0,0x0,0x1800,0x0,0x0,0x0,0x600060,0x30000000,0x0,0x0,0xc,0x3,0x0,0x0,0x60000c00,0x0, + 0x0,0xc000,0x600600,0x60000000,0x18,0xc03100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x601f8,0x0,0x0,0x0,0x0,0x6, + 0x12000,0x2000000,0x40,0x20004000,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0xc06000c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x2004000,0xc00,0x0,0x0,0x0,0x0,0x0,0xc00000, + 0x0,0x1c000000,0x0,0x0,0x0,0x0,0x0,0xc00,0x0,0x0,0x0,0x780000,0xf0000000,0x0,0x0,0x21c,0x3,0x0,0x0,0x60000c00,0x0,0x0,0xc000, + 0x7c0603,0xe0000000,0x10,0xc02300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x601f0,0x0,0x0,0x0,0x0,0x6,0x12000,0x1000000, + 0x40,0x7e004000,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc06000c0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x300c000,0xc00,0x0,0x0,0x0,0x0,0x0,0xc00000,0x0,0x7800000,0x0, + 0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x780000,0xf0000000,0x0,0x0,0x3f8,0x3e,0x0,0x0,0x60000c00,0x0,0x0,0x38000,0x3c0603,0xc0000000, + 0x10,0xfc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x60000,0x0,0x0,0x0,0x0,0x6,0x0,0x1000000,0x0,0x0,0x0,0x0, + 0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0x80600380,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffc,0x0, + 0x0,0x1f0,0x3c,0x0,0x0,0x60000c00,0x0,0x0,0x38000,0x600,0x0,0x0,0xf000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x6,0x0,0xe000000,0x0,0x0,0x0,0x0,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x3,0x80600380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 }; + + // Definition of a 16x32 font. + const unsigned int font16x32[16*32*256/32] = { + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc300000,0x0,0xc300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70000e0,0x3c00730,0xe7001c0,0x0,0x70000e0,0x3c00e70,0x70000e0,0x3c00e70,0x730,0x70000e0,0x3c00730, + 0xe700000,0x700,0xe003c0,0xe7000e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x6600000,0x0,0x6600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x18001c0,0x6600ff0,0xe7003e0,0x0,0x18001c0,0x6600e70,0x18001c0,0x6600e70,0xff0,0x18001c0,0x6600ff0,0xe700000,0x180, + 0x1c00660,0xe7001c0,0x0,0x0,0x0,0x380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000, + 0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00380, + 0xc300ce0,0xe700630,0x0,0x1c00380,0xc300e70,0x1c00380,0xc300e70,0xce0,0x1c00380,0xc300ce0,0xe700000,0x1c0,0x3800c30,0xe700380, + 0x0,0x0,0x0,0x7c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0xc300000,0x0,0xc300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x700000,0x0,0x0,0x0,0x7c007c00,0x3e000000, + 0x0,0x0,0x630,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe000070,0x1800000,0xc60,0x0,0xe000070,0x1800000,0xe000070, + 0x1800000,0x0,0xe000070,0x1800000,0x0,0xe00,0x700180,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x800000,0x0,0x600600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x3f0,0xfc0,0x0,0x7000000,0x38000000,0x1c0000,0xfc0000,0x380001c0,0xe01c00,0x7f800000,0x0,0x0,0x0,0x0,0x0,0x0,0x7c, + 0x1801f00,0x0,0x0,0x1c,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7300000,0x6600000,0x0,0x6600000,0x0,0x0,0x0,0x0,0xe700000, + 0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0x0,0xc000c00,0x43800000,0x0,0x0,0x630,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0xf80,0x70000e0,0x3c00730,0xe700c60,0x0,0x70000e0,0x3c00e70,0x70000e0,0x3c00e70,0xe000730,0x70000e0,0x3c00730,0xe700000,0x700, + 0xe003c0,0xe7000e0,0x38000e70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300000,0x803c00,0x7c00180, + 0xc00300,0x1000000,0x0,0x1c,0x3c007c0,0xfc007e0,0xe01ff8,0x3f03ffc,0x7e007c0,0x0,0x0,0x7c0,0x1c0,0x7f8003f0,0x7f007ff8,0x7ff803f0, + 0x70381ffc,0xff0700e,0x7000783c,0x783807c0,0x7fc007c0,0x7fc00fc0,0x7fff7038,0x700ee007,0x780f780f,0x7ffc03f0,0x70000fc0,0x3c00000, + 0x3000000,0x38000000,0x1c0000,0x1fc0000,0x380001c0,0xe01c00,0x7f800000,0x0,0x0,0x0,0x0,0x0,0x0,0xfc,0x1801f80,0x0,0x1f80000, + 0x7e,0x0,0x0,0x2400000,0xfc00000,0x7ff0000,0x7ffc0000,0x0,0x0,0x0,0x0,0xf30fb0c,0x2400000,0x0,0x240780f,0x1c0,0xfc,0x780f, + 0x18003f0,0xe700000,0x7c00000,0x0,0xff0,0x3c00000,0x78007c0,0xc00000,0xff80000,0xf80,0x7c00000,0xc000c00,0x18001c0,0x1c001c0, + 0x1c001c0,0x1c003e0,0x7fe03f0,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,0x7f007838,0x7c007c0,0x7c007c0,0x7c00000,0x7c67038, + 0x70387038,0x7038780f,0x70001fe0,0x30000c0,0x2400f30,0xe700c60,0x0,0x30000c0,0x2400e70,0x30000c0,0x2400e70,0xf700f30,0x30000c0, + 0x2400f30,0xe700000,0x300,0xc00240,0xe7000c0,0x38000e70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0, + 0x630018c,0x807e00,0xfe00180,0xc00300,0x1000000,0x0,0x38,0xff01fc0,0x3ff01ff0,0x1e01ff8,0x7f83ffc,0x1ff80ff0,0x0,0x0,0xff0, + 0x1f003e0,0x7fe00ff8,0x7fc07ff8,0x7ff80ff8,0x70381ffc,0xff0701c,0x7000783c,0x78381ff0,0x7fe01ff0,0x7fe01ff0,0x7fff7038,0x781ee007, + 0x3c1e380e,0x7ffc0380,0x380001c0,0x3c00000,0x1800000,0x38000000,0x1c0000,0x3c00000,0x380001c0,0xe01c00,0x3800000,0x0,0x0, + 0x0,0x7000000,0x0,0x0,0x1e0,0x18003c0,0x0,0x3fc0000,0x70,0x0,0x0,0x6600000,0x1ff00000,0x1fff0000,0x7ffc0000,0x0,0x0,0x0,0x0, + 0xcf0239c,0x3c00000,0x0,0x3c0380e,0x1c0,0x2001fe,0x380e,0x18007f8,0xe700000,0x8600000,0x0,0xff0,0x7e00000,0x8c00870,0x1800000, + 0x1ff80000,0x180,0xc600000,0xc000c00,0x38001c0,0x3e003e0,0x3e003e0,0x3e001c0,0x7fe0ff8,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc, + 0x7fc07838,0x1ff01ff0,0x1ff01ff0,0x1ff00000,0x1fec7038,0x70387038,0x7038380e,0x70003ce0,0x1800180,0x6600cf0,0xe7007c0,0x0, + 0x1800180,0x6600e70,0x1800180,0x6600e70,0x7c00cf0,0x1800180,0x6600cf0,0xe700000,0x180,0x1800660,0xe700180,0x38000e70,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630030c,0x3f0e700,0x1e200180,0x1800180,0x21100000,0x0, + 0x38,0x1e7819c0,0x38781038,0x1e01c00,0xf080038,0x1c381c38,0x0,0x0,0x1878,0x7fc03e0,0x70e01e18,0x70e07000,0x70001e18,0x703801c0, + 0x707038,0x70007c7c,0x7c381c70,0x70701c70,0x70703830,0x1c07038,0x381ce007,0x1c1c3c1e,0x3c0380,0x380001c0,0x7e00000,0xc00000, + 0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,0x0,0x70c0000,0xe0, + 0x0,0x0,0xc300000,0x38300000,0x3c700000,0x3c0000,0x0,0x0,0x0,0x0,0xce022f4,0x1800000,0x0,0x1803c1e,0x1c0,0x2003c2,0x3c1e, + 0x1800e08,0x7e0,0x300000,0x0,0x7e00000,0xe700000,0x600030,0x3000000,0x3f980000,0x180,0x18200000,0xc000c00,0x1e0001c0,0x3e003e0, + 0x3e003e0,0x3e003e0,0xfe01e18,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70e07c38,0x1c701c70,0x1c701c70,0x1c700000,0x3c787038, + 0x70387038,0x70383c1e,0x70003870,0xc00300,0xc300ce0,0x380,0x0,0xc00300,0xc300000,0xc00300,0xc300000,0xfc00ce0,0xc00300,0xc300ce0, + 0x0,0xc0,0x3000c30,0x300,0x38000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630031c,0xff8c300, + 0x1c000180,0x1800180,0x39380000,0x0,0x70,0x1c3801c0,0x203c001c,0x3e01c00,0x1c000038,0x381c3838,0x0,0x0,0x1038,0xe0e03e0,0x70703c08, + 0x70707000,0x70003808,0x703801c0,0x707070,0x70007c7c,0x7c383838,0x70383838,0x70387010,0x1c07038,0x381c700e,0x1e3c1c1c,0x780380, + 0x1c0001c0,0xe700000,0x0,0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0, + 0x0,0xe000000,0xe0,0x0,0x1000100,0x3800,0x70100000,0x38700000,0x780000,0x1c0,0x7801ce0,0xe380000,0x0,0x2264,0x0,0x0,0x1c1c, + 0x0,0x200780,0x1c1c,0x1800c00,0x1818,0x7f00000,0x0,0x18180000,0xc300000,0x600070,0x0,0x7f980000,0x180,0x18300000,0xc000c00, + 0x3000000,0x3e003e0,0x3e003e0,0x3e003e0,0xee03c08,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70707c38,0x38383838,0x38383838, + 0x38380000,0x38387038,0x70387038,0x70381c1c,0x7fc03870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xbc00000,0x0,0x0,0x0,0x0,0x0,0x0, + 0x38000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300318,0xe88c300,0x1c000180,0x38001c0, + 0xfe00180,0x0,0x70,0x1c3801c0,0x1c001c,0x6e01c00,0x1c000078,0x381c3818,0x0,0x40000,0x40000038,0x1c0607e0,0x70703800,0x70707000, + 0x70003800,0x703801c0,0x7070e0,0x70007c7c,0x7c383838,0x70383838,0x70387000,0x1c07038,0x381c700e,0xf780e38,0x700380,0x1c0001c0, + 0x1c380000,0x0,0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,0x0, + 0xe000000,0xe0,0x0,0x1000100,0x4400,0x70000000,0x38700000,0x700000,0xe0,0x7001c70,0xe380000,0x0,0x2264,0x0,0x0,0xe38,0x0, + 0x200700,0xe38,0x1800c00,0x300c,0xc300000,0x0,0x300c0000,0xc300180,0x6003c0,0x0,0x7f980000,0x180,0x18300000,0xc000c00,0x1800000, + 0x7e007e0,0x7e007e0,0x7e003e0,0xee03800,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70707c38,0x38383838,0x38383838,0x38380000, + 0x38387038,0x70387038,0x70380e38,0x7ff039f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e00000,0x0,0x0,0x0,0x40000,0x0,0x0,0x38000000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300318,0x1c80e700,0x1c000180,0x38001c0,0x3800180, + 0x0,0xe0,0x381c01c0,0x1c001c,0x6e01c00,0x38000070,0x381c381c,0x0,0x3c0000,0x78000078,0x38030770,0x70707800,0x70387000,0x70007000, + 0x703801c0,0x7071c0,0x7000745c,0x7638701c,0x7038701c,0x70387000,0x1c07038,0x1c38718e,0x7700f78,0xf00380,0xe0001c0,0x381c0000, + 0x7e0,0x39e003e0,0x79c03f0,0x3ffc079c,0x39e01fc0,0xfe01c1e,0x3807778,0x39e007e0,0x39e0079c,0x73c07e0,0x7ff83838,0x701ce007, + 0x783c701c,0x1ffc01c0,0x18001c0,0x0,0x1c000100,0xe0,0x0,0x1000100,0x4200,0x70000000,0x70700100,0xf00100,0x10000e0,0x7000c70, + 0xc700000,0x0,0x2204,0x7e00000,0x1e380100,0x1ffc0f78,0x0,0xf80700,0xf78,0x1800e00,0x63e6,0x18300000,0x0,0x6fe60000,0xe700180, + 0xc00060,0x3838,0x7f980000,0x180,0x18300000,0xc000c00,0x18001c0,0x7700770,0x7700770,0x77007f0,0xee07800,0x70007000,0x70007000, + 0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c1008,0x707c7038,0x70387038,0x70380f78,0x707039c0,0x7e007e0,0x7e007e0, + 0x7e007e0,0x1f3c03e0,0x3f003f0,0x3f003f0,0x1fc01fc0,0x1fc01fc0,0x7f039e0,0x7e007e0,0x7e007e0,0x7e00380,0x7ce3838,0x38383838, + 0x3838701c,0x39e0701c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6307fff,0x1c807e0c,0xe000180, + 0x30000c0,0x3800180,0x0,0xe0,0x381c01c0,0x1c001c,0xce01fe0,0x38000070,0x381c381c,0x3800380,0xfc0000,0x7e0000f0,0x30030770, + 0x70707000,0x70387000,0x70007000,0x703801c0,0x707380,0x700076dc,0x7638701c,0x7038701c,0x70387800,0x1c07038,0x1c3873ce,0x7f00770, + 0xe00380,0xe0001c0,0x700e0000,0x1ff8,0x3ff00ff0,0xffc0ff8,0x3ffc0ffc,0x3bf01fc0,0xfe01c3c,0x3807f78,0x3bf00ff0,0x3ff00ffc, + 0x77e0ff0,0x7ff83838,0x3838e007,0x3c783838,0x1ffc01c0,0x18001c0,0x0,0x7ff00380,0x1e0,0x0,0x1000100,0x4200,0x78000000,0x70700380, + 0xe00380,0x3800060,0xe000e30,0x1c600000,0x0,0x2204,0xff00000,0x7f7c0380,0x1ffc0770,0x1c0,0x3fc0700,0x18040770,0x1800780,0x4e12, + 0x18300104,0x0,0x4c320000,0x7e00180,0x1c00030,0x3838,0x7f980000,0x180,0x18302080,0xc000c00,0x18001c0,0x7700770,0x7700770, + 0x7700770,0x1ee07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c381c,0x705c7038,0x70387038, + 0x70380770,0x70383b80,0x1ff81ff8,0x1ff81ff8,0x1ff81ff8,0x3fbe0ff0,0xff80ff8,0xff80ff8,0x1fc01fc0,0x1fc01fc0,0xff83bf0,0xff00ff0, + 0xff00ff0,0xff00380,0xffc3838,0x38383838,0x38383838,0x3ff03838,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x1c0,0x7fff,0x1c803c38,0xf000000,0x70000e0,0xfe00180,0x0,0x1c0,0x381c01c0,0x3c0078,0xce01ff0,0x39e000f0,0x1c38381c,0x3800380, + 0x3e07ffc,0xf8001f0,0x307b0770,0x70e07000,0x70387000,0x70007000,0x703801c0,0x707700,0x700076dc,0x7638701c,0x7038701c,0x70387e00, + 0x1c07038,0x1c3873ce,0x3e007f0,0x1e00380,0x70001c0,0x0,0x1038,0x3c381e18,0x1c7c1e3c,0x3801e3c,0x3c7801c0,0xe01c78,0x380739c, + 0x3c781c38,0x3c381c3c,0x7c21e10,0x7003838,0x3838700e,0x1ef03838,0x3c01c0,0x18001c0,0x0,0x7fe007c0,0x1c0,0x0,0x1000100,0x6400, + 0x7e000000,0x707007c0,0x1e007c0,0x7c00070,0xe000638,0x18600000,0x0,0x0,0x1e100000,0x73ce07c0,0x3c07f0,0x1c0,0x7240700,0x1ddc3ffe, + 0x1800de0,0x8c01,0x1870030c,0x0,0x8c310000,0x3c00180,0x3800030,0x3838,0x7f980000,0x180,0x183030c0,0xc000c00,0x430001c0,0x7700770, + 0x7700770,0x7700770,0x1ce07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c1c38,0x70dc7038, + 0x70387038,0x703807f0,0x70383b80,0x10381038,0x10381038,0x10381038,0x21e71e18,0x1e3c1e3c,0x1e3c1e3c,0x1c001c0,0x1c001c0,0x1e383c78, + 0x1c381c38,0x1c381c38,0x1c380380,0x1c383838,0x38383838,0x38383838,0x3c383838,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x1c0,0x630,0x1e8000e0,0x1f000000,0x70000e0,0x39380180,0x0,0x1c0,0x3b9c01c0,0x3c07f0,0x18e01078,0x3bf800e0, + 0x7e0383c,0x3800380,0x1f807ffc,0x3f001c0,0x61ff0e38,0x7fc07000,0x70387ff0,0x7ff07000,0x7ff801c0,0x707f00,0x7000729c,0x7338701c, + 0x7070701c,0x70703fc0,0x1c07038,0x1e7873ce,0x1c003e0,0x3c00380,0x70001c0,0x0,0x1c,0x3c381c00,0x1c3c1c1c,0x3801c3c,0x383801c0, + 0xe01cf0,0x380739c,0x38381c38,0x3c381c3c,0x7801c00,0x7003838,0x3838700e,0xfe03c78,0x7801c0,0x18001c0,0x0,0x1c000c20,0xff8, + 0x0,0x1ff01ff0,0x3818,0x3fc00100,0x707e0c20,0x3c00c20,0xc200030,0xc000618,0x18c00000,0x0,0x0,0x1c000080,0xe1ce0c20,0x7803e0, + 0x1c0,0xe200700,0xff83ffe,0x1801878,0x9801,0x1cf0071c,0x7ffc0000,0x8c310000,0x7ffe,0x7000030,0x3838,0x3f980380,0x180,0xc6038e0, + 0x7f9c7f9c,0x3e1c01c0,0xe380e38,0xe380e38,0xe380f78,0x1cfc7000,0x7ff07ff0,0x7ff07ff0,0x1c001c0,0x1c001c0,0xfe387338,0x701c701c, + 0x701c701c,0x701c0e70,0x719c7038,0x70387038,0x703803e0,0x70383b80,0x1c001c,0x1c001c,0x1c001c,0xe71c00,0x1c1c1c1c,0x1c1c1c1c, + 0x1c001c0,0x1c001c0,0x1c383838,0x1c381c38,0x1c381c38,0x1c380000,0x3c383838,0x38383838,0x38383c78,0x3c383c78,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630,0xf800380,0x3f830000,0x70000e0,0x31080180,0x0,0x380,0x3b9c01c0, + 0x7807e0,0x38e00038,0x3c3800e0,0xff01c3c,0x3800380,0x7c000000,0x7c03c0,0x61870e38,0x7fc07000,0x70387ff0,0x7ff070fc,0x7ff801c0, + 0x707f80,0x7000739c,0x7338701c,0x7ff0701c,0x7fe00ff0,0x1c07038,0xe7073ce,0x1c003e0,0x3800380,0x38001c0,0x0,0x1c,0x381c3800, + 0x381c380e,0x380381c,0x383801c0,0xe01de0,0x380739c,0x3838381c,0x381c381c,0x7001e00,0x7003838,0x1c70718e,0x7e01c70,0xf00380, + 0x18001e0,0x1e000000,0x1c001bb0,0xff8,0x0,0x1000100,0xe0,0xff00300,0x707e1bb0,0x3801bb0,0x1bb00010,0x8000308,0x30c00000,0x0, + 0x0,0x1e0000c0,0xe1ce1bb0,0xf003e0,0x1c0,0x1c203ff8,0x63003e0,0x180181c,0x9801,0xfb00e38,0x7ffc0000,0x8fc10000,0x7ffe,0xe000860, + 0x3838,0x1f980380,0x180,0x7c01c70,0x1f001f0,0x1f003c0,0xe380e38,0xe380e38,0xe380e38,0x1cfc7000,0x7ff07ff0,0x7ff07ff0,0x1c001c0, + 0x1c001c0,0xfe387338,0x701c701c,0x701c701c,0x701c07e0,0x731c7038,0x70387038,0x703803e0,0x70383980,0x1c001c,0x1c001c,0x1c001c, + 0xe73800,0x380e380e,0x380e380e,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x387c3838,0x38383838,0x38381c70, + 0x381c1c70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xc30,0x7f00e00,0x33c30000,0x70000e0,0x1007ffe, + 0x0,0x380,0x3b9c01c0,0xf00078,0x30e0001c,0x3c1c01c0,0x1c381fdc,0x0,0x70000000,0x1c0380,0x63030e38,0x70707000,0x70387000,0x700070fc, + 0x703801c0,0x707b80,0x7000739c,0x7338701c,0x7fc0701c,0x7fc001f0,0x1c07038,0xe703e5c,0x3e001c0,0x7800380,0x38001c0,0x0,0x7fc, + 0x381c3800,0x381c380e,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x7001fc0,0x7003838,0x1c70718e,0x7c01c70, + 0xe01f00,0x180007c,0x7f8c0000,0x7fc03fb8,0x1c0,0x0,0x1000100,0x700,0x1f00600,0x70703fb8,0x7803fb8,0x3fb80000,0x8000000,0x180, + 0x0,0x0,0x1fc00060,0xe1ce3fb8,0xe001c0,0x1c0,0x1c203ff8,0xc1801c0,0x180c,0x9801,0x1c70,0xc0000,0x8cc10000,0x180,0xfe007c0, + 0x3838,0x7980380,0xff0,0xe38,0x3e003e00,0x3e000380,0xe380e38,0xe380e38,0xe380e38,0x38e07000,0x70007000,0x70007000,0x1c001c0, + 0x1c001c0,0x70387338,0x701c701c,0x701c701c,0x701c03c0,0x731c7038,0x70387038,0x703801c0,0x703838e0,0x7fc07fc,0x7fc07fc,0x7fc07fc, + 0xe73800,0x380e380e,0x380e380e,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c7ffc,0x38dc3838,0x38383838,0x38381c70, + 0x381c1c70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xc60,0xf83878,0x71e30000,0x70000e0,0x1007ffe, + 0x7f0,0x380,0x381c01c0,0x1e0003c,0x60e0001c,0x381c01c0,0x381c079c,0x0,0x7c000000,0x7c0380,0x63031c1c,0x70307000,0x70387000, + 0x7000701c,0x703801c0,0x7071c0,0x7000739c,0x71b8701c,0x7000701c,0x71e00078,0x1c07038,0xe703e7c,0x7e001c0,0xf000380,0x38001c0, + 0x0,0x1ffc,0x381c3800,0x381c3ffe,0x380381c,0x383801c0,0xe01fc0,0x380739c,0x3838381c,0x381c381c,0x7000ff0,0x7003838,0x1ef03bdc, + 0x3800ee0,0x1e01f00,0x180007c,0x61fc0000,0x7fc07f3c,0x1c0,0x0,0x1000100,0x1800,0x780c00,0x70707f3c,0xf007f3c,0x7f3c0000,0x0, + 0x3c0,0x3ffcffff,0x0,0xff00030,0xe1fe7f3c,0x1e001c0,0x1c0,0x1c200700,0xc183ffe,0xe0c,0x9801,0x1ff038e0,0xc07f0,0x8c610000, + 0x180,0x0,0x3838,0x1980380,0x0,0x1ff0071c,0xe000e000,0xe0000f80,0x1c1c1c1c,0x1c1c1c1c,0x1c1c1e38,0x38e07000,0x70007000,0x70007000, + 0x1c001c0,0x1c001c0,0x703871b8,0x701c701c,0x701c701c,0x701c03c0,0x761c7038,0x70387038,0x703801c0,0x70703870,0x1ffc1ffc,0x1ffc1ffc, + 0x1ffc1ffc,0xfff3800,0x3ffe3ffe,0x3ffe3ffe,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c7ffc,0x389c3838,0x38383838, + 0x38380ee0,0x381c0ee0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xfffc,0xbc60fc,0x70e30000,0x70000e0, + 0x180,0x7f0,0x700,0x381c01c0,0x3e0001c,0x7ffc001c,0x381c03c0,0x381c001c,0x0,0x1f807ffc,0x3f00380,0x63031ffc,0x70387000,0x70387000, + 0x7000701c,0x703801c0,0x7071e0,0x7000701c,0x71b8701c,0x7000701c,0x70f00038,0x1c07038,0x7e03e7c,0x77001c0,0xe000380,0x1c001c0, + 0x0,0x3c1c,0x381c3800,0x381c3ffe,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x70003f8,0x7003838,0xee03bdc, + 0x3c00ee0,0x3c00380,0x18000e0,0xf00000,0x1c007e7c,0x3c0,0x0,0x1000100,0x0,0x381800,0x70707e7c,0xe007e7c,0x7e7c0000,0x0,0x7c0, + 0x0,0x0,0x3f80018,0xe1fe7e7c,0x3c001c0,0x1c0,0x1c200700,0xc183ffe,0xf0c,0x8c01,0x38e0,0xc07f0,0x8c710000,0x180,0x0,0x3838, + 0x1980000,0x0,0x71c,0x7000f0,0x700f00,0x1ffc1ffc,0x1ffc1ffc,0x1ffc1ffc,0x3fe07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0, + 0x703871b8,0x701c701c,0x701c701c,0x701c07e0,0x7c1c7038,0x70387038,0x703801c0,0x7ff03838,0x3c1c3c1c,0x3c1c3c1c,0x3c1c3c1c, + 0x3fff3800,0x3ffe3ffe,0x3ffe3ffe,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x391c3838,0x38383838,0x38380ee0, + 0x381c0ee0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffc,0x9c01ce,0x70f60000,0x70000e0,0x180, + 0x0,0x700,0x381c01c0,0x780001c,0x7ffc001c,0x381c0380,0x381c003c,0x0,0x3e07ffc,0xf800380,0x63031ffc,0x70387000,0x70387000, + 0x7000701c,0x703801c0,0x7070f0,0x7000701c,0x71b8701c,0x7000701c,0x70700038,0x1c07038,0x7e03e7c,0xf7801c0,0x1e000380,0x1c001c0, + 0x0,0x381c,0x381c3800,0x381c3800,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x7000078,0x7003838,0xee03a5c, + 0x7c00fe0,0x78001c0,0x18001c0,0x0,0x1c003ef8,0x380,0x0,0x1000100,0x810,0x383000,0x70703ef8,0x1e003ef8,0x3ef80000,0x0,0x7c0, + 0x0,0x0,0x78000c,0xe1c03ef8,0x78001c0,0x1c0,0x1c200700,0x63001c0,0x18003f8,0x4e12,0x1c70,0xc0000,0x4c320000,0x180,0x0,0x3838, + 0x1980000,0x0,0xe38,0x700118,0x701e00,0x1ffc1ffc,0x1ffc1ffc,0x1ffc1ffc,0x7fe07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0, + 0x703871b8,0x701c701c,0x701c701c,0x701c0e70,0x7c1c7038,0x70387038,0x703801c0,0x7fc0381c,0x381c381c,0x381c381c,0x381c381c, + 0x78e03800,0x38003800,0x38003800,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x3b1c3838,0x38383838,0x38380fe0, + 0x381c0fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1860,0x9c0186,0x707e0000,0x30000c0,0x180, + 0x0,0xe00,0x183801c0,0xf00001c,0xe0001c,0x181c0380,0x381c0038,0x0,0xfc0000,0x7e000000,0x61873c1e,0x70383800,0x70707000,0x7000381c, + 0x703801c0,0x707070,0x7000701c,0x70f83838,0x70003838,0x70780038,0x1c07038,0x7e03c3c,0xe3801c0,0x1c000380,0xe001c0,0x0,0x381c, + 0x381c3800,0x381c3800,0x380381c,0x383801c0,0xe01ef0,0x380739c,0x3838381c,0x381c381c,0x7000038,0x7003838,0xfe03e7c,0xfe007c0, + 0x70001c0,0x18001c0,0x0,0xe001ff0,0x380,0x0,0x1000100,0x162c,0x381800,0x30701ff0,0x1c001ff0,0x1ff00000,0x0,0x3c0,0x0,0x0, + 0x380018,0xe1c01ff0,0x70001c0,0x1c0,0x1c200700,0xff801c0,0x18000f0,0x63e6,0xe38,0x0,0x6c3e0000,0x0,0x0,0x3838,0x1980000,0x0, + 0x1c70,0xf0000c,0xf01c00,0x3c1e3c1e,0x3c1e3c1e,0x3c1e3c1c,0x70e03800,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x707070f8, + 0x38383838,0x38383838,0x38381c38,0x38387038,0x70387038,0x703801c0,0x7000381c,0x381c381c,0x381c381c,0x381c381c,0x70e03800, + 0x38003800,0x38003800,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0380,0x3e1c3838,0x38383838,0x383807c0,0x381c07c0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18c0,0x9c0186,0x783c0000,0x38001c0,0x180,0x3800000, + 0x3800e00,0x1c3801c0,0x1e00003c,0xe00038,0x1c1c0780,0x381c0038,0x3800380,0x3c0000,0x78000000,0x61ff380e,0x70383808,0x70707000, + 0x7000381c,0x703801c0,0x40707078,0x7000701c,0x70f83838,0x70003838,0x70384038,0x1c07038,0x7e03c3c,0x1e3c01c0,0x3c000380,0xe001c0, + 0x0,0x383c,0x3c381c00,0x1c3c1c00,0x3801c3c,0x383801c0,0xe01c78,0x380739c,0x38381c38,0x3c381c3c,0x7000038,0x7003878,0x7c01e78, + 0x1ef007c0,0xf0001c0,0x18001c0,0x0,0xe000ee0,0x7800380,0xe380000,0x1001ff0,0x2242,0x40380c00,0x38700ee0,0x3c000ee0,0xee00000, + 0x0,0x0,0x0,0x0,0x380030,0xe1c00ee0,0xf0001c0,0x1c0,0xe200700,0xdd801c0,0x1800038,0x300c,0x71c,0x0,0x300c0000,0x0,0x0,0x3838, + 0x1980000,0x0,0x38e0,0xb0000c,0xb01c08,0x380e380e,0x380e380e,0x380e380e,0x70e03808,0x70007000,0x70007000,0x1c001c0,0x1c001c0, + 0x707070f8,0x38383838,0x38383838,0x3838381c,0x38387038,0x70387038,0x703801c0,0x7000381c,0x383c383c,0x383c383c,0x383c383c, + 0x70e01c00,0x1c001c00,0x1c001c00,0x1c001c0,0x1c001c0,0x1c383838,0x1c381c38,0x1c381c38,0x1c380380,0x1c383878,0x38783878,0x387807c0, + 0x3c3807c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x18c0,0x10b801ce,0x3c3e0000,0x38001c0,0x180, + 0x3800000,0x3801c00,0x1e7801c0,0x3c002078,0xe02078,0x1c380700,0x1c3810f0,0x3800380,0x40000,0x40000380,0x307b380e,0x70701e18, + 0x70e07000,0x70001c1c,0x703801c0,0x60e0703c,0x7000701c,0x70f83c78,0x70003c70,0x703c70f0,0x1c03870,0x3c01c3c,0x3c1c01c0,0x78000380, + 0x7001c0,0x0,0x3c7c,0x3c381e18,0x1c7c1e0c,0x3801c3c,0x383801c0,0xe01c38,0x3c0739c,0x38381c38,0x3c381c3c,0x7001078,0x7803c78, + 0x7c01c38,0x1c780380,0x1e0001c0,0x18001c0,0x0,0x70c06c0,0x7000380,0xe300000,0x1000100,0x2142,0x70f00600,0x3c7006c0,0x780006c0, + 0x6c00000,0x0,0x0,0x0,0x0,0x10780060,0x73e206c0,0x1e0001c0,0x1c0,0x7240700,0x180c01c0,0x1800018,0x1818,0x30c,0x0,0x18180000, + 0x0,0x0,0x3c78,0x1980000,0x0,0x30c0,0x130000c,0x1301c18,0x380e380e,0x380e380e,0x380e380e,0x70e01e18,0x70007000,0x70007000, + 0x1c001c0,0x1c001c0,0x70e070f8,0x3c783c78,0x3c783c78,0x3c781008,0x7c783870,0x38703870,0x387001c0,0x70003a3c,0x3c7c3c7c,0x3c7c3c7c, + 0x3c7c3c7c,0x79f11e18,0x1e0c1e0c,0x1e0c1e0c,0x1c001c0,0x1c001c0,0x1c783838,0x1c381c38,0x1c381c38,0x1c380380,0x1c383c78,0x3c783c78, + 0x3c780380,0x3c380380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x38c0,0x1ff800fc,0x1fee0000, + 0x1800180,0x180,0x3800000,0x3801c00,0xff01ffc,0x3ffc3ff0,0xe03ff0,0xff00700,0x1ff81fe0,0x3800380,0x0,0x380,0x3000780f,0x7ff00ff8, + 0x7fc07ff8,0x70000ffc,0x70381ffc,0x7fe0701c,0x7ff8701c,0x70781ff0,0x70001ff0,0x701c7ff0,0x1c01fe0,0x3c01c38,0x380e01c0,0x7ffc0380, + 0x7001c0,0x0,0x1fdc,0x3ff00ff0,0xffc0ffc,0x3800fdc,0x38383ffe,0xe01c3c,0x1fc739c,0x38380ff0,0x3ff00ffc,0x7001ff0,0x3f81fb8, + 0x7c01c38,0x3c3c0380,0x1ffc01c0,0x18001c0,0x0,0x3fc0380,0x7000380,0xc70718c,0x1000100,0x2244,0x7ff00200,0x1fff0380,0x7ffc0380, + 0x3800000,0x0,0x0,0x0,0x0,0x1ff000c0,0x7f7e0380,0x1ffc01c0,0x1c0,0x3fc3ffe,0x1c0,0x1800018,0x7e0,0x104,0x0,0x7e00000,0x7ffe, + 0x0,0x3fde,0x1980000,0x0,0x2080,0x3300018,0x3300ff0,0x780f780f,0x780f780f,0x780f780e,0xf0fe0ff8,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc, + 0x1ffc1ffc,0x7fc07078,0x1ff01ff0,0x1ff01ff0,0x1ff00000,0x7ff01fe0,0x1fe01fe0,0x1fe001c0,0x70003bf8,0x1fdc1fdc,0x1fdc1fdc, + 0x1fdc1fdc,0x3fbf0ff0,0xffc0ffc,0xffc0ffc,0x3ffe3ffe,0x3ffe3ffe,0xff03838,0xff00ff0,0xff00ff0,0xff00000,0x3ff01fb8,0x1fb81fb8, + 0x1fb80380,0x3ff00380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x31c0,0x7e00078,0x7cf0000,0x1800180, + 0x0,0x3800000,0x3803800,0x3c01ffc,0x3ffc0fe0,0xe01fc0,0x3e00e00,0x7e00f80,0x3800380,0x0,0x380,0x18007007,0x7fc003f0,0x7f007ff8, + 0x700003f0,0x70381ffc,0x3f80701e,0x7ff8701c,0x707807c0,0x700007c0,0x701e1fc0,0x1c00fc0,0x3c01818,0x780f01c0,0x7ffc0380,0x3801c0, + 0x0,0xf9c,0x39e003e0,0x79c03f0,0x380079c,0x38383ffe,0xe01c1e,0x7c739c,0x383807e0,0x39e0079c,0x7000fc0,0x1f80f38,0x3801c38, + 0x781e0380,0x1ffc01c0,0x18001c0,0x0,0x1f80100,0xe000700,0x1c60718c,0x1000100,0x1e3c,0x1fc00100,0x7ff0100,0x7ffc0100,0x1000000, + 0x0,0x0,0x0,0x0,0xfc00080,0x3e3c0100,0x1ffc01c0,0x1c0,0xf83ffe,0x1c0,0x1800838,0x0,0x0,0x0,0x0,0x7ffe,0x0,0x3b9e,0x1980000, + 0x0,0x0,0x2300038,0x23003e0,0x70077007,0x70077007,0x70077007,0xe0fe03f0,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,0x7f007078, + 0x7c007c0,0x7c007c0,0x7c00000,0xc7c00fc0,0xfc00fc0,0xfc001c0,0x700039f0,0xf9c0f9c,0xf9c0f9c,0xf9c0f9c,0x1f1e03e0,0x3f003f0, + 0x3f003f0,0x3ffe3ffe,0x3ffe3ffe,0x7e03838,0x7e007e0,0x7e007e0,0x7e00000,0x63e00f38,0xf380f38,0xf380380,0x39e00380,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0xc00300,0x0,0x3000000,0x3800,0x0,0x0,0x0,0x0, + 0x0,0x300,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe0,0x0,0x0,0x0,0x0,0x380,0x3801c0,0x0,0x0,0x0,0x0,0x1c,0x0,0xe00000, + 0x0,0x0,0x3800001c,0x0,0x0,0x0,0x700,0x1c0,0x18001c0,0x0,0x0,0xe000700,0x18600000,0x1000100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800ff0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0x1800000,0x0,0x6300070,0x6300000,0x0, + 0x0,0x0,0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000000, + 0x0,0x700,0x38000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0xc00300,0x0,0x7000000, + 0x7000,0x0,0x0,0x0,0x0,0x0,0x700,0x0,0x0,0xf040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78,0x0,0x0,0x0,0x0,0x3f0,0x1c0fc0,0x0,0x0, + 0x0,0x0,0x1c,0x0,0xe00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0x700,0x1e0,0x18003c0,0x0,0x0,0xc000700,0x18c00000,0x1000000,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x18007e0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0xc00000, + 0x0,0x7f800e0,0x7f80000,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x700,0x38000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000, + 0x0,0x600600,0x0,0x6000000,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x7fc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0, + 0x3f0,0xfc0,0x0,0x0,0x0,0x0,0x838,0x0,0x1e00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0xf00,0xfc,0x1801f80,0x0,0x0,0x8008e00,0x30c00000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0xc00000, + 0x0,0x3001c0,0x300000,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0xf00,0x38000f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0xff0,0x0,0x1fc00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0x3e00,0x7c,0x1801f00,0x0,0x0,0x800fe00,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x7c00000,0x0,0x3001fc,0x300000, + 0x0,0x0,0x0,0x3e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x3e00,0x38003e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfff8,0x0,0x0,0x0,0x7e0,0x0,0x1f000000, + 0x0,0x0,0x3800001c,0x0,0x0,0x0,0x3c00,0x0,0x1800000,0x0,0x0,0x7800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x7800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00,0x38003c00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0 }; + + // Definition of a 19x38 font. + const unsigned int font19x38[19*38*256/32] = { + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c380000,0x0,0x1c380,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800007,0x3c003,0x86000000, + 0x1e00000,0x3,0x80000700,0x3c00000,0x380000,0x70003c00,0x0,0xe1800e,0x1c00,0xf000e18,0x0,0x0,0x700000e0,0x780000,0x7000,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe700000,0x0,0xe700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0000e,0x7e003,0xe60071c0,0x7f80000,0x1,0xc0000e00,0x7e0038e,0x1c0000, + 0xe0007e00,0x38e00000,0xf98007,0x3800,0x1f800f98,0x1c70000,0x0,0x380001c0,0xfc0071,0xc000e000,0x0,0x0,0x0,0x0,0x3e00000,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x7e00000,0x0,0x7e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe0001c,0xe7006,0x7c0071c0,0xe180000,0x0,0xe0001c00,0xe70038e,0xe0001,0xc000e700,0x38e00000, + 0x19f0003,0x80007000,0x39c019f0,0x1c70000,0x0,0x1c000380,0x1ce0071,0xc001c000,0x0,0x0,0x0,0x0,0x7f00000,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000, + 0x0,0x3c00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x700038,0x1c3806,0x3c0071c0,0xc0c0000,0x0,0x70003800,0x1c38038e,0x70003,0x8001c380,0x38e00000,0x18f0001,0xc000e000, + 0x70e018f0,0x1c70000,0x0,0xe000700,0x3870071,0xc0038000,0x0,0x0,0x0,0x0,0xe380000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60000000,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c38,0x0,0x1,0xc3800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00000,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0xc0c0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe000003,0x80018000,0x0,0xc180000, + 0xe,0x380,0x1800000,0xe00000,0x38001800,0x0,0x38,0xe00,0x6000000,0x0,0x1,0xc0000070,0x300000,0x3800,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7000000,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78c00,0xc30, + 0x0,0x0,0xc3000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800000,0x0,0x0,0x0,0xe0,0x1c000f,0xc0000000,0x0,0x0, + 0x0,0xc0c0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7000007,0x3c003,0xc6000000,0xc180000,0x7,0x700, + 0x3c00000,0x700000,0x70003c00,0x0,0xf1801c,0x1c00,0xf000f18,0x0,0x0,0xe00000e0,0x780000,0x7000,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x1c007000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe0000,0xfe000,0x0,0x3800000,0x700000,0x38, + 0x7,0xe000001c,0x1c00,0x1c00700,0x7fc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf800e,0x3e0000,0x0,0x0,0x0,0x1e00000,0x0,0x1, + 0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7cc00,0x660,0x0,0x0,0x66000000,0x0,0x0,0x0,0x0,0x7,0x1c000000,0x0,0x0,0x0,0x3fe00000, + 0x0,0x0,0x7000000,0x0,0x0,0x0,0x3e0,0x7c001f,0xe0000000,0x0,0x0,0x0,0xe1c0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x1f80,0x380000e,0x7e007,0xe60071c0,0xc180000,0x3,0x80000e00,0x7e0038e,0x380000,0xe0007e00,0x38e00f00,0x1f9800e, + 0x3800,0x1f801f98,0x1c70000,0x0,0x700001c0,0xfc0071,0xc000e007,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0x61c00600,0x1e00007e,0x70000,0x18003000,0x1800000,0x0,0x0,0x1c01f0,0x7e003f,0xc003f800, + 0x1e03ffc,0x7f01ff,0xfc03f000,0x7e000000,0x0,0x0,0xfc0,0x1e,0x7fe000,0x7e03fe00,0x3fff07ff,0xe007e038,0x383ffe0,0xff81c01, + 0xe1c000f8,0xf8f00e0,0xfc01ffc,0x3f00ff,0xc000fe07,0xfffc7007,0x1c007700,0x73c01ef,0x78ffff,0xfe0380,0xfe000,0x38000000,0x1800000, + 0x700000,0x38,0x1f,0xe000001c,0x1c00,0x1c00700,0x7fc0000,0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x3f800e,0x3f8000,0x0,0xfc0000, + 0x0,0x7f00000,0x0,0x1,0x98000000,0x7f00000,0x3ffe00,0xffff0,0x0,0x0,0x0,0x0,0x0,0xcf81f,0xee3807e0,0x0,0x0,0x7e03c01e,0x1c, + 0x0,0x1f800000,0xf0078038,0xfc007,0x1c000000,0xfe00000,0x0,0x0,0x3fe000f0,0xf,0xc001f800,0x6000000,0xffc000,0x0,0x1c0007e0, + 0x360,0x6c0010,0x70000700,0xf0001e,0x3c000,0x78000f00,0x7f800ff,0xf007e01f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83fc0, + 0x7807007,0xe000fc00,0x1f8003f0,0x7e0000,0x1f867,0x70e00e,0x1c01c380,0x38f00787,0x3fe0,0x180000c,0x66006,0x7c0071c0,0xe380000, + 0x1,0x80000c00,0x660038e,0x180000,0xc0006600,0x38e0078e,0x19f0006,0x3000,0x198019f0,0x1c70000,0x0,0x30000180,0xcc0071,0xc000c007, + 0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0x61800600,0x7f8001ff,0x70000, + 0x38003800,0x1800000,0x0,0x0,0x3807fc,0x1fe00ff,0xf00ffe00,0x3e03ffc,0xff81ff,0xfc07fc01,0xff800000,0x0,0x0,0x3fe0,0xfe001e, + 0x7ff801,0xff83ff80,0x3fff07ff,0xe01ff838,0x383ffe0,0xff81c03,0xc1c000f8,0xf8f80e0,0x3ff01fff,0xffc0ff,0xf003ff87,0xfffc7007, + 0x1e00f700,0x71c03c7,0x70ffff,0xfe01c0,0xfe000,0x7c000000,0xc00000,0x700000,0x38,0x3f,0xe000001c,0x1c00,0x1c00700,0x7fc0000, + 0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x3f800e,0x3f8000,0x0,0x3fe0000,0x0,0xff00000,0x0,0x3,0xc000000,0x1ffc0000,0xfffe00, + 0xffff0,0x0,0x0,0x0,0x0,0x0,0xc781f,0xee3803c0,0x0,0x0,0x3c01c01c,0x1c,0xc000,0x7fc00000,0x70070038,0x3fe007,0x1c000000,0x1ff80000, + 0x0,0x0,0x3fe003fc,0x1f,0xe003fc00,0xc000000,0x3ffc000,0x0,0x7c000ff0,0x60,0xc0000,0x30000700,0xf0001e,0x3c000,0x78000f00, + 0x3f000ff,0xf01ff81f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ff8,0x7c0701f,0xf803ff00,0x7fe00ffc,0x1ff8000,0x7fe67, + 0x70e00e,0x1c01c380,0x38700707,0x7ff0,0xc00018,0xc3006,0x3c0071c0,0x7f00000,0x0,0xc0001800,0xc30038e,0xc0001,0x8000c300,0x38e003fc, + 0x18f0003,0x6000,0x30c018f0,0x1c70000,0x0,0x18000300,0x1860071,0xc0018007,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0xe1801fc0,0x618001ff,0x70000,0x30001800,0x21840000,0x0,0x0,0x380ffe,0x1fe00ff, + 0xfc0fff00,0x3e03ffc,0x1ff81ff,0xfc0ffe03,0xffc00000,0x0,0x0,0x7ff0,0x3ff803f,0x7ffc03,0xffc3ffc0,0x3fff07ff,0xe03ffc38,0x383ffe0, + 0xff81c07,0x81c000f8,0xf8f80e0,0x7ff81fff,0x81ffe0ff,0xf80fff87,0xfffc7007,0xe00e700,0x70e0387,0x80f0ffff,0xe001c0,0xe000, + 0xfe000000,0xe00000,0x700000,0x38,0x3c,0x1c,0x1c00,0x1c00700,0x1c0000,0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x78000e,0x3c000, + 0x0,0x7ff0000,0x0,0xf100000,0x0,0x7,0xe000000,0x7ffc0000,0x1fffe00,0xffff0,0x0,0x0,0x0,0x0,0x0,0x3,0xf780180,0x0,0x0,0x1801e03c, + 0x1c,0xc000,0xffc00000,0x780f0038,0x786000,0x7f00,0x18380000,0x0,0xfe00,0x30c,0x10,0x70020e00,0x1c000000,0x7f8c000,0x0,0x6c001c38, + 0x60,0xc0000,0x70000700,0x1f8003f,0x7e000,0xfc001f80,0x3f000ff,0xf03ffc1f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ffc, + 0x7c0703f,0xfc07ff80,0xfff01ffe,0x3ffc000,0xffec7,0x70e00e,0x1c01c380,0x38780f07,0xf070,0xe00038,0x1c3800,0x0,0x3e00000,0x0, + 0xe0003800,0x1c380000,0xe0003,0x8001c380,0x3e0,0x3,0x8000e000,0x70e00000,0x0,0x0,0x1c000700,0x3870000,0x38007,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0xe3807ff0,0xc0c003c1,0x70000,0x70001c00, + 0x718e0000,0x0,0x0,0x700f1e,0x1ce00c0,0x3c0c0f80,0x7e03800,0x3e08000,0x381e0f03,0xc1e00000,0x0,0x0,0x7078,0x783c03f,0x701e07, + 0xc1c383e0,0x38000700,0x7c1c38,0x3801c00,0x381c0f,0x1c000fc,0x1f8f80e0,0x78781c07,0x81e1e0e0,0x780f0180,0xe007007,0xe00e380, + 0xe0f0783,0x80e0000e,0xe000e0,0xe001,0xef000000,0x0,0x700000,0x38,0x38,0x1c,0x0,0x700,0x1c0000,0x0,0x0,0x0,0x0,0x1c000000, + 0x0,0x0,0x0,0x70000e,0x1c000,0x0,0xf830000,0x0,0x1e000000,0x0,0x0,0x10000,0x780c0000,0x3e38000,0xe0,0x0,0x0,0x0,0x0,0x0,0x3, + 0xd580000,0x0,0x0,0xe038,0x1c,0xc000,0xf0400000,0x380e0038,0x702000,0x1ffc0,0xc0000,0x0,0x3ff80,0x606,0x0,0x30000600,0x0, + 0x7f8c000,0x0,0xc001818,0x60,0xc0003,0xe0000700,0x1f8003f,0x7e000,0xfc001f80,0x73801ee,0x7c1c1c,0x38000,0x70000e00,0xe0001, + 0xc0003800,0x700383e,0x7c0703c,0x3c078780,0xf0f01e1e,0x3c3c000,0xf0f87,0x70e00e,0x1c01c380,0x38380e07,0xe038,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0xff0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0xc380fff0,0xc0c00380,0x70000,0x70001c00,0x3dbc0070,0x0,0x0,0x701e0f,0xe0000,0x1e000380, + 0x6e03800,0x7800000,0x781c0707,0x80e00000,0x0,0x0,0x4038,0xe00c03f,0x700e07,0x4380f0,0x38000700,0x700438,0x3801c00,0x381c0e, + 0x1c000ec,0x1b8fc0e0,0xf03c1c03,0xc3c0f0e0,0x3c1e0000,0xe007007,0xe00e380,0xe070703,0xc1e0001e,0xe000e0,0xe001,0xc7000000, + 0x0,0x700000,0x38,0x38,0x1c,0x0,0x700,0x1c0000,0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x70000e,0x1c000,0x0,0xe010000,0x0, + 0x1c000000,0x10,0x20000,0x6c000,0xf0000000,0x3838000,0x1e0,0x0,0xf000f,0xf1e00,0x78f00000,0x0,0x3,0xdd80000,0x0,0x0,0xf078, + 0x0,0xc001,0xe0000000,0x1c1c0038,0x700000,0x3c1e0,0xc0000,0x0,0x783c0,0x606,0x0,0x30000e00,0x0,0xff8c000,0x0,0xc00300c,0x60, + 0xc0003,0xe0000000,0x1f8003f,0x7e000,0xfc001f80,0x73801ce,0x70041c,0x38000,0x70000e00,0xe0001,0xc0003800,0x700380f,0x7e07078, + 0x1e0f03c1,0xe0783c0f,0x781e000,0x1c0787,0x70e00e,0x1c01c380,0x383c1e07,0xff00e038,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x878, + 0x0,0x0,0x0,0x7,0x80000080,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c, + 0x1c7000,0xc301e630,0xc0c00380,0x70000,0xe0000e00,0xff00070,0x0,0x0,0xe01c07,0xe0000,0xe000380,0xce03800,0x7000000,0x701c0707, + 0x600000,0x0,0x4000010,0x38,0x1c00e07f,0x80700e0e,0x38070,0x38000700,0xe00038,0x3801c00,0x381c1c,0x1c000ec,0x1b8ec0e0,0xe01c1c01, + 0xc38070e0,0x1c1c0000,0xe007007,0x701c380,0xe078e01,0xc1c0003c,0xe00070,0xe003,0x83800000,0x7f,0x71f000,0x3e003e38,0x3f007ff, + 0xe01f1c1c,0x7801fc00,0x3fc00701,0xe01c0077,0x8f071e00,0xf801c7c,0x7c700e,0x3e01fc03,0xfff8380e,0xe007700,0x73c0787,0x387ffc, + 0x70000e,0x1c000,0x0,0xe000000,0x0,0x1c000000,0x10,0x20000,0xc2000,0xe0000000,0x3838000,0x3c0,0x0,0xf000f,0x78e00,0x70e00000, + 0x0,0x3,0xc980fe0,0x1f0,0xf8000007,0xffc07070,0x0,0x3f801,0xc0000000,0x1e3c0038,0x700000,0x70070,0x7fc0000,0x0,0xe00e0,0x606, + 0x1c0000,0x70007c00,0x380e,0xff8c000,0x0,0xc00300c,0x60,0xc0000,0x70000000,0x3fc007f,0x800ff001,0xfe003fc0,0x73801ce,0xe0001c, + 0x38000,0x70000e00,0xe0001,0xc0003800,0x7003807,0x7607070,0xe0e01c1,0xc0383807,0x700e000,0x1c0387,0x70e00e,0x1c01c380,0x381c1c07, + 0xffc0e0f8,0x3f8007f,0xfe001,0xfc003f80,0x7f007e3,0xe003e001,0xf8003f00,0x7e000fc,0xfe001f,0xc003f800,0x7f00003c,0x38f0007, + 0xc000f800,0x1f0003e0,0x7c0007,0x8003f0c3,0x80e0701c,0xe0381c0,0x70700387,0x1f01c00e,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c701f,0xfff1c600,0xc0c00380,0x70000,0xe0000e00,0x3c00070,0x0,0x0,0xe03c07, + 0x800e0000,0xe000380,0x1ce03800,0x7000000,0x701c0707,0x7003c0,0x780000,0x3c00001e,0x38,0x18006073,0x80700e0e,0x38070,0x38000700, + 0xe00038,0x3801c00,0x381c38,0x1c000ee,0x3b8ee0e1,0xe01e1c01,0xc78078e0,0x1c1c0000,0xe007007,0x701c387,0xe03de00,0xe3800038, + 0xe00070,0xe007,0x1c00000,0x1ff,0xc077f801,0xff807fb8,0xff807ff,0xe03fdc1d,0xfc01fc00,0x3fc00703,0xc01c007f,0xdf877f00,0x3fe01dfe, + 0xff700e,0xff07ff03,0xfff8380e,0x700f700,0x71e0f03,0x80707ffc,0x70000e,0x1c000,0x0,0x1c000008,0x0,0x1c000000,0x10,0x20000, + 0x82000,0xe0000000,0x7038000,0x80000380,0x2000040,0x7000e,0x38700,0xf1e00000,0x0,0x3,0xc183ff8,0x3fd,0xfc008007,0xffc038e0, + 0x0,0xffc01,0xc0008008,0xe380038,0x380000,0xe3e38,0x1ffc0040,0x80000000,0x1cfc70,0x606,0x1c0000,0xe0007c00,0x380e,0xff8c000, + 0x0,0xc00300c,0x8100060,0xc0000,0x30000700,0x39c0073,0x800e7001,0xce0039c0,0x73801ce,0xe0001c,0x38000,0x70000e00,0xe0001, + 0xc0003800,0x7003807,0x77070f0,0xf1e01e3,0xc03c7807,0x8f00f080,0x83c0787,0x70e00e,0x1c01c380,0x380e3807,0xffe0e1c0,0xffe01ff, + 0xc03ff807,0xff00ffe0,0x1ffc0ff7,0xf01ff807,0xfc00ff80,0x1ff003fe,0xfe001f,0xc003f800,0x7f0003fc,0x3bf801f,0xf003fe00,0x7fc00ff8, + 0x1ff0007,0x8007fd83,0x80e0701c,0xe0381c0,0x70380707,0x7f80e01c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x1c,0x1c701f,0xfff1c600,0x618081c0,0x70000,0xe0000e00,0x3c00070,0x0,0x0,0xe03803,0x800e0000,0xe000380,0x18e03800, + 0xf000000,0xf01c0707,0x7003c0,0x780000,0xfc00001f,0x80000078,0x301e6073,0x80700e1c,0x38038,0x38000700,0x1c00038,0x3801c00, + 0x381c70,0x1c000e6,0x338ee0e1,0xc00e1c01,0xc70038e0,0x1c1c0000,0xe007007,0x701c387,0xe01dc00,0xf7800078,0xe00070,0xe00e,0xe00000, + 0x3ff,0xe07ffc03,0xffc0fff8,0x1ffc07ff,0xe07ffc1d,0xfe01fc00,0x3fc00707,0x801c007f,0xdf877f80,0x7ff01fff,0x1fff00e,0xff07ff03, + 0xfff8380e,0x700e380,0xe0e0e03,0x80707ffc,0x70000e,0x1c000,0x0,0x7ffc001c,0x0,0x1c000000,0x10,0x20000,0x82000,0xe0000000, + 0x7038001,0xc0000780,0x70000e0,0x3800e,0x38700,0xe1c00000,0x0,0x3,0xc183ff8,0x7ff,0xfc01c007,0xffc03de0,0x0,0x1ffc01,0xc001c01c, + 0xf780038,0x3c0000,0xcff18,0x380c00c1,0x80000000,0x18fe30,0x30c,0x1c0001,0xc0000e00,0x380e,0xff8c000,0x0,0xc00300c,0xc180060, + 0xc0000,0x30000700,0x39c0073,0x800e7001,0xce0039c0,0xe1c038e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x877070e0, + 0x71c00e3,0x801c7003,0x8e0071c0,0x1c380fc7,0x70e00e,0x1c01c380,0x380f7807,0x1e0e380,0x1fff03ff,0xe07ffc0f,0xff81fff0,0x3ffe0fff, + 0xf03ffc0f,0xfe01ffc0,0x3ff807ff,0xfe001f,0xc003f800,0x7f0007fe,0x3bfc03f,0xf807ff00,0xffe01ffc,0x3ff8007,0x800fff83,0x80e0701c, + 0xe0381c0,0x70380707,0xffc0e01c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c701f, + 0xfff1c600,0x7f8381e0,0x70000,0xc0000600,0xff00070,0x0,0x0,0x1c03803,0x800e0000,0xe000f00,0x38e03fe0,0xe000000,0xe00e0e07, + 0x7003c0,0x780007,0xf0ffff87,0xf00000f0,0x307fe0f3,0xc0703c1c,0x38038,0x38000700,0x1c00038,0x3801c00,0x381ce0,0x1c000e6,0x338e70e1, + 0xc00e1c01,0xc70038e0,0x3c1e0000,0xe007007,0x783c38f,0x8e01fc00,0x770000f0,0xe00038,0xe01c,0x700000,0x381,0xe07c1e07,0xc0c1e0f8, + 0x3c1e0038,0xf07c1f,0xe001c00,0x1c0070f,0x1c0079,0xf3c7c380,0xf0781f07,0x83c1f00f,0xc10f0300,0x1c00380e,0x700e380,0xe0f1e03, + 0xc0f00078,0x70000e,0x1c000,0x0,0xfff8003e,0x0,0x3c000000,0x10,0x20000,0xc6000,0xf0000000,0x7038003,0xe0000f00,0xf8001f0, + 0x3801c,0x18300,0xe1800000,0x0,0x3,0xc187818,0x70f,0x9e03e000,0x7801dc0,0x1c,0x3cc401,0xc000efb8,0x7f7f0038,0x3f0000,0x1ce11c, + 0x300c01c3,0x80000000,0x38c638,0x3fc,0x1c0003,0x80000600,0x380e,0xff8c000,0x0,0xc00300c,0xe1c0060,0xc0010,0x70000700,0x79e00f3, + 0xc01e7803,0xcf0079e0,0xe1c038e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x873870e0,0x71c00e3,0x801c7003, + 0x8e0070e0,0x38381dc7,0x70e00e,0x1c01c380,0x38077007,0xf0e700,0x1c0f0381,0xe0703c0e,0x781c0f0,0x381e083e,0x787c0c1e,0xf03c1e0, + 0x783c0f07,0x800e0001,0xc0003800,0x7000fff,0x3e1c078,0x3c0f0781,0xe0f03c1e,0x783c000,0x1e0f03,0x80e0701c,0xe0381c0,0x70380f07, + 0xc1e0e03c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1,0x8701c600,0x1e0f01e0,0x1, + 0xc0000700,0x3dbc0070,0x0,0x0,0x1c03803,0x800e0000,0x1e01fe00,0x70e03ff8,0xe3e0001,0xe007fc07,0x80f003c0,0x78001f,0xc0ffff81, + 0xfc0001e0,0x30e1e0e1,0xc07ff81c,0x38038,0x3ffe07ff,0xc1c0003f,0xff801c00,0x381de0,0x1c000e7,0x738e70e1,0xc00e1c03,0xc70038e0, + 0x780f8000,0xe007007,0x383838d,0x8e00f800,0x7f0000e0,0xe00038,0xe000,0x0,0x200,0xf0780e07,0x8041c078,0x380e0038,0xe03c1e, + 0xf001c00,0x1c0071e,0x1c0070,0xe1c783c0,0xe0381e03,0x8380f00f,0xe0000,0x1c00380e,0x381c380,0xe07bc01,0xc0e00078,0x70000e, + 0x1c000,0x0,0x1c000061,0x0,0x38000000,0x10,0x20000,0x7c000,0x7c000000,0x703fc06,0x10000e00,0x18400308,0x1801c,0x1c381,0xc3800000, + 0x0,0x0,0x7000,0xe0f,0xe061000,0x7801fc0,0x1c,0x38c001,0xc0007ff0,0x7fff0038,0x77c000,0x19c00c,0x301c0387,0x0,0x30c618,0xf0, + 0x1c0007,0x600,0x380e,0x7f8c007,0x80000000,0xc001818,0x70e03fc,0x387f871f,0xe0e00700,0x70e00e1,0xc01c3803,0x870070e0,0xe1c038f, + 0xe1c0001f,0xff03ffe0,0x7ffc0fff,0x800e0001,0xc0003800,0x7003803,0x873870e0,0x71c00e3,0x801c7003,0x8e007070,0x703839c7,0x70e00e, + 0x1c01c380,0x3807f007,0x70e700,0x10078200,0xf0401e08,0x3c10078,0x200f001c,0x3878041c,0x70380e0,0x701c0e03,0x800e0001,0xc0003800, + 0x7001e0f,0x3c1e070,0x1c0e0381,0xc070380e,0x701c000,0x1c0f03,0x80e0701c,0xe0381c0,0x701c0e07,0x80e07038,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x3,0x8600e600,0x7803f0,0x1,0xc0000700,0x718e0070,0x0,0x0,0x38038c3, + 0x800e0000,0x3c01f800,0x60e03ffc,0xeff8001,0xc001f003,0xc1f003c0,0x7800fe,0xffff80,0x3f8003c0,0x60c0e0e1,0xc07fe01c,0x38038, + 0x3ffe07ff,0xc1c07e3f,0xff801c00,0x381fe0,0x1c000e3,0x638e30e1,0xc00e1c07,0x870038ff,0xf00ff800,0xe007007,0x38381cd,0x9c007000, + 0x3e0001e0,0xe0001c,0xe000,0x0,0x0,0x70780f0f,0x3c078,0x70070038,0x1e03c1c,0x7001c00,0x1c0073c,0x1c0070,0xe1c701c1,0xe03c1e03, + 0xc780f00f,0xe0000,0x1c00380e,0x381c387,0xe03f801,0xc0e000f0,0x70000e,0x1c007,0xe0100000,0x1c0000cd,0x80000003,0xffc00000, + 0x3ff,0x807ff000,0xe0,0x7fc00060,0x703fc0c,0xd8001e00,0x3360066c,0x1c018,0xc181,0x83000000,0x0,0x0,0x7000,0x300e07,0xe0cd800, + 0xf000f80,0x1c,0x78c00f,0xff0038e0,0x3e00038,0xe1e000,0x19800c,0x383c070e,0x7fffc00,0x30fc18,0x0,0xffff80e,0x20e00,0x380e, + 0x7f8c007,0x80000000,0xc001c38,0x38703ff,0xf87fff0f,0xcfe00f00,0x70e00e1,0xc01c3803,0x870070e0,0x1e1e078f,0xe1c0001f,0xff03ffe0, + 0x7ffc0fff,0x800e0001,0xc0003800,0x700ff83,0x871870e0,0x71c00e3,0x801c7003,0x8e007038,0xe03871c7,0x70e00e,0x1c01c380,0x3803e007, + 0x70e700,0x38000,0x70000e00,0x1c00038,0x7001c,0x38f00038,0x3870070,0xe00e1c01,0xc00e0001,0xc0003800,0x7001c07,0x380e0f0,0x1e1e03c3, + 0xc078780f,0xf01e000,0x3c0f03,0x80e0701c,0xe0381c0,0x701c0e07,0x80f07038,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x3,0x8600ff00,0x1e00778,0x38000001,0xc0000700,0x21843fff,0xe0000000,0x0,0x38039e3,0x800e0000, + 0x7c01fe00,0xe0e0203e,0xeffc001,0xc00ffe03,0xff700000,0x7f0,0x0,0x7f00380,0x618060e1,0xc07ffc1c,0x38038,0x3ffe07ff,0xc1c07e3f, + 0xff801c00,0x381ff0,0x1c000e3,0x638e38e1,0xc00e1fff,0x870038ff,0xc003fe00,0xe007007,0x38381cd,0x9c00f800,0x3e0003c0,0xe0001c, + 0xe000,0x0,0x0,0x7070070e,0x38038,0x70070038,0x1c01c1c,0x7001c00,0x1c00778,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0xfc000, + 0x1c00380e,0x381c3c7,0x1e01f001,0xe1e001e0,0xf0000e,0x1e01f,0xf8300000,0x1c00019c,0xc0000003,0xffc00000,0x10,0x20000,0x700, + 0x1ff000c0,0x703fc19,0xcc003c00,0x67300ce6,0xc038,0xc181,0x83000000,0x0,0x0,0x7e00,0x180e07,0xe19cc00,0x1e000f80,0x1c,0x70c00f, + 0xff007070,0x3e00038,0xe0f000,0x19800c,0x1fec0e1c,0x7fffc00,0x30f818,0x0,0xffff81f,0xf003fc00,0x380e,0x3f8c007,0x80000000, + 0x7f800ff0,0x1c3803f,0xe007fc00,0xff800e00,0x70e00e1,0xc01c3803,0x870070e0,0x1c0e070f,0xe1c0001f,0xff03ffe0,0x7ffc0fff,0x800e0001, + 0xc0003800,0x700ff83,0x871c70e0,0x71c00e3,0x801c7003,0x8e00701d,0xc038e1c7,0x70e00e,0x1c01c380,0x3803e007,0x70e3c0,0x38000, + 0x70000e00,0x1c00038,0x7001c,0x38e00038,0x3870070,0xe00e1c01,0xc00e0001,0xc0003800,0x7003c07,0x8380e0e0,0xe1c01c3,0x80387007, + 0xe00e1ff,0xfe381b83,0x80e0701c,0xe0381c0,0x701e1e07,0x707878,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x1c,0x3,0xe007fe0,0x7800e3c,0x38000001,0xc0000700,0x1803fff,0xe0000000,0x0,0x70039c3,0x800e0000,0xf8000f80, + 0xc0e0000e,0xf83c003,0xc01e0f01,0xff700000,0x7c0,0x0,0x1f00780,0x618061c0,0xe0701e1c,0x38038,0x38000700,0x1c07e38,0x3801c00, + 0x381e78,0x1c000e3,0xe38e18e1,0xc00e1fff,0x70038ff,0xe0007f80,0xe007007,0x1c701dd,0x9c00f800,0x1c000780,0xe0000e,0xe000,0x0, + 0x7f,0xf070070e,0x38038,0x7fff0038,0x1c01c1c,0x7001c00,0x1c007f8,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x7fc00,0x1c00380e, + 0x1c381c7,0x1c01f000,0xe1c001c0,0xfe0000e,0xfe1f,0xfff00000,0x7ff003fc,0xe0000003,0xffc00000,0x10,0x20000,0x3800,0x3fc0180, + 0x703803f,0xce007800,0xff381fe7,0x30,0x0,0xc0,0x0,0x0,0x3fe0,0xc0e07,0xfe3fce00,0x1c000700,0x1c,0x70c00f,0xff006030,0x1c00000, + 0xe07800,0x19800c,0xfcc1c38,0x7fffc00,0x30d818,0x0,0xffff81f,0xf001f800,0x380e,0xf8c007,0x80000000,0x7f8007e0,0xe1c3fe,0x7fc00f, + 0xf8001e00,0xe0701c0,0xe0381c07,0x380e070,0x1c0e070e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x700ff83,0x870c70e0, + 0x71c00e3,0x801c7003,0x8e00700f,0x8038c1c7,0x70e00e,0x1c01c380,0x3801c007,0xf0e3e0,0x3ff807f,0xf00ffe01,0xffc03ff8,0x7ff03ff, + 0xf8e0003f,0xff87fff0,0xfffe1fff,0xc00e0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e1ff,0xfe383383,0x80e0701c, + 0xe0381c0,0x700e1c07,0x703870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x3,0xc000ff0, + 0x3c1e1c1c,0x38000001,0xc0000700,0x1803fff,0xe0000007,0xf8000000,0x7003803,0x800e0001,0xf0000381,0xc0e00007,0xf01e003,0x801c0700, + 0x7c700000,0x7c0,0x0,0x1f00700,0x618061c0,0xe0700e1c,0x38038,0x38000700,0x1c00e38,0x3801c00,0x381e38,0x1c000e1,0xc38e1ce1, + 0xc00e1ffc,0x70038e0,0xf0000780,0xe007007,0x1c701dd,0xdc01fc00,0x1c000780,0xe0000e,0xe000,0x0,0x1ff,0xf070070e,0x38038,0x7fff0038, + 0x1c01c1c,0x7001c00,0x1c007f8,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x3ff00,0x1c00380e,0x1c381cd,0x9c00e000,0xe1c003c0, + 0xf80000e,0x3e18,0x3ff00000,0xffe007fd,0xf0000000,0x38000000,0x10,0x20000,0x1c000,0x3c0300,0x703807f,0xdf007801,0xff7c3fef, + 0x80000000,0x0,0x3e0,0x7ffe7ff,0xff000000,0x1ff8,0x60e07,0xfe7fdf00,0x3c000700,0x1c,0x70c001,0xc0006030,0x7fff0000,0xf03800, + 0x19800c,0x1c38,0x1c07,0xf830cc18,0x0,0x1c0000,0x0,0x380e,0x18c007,0x80000000,0x0,0xe1cfe0,0x1fc003f,0x80003c00,0xe0701c0, + 0xe0381c07,0x380e070,0x1c0e070e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x870e70e0,0x71c00e3,0x801c7003, + 0x8e007007,0x3981c7,0x70e00e,0x1c01c380,0x3801c007,0x1e0e0f8,0xfff81ff,0xf03ffe07,0xffc0fff8,0x1fff07ff,0xf8e0003f,0xff87fff0, + 0xfffe1fff,0xc00e0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e1ff,0xfe386383,0x80e0701c,0xe0381c0,0x700e1c07, + 0x703870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x7f,0xffc00678,0x707f9c1e,0x38000001, + 0xc0000700,0x70,0x7,0xf8000000,0xe003803,0x800e0003,0xe00001c3,0x80e00007,0xe00e007,0x80380380,0x700000,0x7f0,0x0,0x7f00700, + 0x618061ff,0xe070071c,0x38038,0x38000700,0x1c00e38,0x3801c00,0x381c3c,0x1c000e1,0xc38e1ce1,0xc00e1c00,0x70038e0,0x700003c0, + 0xe007007,0x1c701d8,0xdc03dc00,0x1c000f00,0xe00007,0xe000,0x0,0x3ff,0xf070070e,0x38038,0x7fff0038,0x1c01c1c,0x7001c00,0x1c007fc, + 0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x3f00,0x1c00380e,0x1c381cd,0x9c01f000,0x73800780,0xfe0000e,0xfe10,0x7c00000,0x1c000ffb, + 0xf8000000,0x38000000,0x10,0x20000,0x20000,0x1e0700,0x70380ff,0xbf80f003,0xfefe7fdf,0xc0000000,0x0,0x3f0,0x7ffe7ff,0xff000000, + 0x1f8,0x30e07,0xfeffbf80,0x78000700,0x1c,0x70c001,0xc0006030,0x7fff0000,0x783800,0x1ce11c,0xe1c,0x1c07,0xf838ce38,0x0,0x1c0000, + 0x0,0x380e,0x18c000,0x0,0x0,0x1c38c00,0x1800030,0x7800,0xfff01ff,0xe03ffc07,0xff80fff0,0x3fff0ffe,0x1c0001c,0x38000,0x70000e00, + 0xe0001,0xc0003800,0x7003803,0x870e70e0,0x71c00e3,0x801c7003,0x8e00700f,0x803b81c7,0x70e00e,0x1c01c380,0x3801c007,0xffe0e03c, + 0x1fff83ff,0xf07ffe0f,0xffc1fff8,0x3fff0fff,0xf8e0003f,0xff87fff0,0xfffe1fff,0xc00e0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3, + 0x80387007,0xe00e000,0x38c383,0x80e0701c,0xe0381c0,0x70073807,0x701ce0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f,0xffc0063c,0x40619c0f,0x30000001,0xc0000700,0x70,0x7,0xf8000000,0xe003803,0x800e0007,0xc00001c3, + 0xfffc0007,0xe00e007,0x380380,0xf00000,0xfe,0xffff80,0x3f800700,0x618063ff,0xf070071c,0x38038,0x38000700,0x1c00e38,0x3801c00, + 0x381c1e,0x1c000e0,0x38e0ee1,0xc00e1c00,0x70038e0,0x380001c0,0xe007007,0x1ef01d8,0xdc038e00,0x1c001e00,0xe00007,0xe000,0x0, + 0x7c0,0x7070070e,0x38038,0x70000038,0x1c01c1c,0x7001c00,0x1c0079e,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x780,0x1c00380e, + 0xe701cd,0x9c01f000,0x73800f00,0xe0000e,0xe000,0x0,0x1c0007f7,0xf0000000,0x70000000,0x10,0x20000,0x0,0xe0e00,0x703807f,0x7f01e001, + 0xfdfc3fbf,0x80000000,0x0,0x7f0,0x0,0x0,0x3c,0x18e07,0x7f7f00,0xf0000700,0x1c,0x70c001,0xc0007070,0x1c00000,0x3e7000,0xcff18, + 0x3ffc070e,0x1c07,0xf818c630,0x0,0x1c0000,0x0,0x380e,0x18c000,0x0,0x3ffc,0x3870000,0xe000fc00,0x380f000,0x1fff83ff,0xf07ffe0f, + 0xffc1fff8,0x3fff0ffe,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x870770e0,0x71c00e3,0x801c7003,0x8e00701d, + 0xc03f01c7,0x70e00e,0x1c01c380,0x3801c007,0xffc0e01c,0x3e0387c0,0x70f80e1f,0x1c3e038,0x7c071e1c,0xe00038,0x70000,0xe0001c00, + 0xe0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e000,0x398383,0x80e0701c,0xe0381c0,0x70073807,0x701ce0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f,0xffc0061c,0xc0dc07,0xf0000001,0xc0000700, + 0x70,0x0,0x0,0x1c003c07,0x800e000f,0x1c3,0xfffc0007,0xe00e007,0x380380,0xe00000,0x1f,0xc0ffff81,0xfc000700,0x618063ff,0xf070070e, + 0x38070,0x38000700,0xe00e38,0x3801c00,0x381c0e,0x1c000e0,0x38e0ee1,0xe01e1c00,0x78078e0,0x380001c0,0xe007007,0xee01f8,0xfc078f00, + 0x1c001c00,0xe00003,0x8000e000,0x0,0x700,0x7070070e,0x38038,0x70000038,0x1c01c1c,0x7001c00,0x1c0070e,0x1c0070,0xe1c701c1, + 0xc01c1c01,0xc700700e,0x380,0x1c00380e,0xe700ed,0xb803f800,0x77800f00,0x70000e,0x1c000,0x0,0xe0003f7,0xe0000000,0x70000000, + 0x10,0x20000,0x1c0e0,0xe1c00,0x703803f,0x7e01c000,0xfdf81fbf,0x0,0x0,0x3f0,0x0,0x0,0x1c,0x1ce07,0x3f7e00,0xf0000700,0x1c, + 0x70c001,0xc00038e0,0x1c00038,0xf7000,0xe3e38,0x3ffc0387,0x1c00,0x1cc770,0x0,0x1c0000,0x0,0x380e,0x18c000,0x0,0x3ffc,0x70e0001, + 0xe001fe00,0x780e000,0x1fff83ff,0xf07ffe0f,0xffc1fff8,0x3fff0ffe,0xe0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003807, + 0x70770f0,0xf1e01e3,0xc03c7807,0x8f00f038,0xe03e03c7,0x70e00e,0x1c01c380,0x3801c007,0xff00e00e,0x38038700,0x70e00e1c,0x1c38038, + 0x70071c1c,0xe00038,0x70000,0xe0001c00,0xe0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e000,0x3b0383,0x80e0701c, + 0xe0381c0,0x70077807,0x701de0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6,0x1c00061c, + 0xc0de03,0xe0000001,0xc0000700,0x70,0x0,0x0,0x1c001c07,0xe001e,0x1c3,0xfffc0007,0x600e00e,0x380380,0xe00000,0x7,0xf0ffff87, + 0xf0000000,0x60c0e380,0x7070070e,0x38070,0x38000700,0xe00e38,0x3801c00,0x381c0f,0x1c000e0,0x38e06e0,0xe01c1c00,0x38070e0, + 0x1c0001c0,0xe007007,0xee00f8,0xf80f0700,0x1c003c00,0xe00003,0x8000e000,0x0,0x700,0x70780f0f,0x3c078,0x70000038,0x1e03c1c, + 0x7001c00,0x1c0070f,0x1c0070,0xe1c701c1,0xe03c1e03,0xc780f00e,0x380,0x1c00380e,0xe700f8,0xf807bc00,0x3f001e00,0x70000e,0x1c000, + 0x0,0xe0001ff,0xc0000000,0x70000000,0x10,0x20000,0x33110,0xe0e00,0x383801f,0xfc03c000,0x7ff00ffe,0x0,0x0,0x3e0,0x0,0x0,0x1c, + 0x38e07,0x1ffc01,0xe0000700,0x1c,0x78c001,0xc0007ff0,0x1c00038,0x7c000,0x70070,0x1c3,0x80001c00,0xe00e0,0x0,0x1c0000,0x0, + 0x380e,0x18c000,0x0,0x0,0xe1c0001,0xe0010700,0x780e000,0x1c038380,0x70700e0e,0x1c1c038,0x78070e0e,0xe0001c,0x38000,0x70000e00, + 0xe0001,0xc0003800,0x7003807,0x7037070,0xe0e01c1,0xc0383807,0x700e070,0x701c0387,0x70e00e,0x1c01c380,0x3801c007,0xe00e,0x38038700, + 0x70e00e1c,0x1c38038,0x70071c1c,0xf00038,0x70000,0xe0001c00,0xe0001,0xc0003800,0x7003c07,0x8380e0f0,0x1e1e03c3,0xc078780f, + 0xf01e007,0x803e0783,0x80e0701c,0xe0381c0,0x7003f007,0x80f00fc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x6,0x1800061c,0xc0de01,0xc0000000,0xc0000e00,0x70,0xf0000,0x3c00,0x38001c0f,0xe003c,0x3c0,0xe0000e,0x701e00e, + 0x3c0780,0x1e003c0,0x780000,0xfc00001f,0x80000000,0x60e1e780,0x78700f07,0x4380f0,0x38000700,0xf00e38,0x3801c00,0xc0781c07, + 0x81c000e0,0x38e07e0,0xe03c1c00,0x380f0e0,0x1e0003c0,0xe00780f,0xee00f0,0x780e0780,0x1c007800,0xe00001,0xc000e000,0x0,0x700, + 0xf0780e07,0x8041c078,0x38020038,0xe03c1c,0x7001c00,0x1c00707,0x801c0070,0xe1c701c0,0xe0381e03,0x8380f00e,0x80380,0x1c003c1e, + 0x7e00f8,0xf80f1e00,0x3f003c00,0x70000e,0x1c000,0x0,0xf0100f7,0x80078000,0x700078f0,0x10,0x7ff000,0x61208,0x1e0700,0x383800f, + 0x78078000,0x3de007bc,0x0,0x0,0x0,0x0,0x0,0x401c,0x70e0f,0xf7803,0xc0000700,0x1c,0x38c001,0xc000efb8,0x1c00038,0x1e000,0x3c1e0, + 0xc1,0x80000000,0x783c0,0x0,0x0,0x0,0x3c1e,0x18c000,0x0,0x0,0xc180003,0x60000300,0xd80e010,0x3c03c780,0x78f00f1e,0x1e3c03c, + 0x70039c0e,0x70041c,0x38000,0x70000e00,0xe0001,0xc0003800,0x700380f,0x703f070,0x1e0e03c1,0xc078380f,0x701e0e0,0x381c0787, + 0x80f0f01e,0x1e03c3c0,0x7801c007,0xe00e,0x38078700,0xf0e01e1c,0x3c38078,0x700f1c1c,0x78041c,0x1038020,0x70040e00,0x800e0001, + 0xc0003800,0x7001c07,0x380e070,0x1c0e0381,0xc070380e,0x701c007,0x801e0703,0xc1e0783c,0xf0781e0,0xf003f007,0x80e00fc0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0xe,0x1801867c,0xc0cf83,0xe0000000,0xe0000e00, + 0x70,0xf0000,0x3c00,0x38000f1e,0xe0070,0x180780,0xe0603e,0x783c01e,0x1e0f01,0x7c003c0,0x780000,0x3c00001e,0x700,0x307fe700, + 0x38701e07,0xc1c383e0,0x38000700,0x7c1e38,0x3801c00,0xe0f01c03,0x81c000e0,0x38e03e0,0x78781c00,0x1e1e0e0,0xe180780,0xe003c1e, + 0x7c00f0,0x781e03c0,0x1c007000,0xe00001,0xc000e000,0x0,0x783,0xf07c1e07,0xc0c1e0f8,0x3e0e0038,0xf07c1c,0x7001c00,0x1c00703, + 0xc01e0070,0xe1c701c0,0xf0781f07,0x83c1f00e,0xe0f80,0x1e003c3e,0x7e00f8,0xf80e0e00,0x3f003800,0x70000e,0x1c000,0x0,0x7830077, + 0xf0000,0x700078f0,0x10,0x20000,0x41208,0xc03c0380,0x3c38007,0x70070000,0x1dc003b8,0x0,0x0,0x0,0x0,0x0,0x707c,0x6070f,0x86077003, + 0x80000700,0x1c,0x3ec401,0xc001c01c,0x1c00038,0xf000,0x1ffc0,0x40,0x80000000,0x3ff80,0x0,0x0,0x0,0x3e3e,0x18c000,0x0,0x0, + 0x8100006,0x60000300,0x1980f070,0x3801c700,0x38e0071c,0xe3801c,0x70039c0e,0x7c1c1c,0x38000,0x70000e00,0xe0001,0xc0003800, + 0x700383e,0x701f03c,0x3c078780,0xf0f01e1e,0x3c3c1c0,0x1c3f0f03,0xc1e0783c,0xf0781e0,0xf001c007,0xe81e,0x3c1f8783,0xf0f07e1e, + 0xfc3c1f8,0x783f1e3e,0x187c0c1f,0x703e0e0,0x7c1c0f83,0x800e0001,0xc0003800,0x7001e0f,0x380e078,0x3c0f0781,0xe0f03c1e,0x783c007, + 0x801e0f03,0xc3e0787c,0xf0f81e1,0xf003f007,0xc1e00fc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x1c,0xe,0x3801fff8,0x6187ff,0xe0000000,0xe0000e00,0x70,0xf0000,0x3c00,0x38000ffe,0x1fff0ff,0xfe1fff80,0xe07ffc,0x3ffc01c, + 0x1fff01,0xff8003c0,0x780000,0x4000010,0x700,0x301e6700,0x387ffe03,0xffc3ffc0,0x3fff0700,0x3ffe38,0x383ffe0,0xfff01c03,0xc1fff8e0, + 0x38e03e0,0x7ff81c00,0x1ffe0e0,0xf1fff80,0xe003ffe,0x7c00f0,0x781c01c0,0x1c00ffff,0xe00001,0xc000e000,0x0,0x3ff,0x707ffc03, + 0xffc0fff8,0x1ffe0038,0x7ffc1c,0x707fff0,0x1c00701,0xc00ff070,0xe1c701c0,0x7ff01fff,0x1fff00e,0xfff00,0xff81fee,0x7e00f0, + 0x781e0f00,0x1e007ffc,0x70000e,0x1c000,0x0,0x3ff003e,0xf0000,0xe00070e0,0x60830010,0x20000,0x41208,0xfffc01c0,0x1fffe03,0xe00ffff0, + 0xf8001f0,0x0,0x0,0x0,0x0,0x0,0x7ff8,0xc07fd,0xfe03e007,0xffc00700,0x1c,0x1ffc1f,0xffc08008,0x1c00038,0x7000,0x7f00,0x0,0x0, + 0xfe00,0x0,0xffff800,0x0,0x3ff7,0x8018c000,0x0,0x0,0x6,0x60000700,0x19807ff0,0x3801c700,0x38e0071c,0xe3801c,0x70039c0f,0xf03ffc1f, + 0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ffc,0x701f03f,0xfc07ff80,0xfff01ffe,0x3ffc080,0x83fff03,0xffe07ffc,0xfff81ff, + 0xf001c007,0xeffc,0x1ffb83ff,0x707fee0f,0xfdc1ffb8,0x3ff70ff7,0xf83ffc0f,0xff01ffe0,0x3ffc07ff,0x83fff87f,0xff0fffe1,0xfffc0ffe, + 0x380e03f,0xf807ff00,0xffe01ffc,0x3ff8007,0x803ffe01,0xfee03fdc,0x7fb80ff,0x7001e007,0xffc00780,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0xc,0x3801fff0,0x7f83fe,0x70000000,0xe0000e00,0x0,0xf0000,0x3c00,0x700007fc, + 0x1fff0ff,0xfe1ffe00,0xe07ff8,0x1ff801c,0xffe01,0xff0003c0,0x780000,0x0,0x700,0x38000f00,0x3c7ffc01,0xff83ff80,0x3fff0700, + 0x1ffc38,0x383ffe0,0x7fe01c01,0xe1fff8e0,0x38e03e0,0x3ff01c00,0xffc0e0,0x71fff00,0xe001ffc,0x7c00f0,0x783c01e0,0x1c00ffff, + 0xe00000,0xe000e000,0x0,0x1ff,0x7077f801,0xff807fb8,0xffc0038,0x3fdc1c,0x707fff0,0x1c00701,0xe007f070,0xe1c701c0,0x3fe01dfe, + 0xff700e,0x7fe00,0xff80fee,0x3c0070,0x703c0780,0x1e007ffc,0x70000e,0x1c000,0x0,0x1fe001c,0xe0000,0xe000e1c0,0x71c78010,0x20000, + 0x21318,0xfff800c0,0xfffe01,0xc00ffff0,0x70000e0,0x0,0x0,0x0,0x0,0x0,0x3ff0,0x1803fd,0xfe01c007,0xffc00700,0x1c,0xffc1f,0xffc00000, + 0x1c00038,0x7000,0x0,0x0,0x0,0x0,0x0,0xffff800,0x0,0x3ff7,0x8018c000,0x0,0x0,0xc,0x60000e00,0x31803fe0,0x7801ef00,0x3de007bc, + 0xf7801e,0xf003fc0f,0xf01ff81f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ff8,0x701f01f,0xf803ff00,0x7fe00ffc,0x1ff8000, + 0x67fe01,0xffc03ff8,0x7ff00ff,0xe001c007,0xeff8,0xffb81ff,0x703fee07,0xfdc0ffb8,0x1ff70ff7,0xf81ff807,0xfe00ffc0,0x1ff803ff, + 0x3fff87f,0xff0fffe1,0xfffc07fc,0x380e01f,0xf003fe00,0x7fc00ff8,0x1ff0000,0x37fc00,0xfee01fdc,0x3fb807f,0x7001e007,0x7f800780, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0xc,0x30007fc0,0x1e00f8,0x78000000,0x70001c00, + 0x0,0xe0000,0x3c00,0x700001f0,0x1fff0ff,0xfe07f800,0xe01fe0,0x7e0038,0x3f800,0xfc0003c0,0x700000,0x0,0x700,0x18000e00,0x1c7ff000, + 0x7e03fe00,0x3fff0700,0x7f038,0x383ffe0,0x1f801c00,0xf1fff8e0,0x38e01e0,0xfc01c00,0x3f80e0,0x787fc00,0xe0007f0,0x7c00f0,0x387800f0, + 0x1c00ffff,0xe00000,0xe000e000,0x0,0xfc,0x7071f000,0x3f003e38,0x3f00038,0x1f1c1c,0x707fff0,0x1c00700,0xf003f070,0xe1c701c0, + 0x1f801c7c,0x7c700e,0x1f800,0x3f8078e,0x3c0070,0x707803c0,0x1c007ffc,0x70000e,0x1c000,0x0,0x7c0008,0x1e0000,0xe000e1c0,0x71c30010, + 0x20000,0x1e1f0,0x3fe00020,0x3ffe00,0x800ffff0,0x2000040,0x0,0x0,0x0,0x0,0x0,0xfc0,0x3001f0,0x78008007,0xffc00700,0x1c,0x3f81f, + 0xffc00000,0x1c00038,0x407000,0x0,0x0,0x0,0x0,0x0,0xffff800,0x0,0x39c7,0x18c000,0x0,0x0,0x18,0x60001c00,0x61801f80,0x7000ee00, + 0x1dc003b8,0x77000e,0xe001f80f,0xf007e01f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83fc0,0x700f007,0xe000fc00,0x1f8003f0, + 0x7e0000,0xe1f800,0x7f000fe0,0x1fc003f,0x8001c007,0xe7f0,0x7e380fc,0x701f8e03,0xf1c07e38,0xfc703c1,0xe003f001,0xf8003f00, + 0x7e000fc,0x3fff87f,0xff0fffe1,0xfffc03f8,0x380e00f,0xc001f800,0x3f0007e0,0xfc0000,0x61f800,0x78e00f1c,0x1e3803c,0x7001c007, + 0x1f000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x70001c00,0x0, + 0x1c0000,0x0,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0xc000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0, + 0x0,0x0,0x0,0x0,0xe00000,0x7000e000,0x0,0x0,0x0,0x0,0x0,0x1c00,0x0,0x1c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x1c000000, + 0x70000e,0x1c000,0x0,0x0,0x1c0000,0xe000c180,0x10,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000, + 0x0,0x38,0x70e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x18c000,0x2000,0x0,0x1f,0xf8003800,0x7fe00000,0x0,0x0,0x0,0x0,0x4000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x400000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x400000, + 0x0,0x0,0x1c007,0x700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x30001800, + 0x0,0x1c0000,0x0,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e000, + 0x0,0x0,0x0,0x0,0x0,0xe00000,0x7000e000,0x0,0x0,0x0,0x0,0x0,0x1c00,0x0,0x1c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x1c000000, + 0x70000e,0x1c000,0x0,0x0,0x1c0001,0xe001c380,0x10,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000, + 0x0,0x38,0x7fe000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x18c000,0x3000,0x0,0x1f,0xf8007000,0x7fe00000,0x0,0x0,0x0,0x0,0x6000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x1c007,0x700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x38003800, + 0x0,0x380000,0x1,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00000,0x0,0x0,0x3c18000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf000, + 0x0,0x0,0x0,0x0,0x0,0xfe0000,0x380fe000,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x1c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x38000000, + 0x78000e,0x3c000,0x0,0x0,0x180001,0xc0018300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0, + 0x38,0x1f8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x18c000,0x1800,0x0,0x0,0x6000e000,0x1800000,0x0,0x0,0x0,0x0,0x3000,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x38007,0xe00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x18003000, + 0x0,0x300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x1ff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0, + 0x0,0x0,0x0,0xfe0000,0xfe000,0x0,0x0,0x0,0x0,0x0,0x607800,0x0,0x3c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x78000000, + 0x3f800e,0x3f8000,0x0,0x0,0x300043,0xc0018200,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000, + 0x0,0x38,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x11800,0x0,0x0,0x6001ff00,0x1800000,0x0,0x0,0x0,0x0,0x23000,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x23000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78007, + 0x1e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x1c007000,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe0000, + 0xfe000,0x0,0x0,0x0,0x0,0x0,0x7ff000,0x0,0x7f800000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x3,0xf8000000,0x3f800e,0x3f8000,0x0, + 0x0,0x10007f,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x38,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x3800,0x0,0x1f800,0x0,0x0,0x6001ff00,0x1800000,0x0,0x0,0x0,0x0,0x3f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f8007,0xfe00,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff8,0x0,0x0,0x0,0x0,0x7fe000,0x0, + 0x7f000000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x3,0xf0000000,0xf800e,0x3e0000,0x0,0x0,0x7f,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x1f000,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x3f0007,0xfc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x7fff8,0x0,0x0,0x0,0x0,0x1fc000,0x0,0x7e000000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x3,0xc0000000,0xe,0x0, + 0x0,0x0,0x3e,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x3800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0007,0xf000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 }; + + // Definition of a 29x57 font. + const unsigned int font29x57[29*57*256/32] = { + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x781e00,0x0,0x0,0x7,0x81e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c0000,0xf8000,0x7e00000,0x0,0x7, + 0xc0000000,0x0,0x7c00,0xf80,0x7e000,0x0,0x7c00000,0xf80000,0x7e000000,0x0,0x0,0x1f00,0x3e0,0x1f800,0x0,0x0,0x0,0x3,0xe0000000, + 0x7c00003f,0x0,0xf8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x3c3c00,0x0,0x0,0x3,0xc3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e0000, + 0x1f0000,0x7e00000,0xf838001f,0xf80001f,0xf0000000,0x0,0x3e00,0x1f00,0x7e000,0x3e1f000,0x3e00000,0x1f00000,0x7e00003e,0x1f000000, + 0x3e0,0xe0000f80,0x7c0,0x1f800,0x3e0e00,0x7c3e000,0x0,0x1,0xf0000000,0xf800003f,0x1f0f,0x800001f0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e7800,0x0,0x0, + 0x1,0xe7800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e0000,0x1e0000,0xff00001,0xfe38001f,0xf80003f, + 0xf8000000,0x0,0x1e00,0x1e00,0xff000,0x3e1f000,0x1e00000,0x1e00000,0xff00003e,0x1f000000,0x7f8,0xe0000780,0x780,0x3fc00,0x7f8e00, + 0x7c3e000,0x0,0x0,0xf0000000,0xf000007f,0x80001f0f,0x800001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xef000,0x0,0x0,0x0,0xef000000,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000,0x3c0000,0x1e780003,0xfff8001f,0xf80003c,0x78000000,0x0,0xf00,0x3c00,0x1e7800, + 0x3e1f000,0xf00000,0x3c00001,0xe780003e,0x1f000000,0xfff,0xe00003c0,0xf00,0x79e00,0xfffe00,0x7c3e000,0x0,0x0,0x78000001,0xe00000f3, + 0xc0001f0f,0x800003c0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7e000,0x0,0x0,0x0,0x7e000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x78000,0x780000,0x3c3c0003,0x8ff0001f,0xf800078,0x3c000000,0x0,0x780,0x7800,0x3c3c00,0x3e1f000,0x780000,0x7800003,0xc3c0003e, + 0x1f000000,0xe3f,0xc00001e0,0x1e00,0xf0f00,0xe3fc00,0x7c3e000,0x0,0x0,0x3c000003,0xc00001e1,0xe0001f0f,0x80000780,0x0,0x0, + 0x0,0x0,0x0,0x0,0x1f,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x7e000,0x0,0x0,0x0,0x7e000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc00,0x7e000,0xfe000,0x0,0x3c000,0xf00000,0x781e0003, + 0x83e0001f,0xf800070,0x1c000000,0x0,0x3c0,0xf000,0x781e00,0x3e1f000,0x3c0000,0xf000007,0x81e0003e,0x1f000000,0xe0f,0x800000f0, + 0x3c00,0x1e0780,0xe0f800,0x7c3e000,0x0,0x0,0x1e000007,0x800003c0,0xf0001f0f,0x80000f00,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf8000000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3fc00,0x1fe000,0x3ff800,0x0,0x0,0x0,0x0,0x0,0x70,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x78000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x78,0xf000000,0x0,0x0,0x780f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x3fc00,0x1fe000,0x3ffc00,0x0,0x0,0x0,0x0,0x0,0x70,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00000,0x3e000,0x3e00000,0x0,0x78,0x3c000000,0x0,0x1f000,0x3e0, + 0x3e000,0x0,0x1f000000,0x3e0000,0x3e000000,0x0,0x0,0x7c00,0xf8,0xf800,0x0,0x0,0x0,0xf,0x80000000,0x1f00001f,0x0,0x3e,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x781c0000,0x38,0xe000000,0x0,0x0,0x380e0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x39c00,0x1ce000,0x303e00, + 0x0,0x0,0x0,0x0,0x0,0x78,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x0,0x0, + 0x0,0x0,0xf80000,0x7c000,0x3e00000,0xf0380000,0x70,0x1c000000,0x0,0xf800,0x7c0,0x3e000,0x0,0xf800000,0x7c0000,0x3e000000, + 0x0,0x3c0,0xe0003e00,0x1f0,0xf800,0x3c0e00,0x0,0x0,0x7,0xc0000000,0x3e00001f,0x0,0x7c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0xff,0x0, + 0xf8,0xf8000,0x1c000,0x0,0x0,0x0,0x0,0x1f,0xc0000000,0x1ff8,0xff00,0x0,0x0,0x3fe000,0x0,0x1fc00001,0xfe000000,0x0,0x0,0x0, + 0x0,0x7f800,0x0,0x0,0x0,0xff00000,0x0,0x0,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xf8000000,0xfe,0x0,0x7f80,0x0,0x0,0x0,0x0,0x0, + 0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x780000,0x1,0xe0000000,0x0,0x780000,0x3,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc0000f0,0x3f000,0x0,0x0,0x3fc00,0x0,0x0,0x1fc000,0x0,0x0,0x0,0x1fc0, + 0x0,0xff000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe1c0000,0x1c,0x1c000000,0x0,0x0,0x1c1c0,0x0,0x0,0x0,0x0,0x1fe0000, + 0x0,0x0,0x1ff,0x1f0f8,0x0,0xff000,0x0,0x0,0x0,0x3f,0xff00000f,0x80000000,0xfe0,0x3f80,0xf00,0x0,0x0,0x0,0x1,0xf8000003,0xe0000000, + 0x1c00,0xe000,0xe00,0x0,0x0,0x0,0x0,0x0,0x3c,0x78000000,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f0,0x3f80,0x1fc00,0xfe000, + 0x7f0000,0x0,0x1fc07000,0x0,0x0,0x0,0x0,0x0,0x3f800,0x780000,0x78000,0x7f00001,0xfc38001f,0xf800070,0x1c000000,0x0,0x7800, + 0x780,0x7f000,0x3e1f000,0x7800000,0x780000,0x7f00003e,0x1f0003f0,0x7f0,0xe0001e00,0x1e0,0x1fc00,0x7f0e00,0x7c3e000,0x0,0x3, + 0xc0000000,0x3c00003f,0x80001f0f,0x80000078,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x1e078000,0x30000000,0x3ff,0xc00001e0,0xf0, + 0x78000,0x1c000,0x0,0x0,0x0,0x0,0x1e0007f,0xf000007e,0x1ffff,0x7ffe0,0x1f80,0x3ffff80,0xfff803,0xfffff800,0xfff80007,0xff800000, + 0x0,0x0,0x0,0x0,0x1ffe00,0x0,0xfe0003,0xfff80000,0x3ffe01ff,0xe00003ff,0xffe01fff,0xff0003ff,0xe01e0007,0x803ffff0,0xfff80, + 0x3c000fc0,0x7800001f,0x8003f07e,0x1e000f,0xfe0007ff,0xf00003ff,0x8007ffe0,0x1fff8,0x7fffffe,0xf0003c1,0xe000079e,0xf1f,0x1f3e0, + 0x1f01ff,0xfff8003f,0xf003c000,0x7fe0,0x3f00,0x0,0x3c0000,0x1,0xe0000000,0x0,0x780000,0xf,0xfe000000,0x78000,0x3c00,0xf000, + 0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xfc0000f0,0x3fe00,0x0,0x0,0xfff00,0x0,0x0,0x3fe000, + 0x0,0x0,0x0,0x1dc0,0x0,0x3fff00,0x0,0x3ffff80,0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff1c07ff,0x3c0f001e,0x3c000000, + 0x0,0x0,0x1e3c0,0xf80007c,0x0,0x780000,0x0,0xfff8000,0x3e00,0x1f00000,0x7ff,0xc001f0f8,0x0,0x3ffc00,0x0,0x0,0x0,0x3f,0xff00003f, + 0xe0000000,0x3ff8,0xffe0,0x1e00,0x0,0xfffc00,0x0,0x7,0xf800000f,0xf8000000,0x1c00,0xe000,0xe00,0xf000,0x1fc000,0xfe0000,0x7f00000, + 0x3f800001,0xfc00003f,0xf80000ff,0xffc003ff,0xe007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01ffc, + 0xfc00,0x3c001ffc,0xffe0,0x7ff00,0x3ff800,0x1ffc000,0x0,0x7ff8f0f0,0x3c0780,0x1e03c00,0xf01e000,0x783e0001,0xf01e0000,0xffe00, + 0x3c0000,0xf0000,0x7700001,0xfe38001f,0xf800070,0x1c000000,0x0,0x3c00,0xf00,0x77000,0x3e1f000,0x3c00000,0xf00000,0x7700003e, + 0x1f0000f8,0xc0007f8,0xe0000f00,0x3c0,0x1dc00,0x7f8e00,0x7c3e000,0x0,0x1,0xe0000000,0x7800003b,0x80001f0f,0x800000f0,0x1e0000, + 0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x780000,0x3c1e0000,0x1e070000,0x300001f0,0x7ff,0xc00001e0,0x1e0,0x7c000,0x1c000,0x0,0x0,0x0,0x0,0x3c000ff,0xf80007fe, + 0x3ffff,0x801ffff8,0x1f80,0x3ffff80,0x3fff803,0xfffff801,0xfffc000f,0xffc00000,0x0,0x0,0x0,0x0,0x7fff80,0x0,0xfe0003,0xffff0000, + 0xffff01ff,0xfc0003ff,0xffe01fff,0xff000fff,0xf01e0007,0x803ffff0,0xfff80,0x3c001f80,0x7800001f,0xc007f07e,0x1e001f,0xff0007ff, + 0xfc0007ff,0xc007fffc,0x3fffc,0x7fffffe,0xf0003c1,0xf0000f9e,0xf0f,0x8003e1e0,0x1e01ff,0xfff8003f,0xf001e000,0x7fe0,0x3f00, + 0x0,0x1e0000,0x1,0xe0000000,0x0,0x780000,0x1f,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x1fff80,0x0,0x0,0xffe000,0x0,0x0,0x0,0x3de0,0x0,0x7fff80,0x0,0xfffff80, + 0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe7bc07ff,0x3e1f000f,0x78000000,0x0,0x0,0xf780,0x7800078,0x0,0x780000,0x180000, + 0x1fff8000,0x1e00,0x1e0003c,0xfff,0xc001f0f8,0x0,0x7ffe00,0x0,0x0,0x0,0x3f,0xff00007f,0xf0000000,0x3ffc,0xfff0,0x3c00,0x0, + 0x7fffc00,0x0,0x7,0xf800003f,0xfe000000,0x1c00,0xe000,0xe00,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00001f,0xe00001ff, + 0xffc00fff,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xc000fc00,0x3c003ffe,0x1fff0, + 0xfff80,0x7ffc00,0x3ffe000,0x0,0xfffce0f0,0x3c0780,0x1e03c00,0xf01e000,0x781e0001,0xe01e0000,0x3fff00,0x1e0000,0x1e0000,0xf780003, + 0xcf78001f,0xf800078,0x3c000000,0x0,0x1e00,0x1e00,0xf7800,0x3e1f000,0x1e00000,0x1e00000,0xf780003e,0x1f0000fc,0x7c000f3d, + 0xe0000780,0x780,0x3de00,0xf3de00,0x7c3e000,0x0,0x0,0xf0000000,0xf000007b,0xc0001f0f,0x800001e0,0x1e0000,0x3e1f00,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000, + 0x3c1e0000,0x1e0f0000,0x300007fc,0xfff,0xc00001e0,0x1e0,0x3c000,0x1c000,0x0,0x0,0x0,0x0,0x3c001ff,0xfc001ffe,0x3ffff,0xc01ffffc, + 0x3f80,0x3ffff80,0x7fff803,0xfffff803,0xfffe001f,0xffe00000,0x0,0x0,0x0,0x0,0xffff80,0x7f800,0xfe0003,0xffff8001,0xffff01ff, + 0xff0003ff,0xffe01fff,0xff001fff,0xf01e0007,0x803ffff0,0xfff80,0x3c003f00,0x7800001f,0xc007f07f,0x1e003f,0xff8007ff,0xff000fff, + 0xe007ffff,0x7fffc,0x7fffffe,0xf0003c0,0xf0000f1e,0xf07,0x8003c1f0,0x3e01ff,0xfff8003f,0xf001e000,0x7fe0,0x7f80,0x0,0xe0000, + 0x1,0xe0000000,0x0,0x780000,0x1f,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0, + 0x0,0x0,0x0,0x0,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x3fff80,0x0,0x0,0xffe000,0x0,0x0,0x0,0x78f0,0x0,0xffff80,0x0,0x3fffff80,0x1f, + 0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xc7f80070,0x3e1f0007,0x70000000,0x0,0x0,0x7700,0x7c000f8,0x0,0x780000,0x180000, + 0x3fff8000,0x1f00,0x3e0003c,0x1f03,0xc001f0f8,0x0,0x703f00,0x0,0x0,0x0,0x3f,0xff0000f0,0xf8000000,0x303e,0xc0f8,0x7800,0x0, + 0xffffc00,0x0,0x7,0x3800003e,0x3e000000,0x1c00,0xe000,0x3c00,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00000f,0xe00001ff, + 0xffc01fff,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf000fe00,0x3c007fff,0x3fff8, + 0x1fffc0,0xfffe00,0x7fff000,0x1,0xffffc0f0,0x3c0780,0x1e03c00,0xf01e000,0x781f0003,0xe01e0000,0x3fff80,0xe0000,0x3c0000,0x1e3c0003, + 0x8ff0001f,0xf80003c,0x78000000,0x0,0xe00,0x3c00,0x1e3c00,0x3e1f000,0xe00000,0x3c00001,0xe3c0003e,0x1f00007f,0xf8000e3f,0xc0000380, + 0xf00,0x78f00,0xe3fc00,0x7c3e000,0x0,0x0,0x70000001,0xe00000f1,0xe0001f0f,0x800003c0,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c0f0000, + 0x30000ffe,0xf80,0xc00001e0,0x3c0,0x1e000,0x101c040,0x0,0x0,0x0,0x0,0x78003f0,0x7e001ffe,0x3f807,0xe01f00fe,0x3f80,0x3ffff80, + 0x7e01803,0xfffff007,0xe03f003f,0x3f00000,0x0,0x0,0x0,0x0,0xfc0fc0,0x3ffe00,0xfe0003,0xffffc003,0xf81f01ff,0xff8003ff,0xffe01fff, + 0xff003f01,0xf01e0007,0x803ffff0,0xfff80,0x3c007e00,0x7800001f,0xc007f07f,0x1e007e,0xfc007ff,0xff801f83,0xf007ffff,0x800fc07c, + 0x7fffffe,0xf0003c0,0xf0000f0f,0x1e07,0xc007c0f8,0x7c01ff,0xfff8003c,0xf000,0x1e0,0xffc0,0x0,0xf0000,0x1,0xe0000000,0x0,0x780000, + 0x3e,0x0,0x78000,0x3c00,0xf000,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1f,0x800000f0,0x1f80, + 0x0,0x0,0x7e0780,0x0,0x0,0x1f82000,0x0,0x0,0x0,0x7070,0x0,0x1f80f80,0x0,0x7fffff80,0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x1,0xc3f80070,0x3f3f0007,0xf0000000,0x0,0x0,0x7f00,0x3e001f0,0x0,0x780000,0x180000,0x7f018000,0xf80,0x7c0003c,0x3e00, + 0x4001f0f8,0xfe00,0x400f00,0x0,0x0,0x0,0x7f000000,0xe0,0x38000000,0x1e,0x38,0x7800,0x0,0x1ffe1c00,0x0,0x0,0x38000078,0xf000000, + 0x1c00,0xe000,0x7f800,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00001f,0xf00001ff,0xffc03f81,0xf007ffff,0xc03ffffe, + 0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf800fe00,0x3c00fc1f,0x8007e0fc,0x3f07e0,0x1f83f00,0xfc1f800, + 0x3,0xf07fc0f0,0x3c0780,0x1e03c00,0xf01e000,0x780f8007,0xc01e0000,0x7e0fc0,0xf0000,0x3c0000,0x1c1c0003,0x87f0001f,0xf80003f, + 0xf8000000,0x0,0xf00,0x3c00,0x1c1c00,0x3e1f000,0xf00000,0x3c00001,0xc1c0003e,0x1f00003f,0xc0000e1f,0xc00003c0,0xf00,0x70700, + 0xe1fc00,0x7c3e000,0x0,0x0,0x78000001,0xe00000e0,0xe0001f0f,0x800003c0,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c0f0001,0xff801e0f, + 0x1f00,0x1e0,0x3c0,0x1e000,0x3c1c1e0,0x0,0x0,0x0,0x0,0x78007c0,0x1f001f9e,0x3c001,0xf010003e,0x7780,0x3c00000,0xf800000,0xf007, + 0xc01f007c,0x1f80000,0x0,0x0,0x0,0x0,0xe003e0,0x7fff00,0x1ef0003,0xc007e007,0xc00301e0,0x1fc003c0,0x1e00,0x7c00,0x301e0007, + 0x80007800,0x780,0x3c00fc00,0x7800001f,0xe00ff07f,0x1e00f8,0x3e00780,0x1fc03e00,0xf807801f,0xc01f001c,0xf000,0xf0003c0,0xf0000f0f, + 0x1e03,0xc00f8078,0x780000,0xf0003c,0xf000,0x1e0,0x1f3e0,0x0,0x78000,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0, + 0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1f,0xf0,0xf80,0x0,0x0,0xf80180,0x0,0x0,0x1e00000, + 0x0,0x0,0x0,0xe038,0x0,0x3e00380,0x0,0xfe0f0000,0x0,0xf0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xc0f00070,0x3b370003,0xe0000000, + 0x0,0x0,0x3e00,0x1e001e0,0x0,0x780000,0x180000,0x7c000000,0x780,0x780003c,0x3c00,0x0,0x7ffc0,0x780,0x0,0x0,0x3,0xffe00000, + 0x1c0,0x3c000000,0xe,0x38,0xf000,0x0,0x3ffe1c00,0x0,0x0,0x38000078,0xf000000,0x1c00,0xe000,0x7f000,0xf000,0x3de000,0x1ef0000, + 0xf780000,0x7bc00003,0xde00001e,0xf00003e7,0x80007c00,0x30078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001, + 0xe0001e03,0xfc00fe00,0x3c01f007,0xc00f803e,0x7c01f0,0x3e00f80,0x1f007c00,0x7,0xc01f80f0,0x3c0780,0x1e03c00,0xf01e000,0x78078007, + 0x801e0000,0x7803c0,0x78000,0x780000,0x380e0003,0x81e00000,0x1f,0xf0000000,0x0,0x780,0x7800,0x380e00,0x0,0x780000,0x7800003, + 0x80e00000,0x1ff,0x80000e07,0x800001e0,0x1e00,0xe0380,0xe07800,0x0,0x0,0x0,0x3c000003,0xc00001c0,0x70000000,0x780,0x1e0000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x780000,0x3c1e0000,0x3c0e0007,0xfff01c07,0x1e00,0x1e0,0x780,0xf000,0x3e1c3e0,0x0,0x0,0x0,0x0,0xf0007c0,0x1f00181e,0x20000, + 0xf000001f,0xf780,0x3c00000,0x1f000000,0x1f00f,0x800f8078,0xf80000,0x0,0x0,0x0,0x0,0x8003e0,0x1fc0f80,0x1ef0003,0xc001e007, + 0x800101e0,0x7e003c0,0x1e00,0x7800,0x101e0007,0x80007800,0x780,0x3c00f800,0x7800001e,0xe00ef07f,0x801e00f0,0x1e00780,0x7c03c00, + 0x78078007,0xc01e0004,0xf000,0xf0003c0,0x78001e0f,0x1e03,0xe00f807c,0xf80000,0x1f0003c,0x7800,0x1e0,0x3e1f0,0x0,0x3c000,0x1, + 0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0, + 0x1e,0xf0,0x780,0x0,0x0,0x1f00080,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x1e03c,0x0,0x3c00080,0x0,0xf80f0000,0x0,0x1f0000,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x3bf70003,0xe0000000,0x0,0x0,0x3e00,0x1f003e0,0x0,0x780000,0x180000,0x78000000,0x7c0,0xf80003c, + 0x3c00,0x0,0x1f01f0,0x780,0x0,0x0,0xf,0x80f80000,0x1c0,0x1c000000,0xe,0x38,0x1e000,0x0,0x7ffe1c00,0x0,0x0,0x380000f0,0x7800000, + 0x1c00,0xe000,0x7fc00,0xf000,0x3de000,0x1ef0000,0xf780000,0x7bc00003,0xde00001e,0xf00003c7,0x80007800,0x10078000,0x3c0000, + 0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x7e00ff00,0x3c01e003,0xc00f001e,0x7800f0,0x3c00780,0x1e003c00, + 0x7,0x800f00f0,0x3c0780,0x1e03c00,0xf01e000,0x7807c00f,0x801e0000,0xf803c0,0x3c000,0xf00000,0x780f0000,0x0,0x7,0xc0000000, + 0x0,0x3c0,0xf000,0x780f00,0x0,0x3c0000,0xf000007,0x80f00000,0x7ff,0xc0000000,0xf0,0x3c00,0x1e03c0,0x0,0x0,0x0,0x0,0x1e000007, + 0x800003c0,0x78000000,0xf00,0x1e0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c1e001f,0xfff03803,0x80001e00,0x1e0,0x780,0xf000,0xf9cf80, + 0x0,0x0,0x0,0x0,0xf000780,0xf00001e,0x0,0xf800000f,0xe780,0x3c00000,0x1e000000,0x1e00f,0x78078,0x7c0000,0x0,0x0,0x0,0x0,0x1e0, + 0x3f003c0,0x1ef0003,0xc000f00f,0x800001e0,0x1f003c0,0x1e00,0xf000,0x1e0007,0x80007800,0x780,0x3c01f000,0x7800001e,0xe00ef07f, + 0x801e01f0,0x1e00780,0x3c07c00,0x78078003,0xc03e0000,0xf000,0xf0003c0,0x78001e0f,0x1e01,0xf01f003c,0xf00000,0x3e0003c,0x7800, + 0x1e0,0x7c0f8,0x0,0x0,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000, + 0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,0x0,0x0,0x3c00000,0x0,0x8,0x40,0x0,0x7e0000,0x7c00000,0x1,0xf00f0000, + 0x0,0x3e0000,0x0,0x3f,0xfc0,0xfc3f0,0xfc3f0,0x0,0x0,0x0,0x70,0x39e70000,0x0,0x0,0x0,0x0,0xf003c0,0x0,0x0,0x180000,0xf8000000, + 0x3c0,0xf00003c,0x3c00,0x0,0x3c0078,0x7ff80,0x0,0x0,0x1e,0x3c0000,0x1c0,0x1c000000,0xe,0xf0,0x0,0x0,0x7ffe1c00,0x0,0x0,0x380000f0, + 0x7800000,0x1c00,0xe000,0x3c00,0x0,0x3de000,0x1ef0000,0xf780000,0x7bc00003,0xde00001e,0xf00003c7,0x8000f800,0x78000,0x3c0000, + 0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x1f00ff00,0x3c03e003,0xc01f001e,0xf800f0,0x7c00780,0x3e003c00, + 0xf,0x800f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7803c00f,0x1fffc0,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x307,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x781e003f,0xfff03803, + 0x80001e00,0x1e0,0xf80,0xf000,0x3dde00,0x0,0x0,0x0,0x0,0xf000f00,0x780001e,0x0,0x7800000f,0x1e780,0x3c00000,0x3e000000,0x3e00f, + 0x780f0,0x7c0000,0x0,0x0,0x0,0x0,0x1e0,0x7c001e0,0x3ef8003,0xc000f00f,0x1e0,0xf003c0,0x1e00,0xf000,0x1e0007,0x80007800,0x780, + 0x3c03e000,0x7800001e,0xf01ef07b,0xc01e01e0,0xf00780,0x3e07800,0x3c078003,0xe03c0000,0xf000,0xf0003c0,0x78001e0f,0x1e00,0xf01e003e, + 0x1f00000,0x3c0003c,0x7800,0x1e0,0x78078,0x0,0x0,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,0x0,0x0,0x3c00000,0x0,0x18,0xc0,0x0, + 0xe70000,0x7800000,0x1,0xe00f0000,0x0,0x3c0000,0x0,0x3f,0xfc0,0xfc1f0,0x1f83f0,0x0,0x0,0x0,0x70,0x39e70000,0x0,0x0,0x0,0x0, + 0xf807c0,0x0,0x0,0x180000,0xf0000000,0x3e0,0x1f00003c,0x3e00,0x0,0x70001c,0x3fff80,0x0,0x0,0x38,0xe0000,0x1c0,0x1c000078, + 0x1c,0x1fe0,0x0,0x0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800000,0x1c00,0xe000,0xe00,0x0,0x7df000,0x3ef8000,0x1f7c0000,0xfbe00007, + 0xdf00003c,0x780003c7,0x8000f000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf00f780, + 0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0xf,0xf80f0,0x3c0780,0x1e03c00,0xf01e000,0x7803e01f,0x1ffff8,0xf001e0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x0,0x0,0x0,0x1e0000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x780000,0x3c1e0000,0x781e003e,0x30703803,0x80001e00,0x1e0,0xf00,0x7800,0xff800,0x1e0000,0x0,0x0,0x0,0x1e000f00,0x780001e, + 0x0,0x7800000f,0x3c780,0x3c00000,0x3c000000,0x3c00f,0x780f0,0x3c0000,0x0,0x0,0x2000000,0x800000,0x1e0,0x78000e0,0x3c78003, + 0xc000f01e,0x1e0,0xf803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c07c000,0x7800001e,0x701cf07b,0xc01e01e0,0xf00780,0x1e07800, + 0x3c078001,0xe03c0000,0xf000,0xf0003c0,0x7c003e0f,0x1e00,0xf83e001e,0x1e00000,0x7c0003c,0x3c00,0x1e0,0xf807c,0x0,0x0,0x1fe0001, + 0xe1fc0000,0x7f00003,0xf8780007,0xf000003c,0x7f0,0x783f0,0x0,0x0,0x7800000,0x1e00000,0x3e0f8000,0xfc00007,0xf8000007,0xf00001fc, + 0xf,0xc0003fc0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x3c00000,0x0,0x0,0x3c00000,0x0,0x18,0xc0,0x0,0x1818000, + 0x7800000,0x1,0xe00f0000,0x0,0x7c0000,0x0,0x1f,0x80001f80,0x7c1f8,0x1f83e0,0x0,0x0,0x0,0x70,0x38c70007,0xf8000000,0x7f03, + 0xf0000000,0x0,0x780780,0x0,0x0,0xfe0000,0xf0000000,0x1e0,0x1e00003c,0x3f00,0x0,0xe07f0e,0x7fff80,0x0,0x0,0x70,0x70000,0x1c0, + 0x1c000078,0x3c,0x1fc0,0x0,0x0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800000,0x1c00,0xe000,0xe00,0x0,0x78f000,0x3c78000,0x1e3c0000, + 0xf1e00007,0x8f00003c,0x78000787,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00, + 0xf80f780,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0xf,0x1f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7801e01e,0x1ffffc, + 0xf007e0,0x3fc000,0x1fe0000,0xff00000,0x7f800003,0xfc00001f,0xe0000fc0,0xfc00007f,0xfe0,0x7f00,0x3f800,0x1fc000,0x0,0x0,0x0, + 0x1,0xf000001f,0x80000ff0,0x7f80,0x3fc00,0x1fe000,0xff0000,0x1f80000,0x1fc1e000,0x0,0x0,0x0,0x0,0x1e1fc0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000, + 0x781c007c,0x30003803,0x80001f00,0x1e0,0xf00,0x7800,0x7f000,0x1e0000,0x0,0x0,0x0,0x1e000f00,0x780001e,0x0,0x7800000f,0x3c780, + 0x3c00000,0x3c000000,0x3c00f,0x780f0,0x3c0000,0x0,0x0,0x1e000000,0xf00000,0x3e0,0xf0000e0,0x3c78003,0xc000f01e,0x1e0,0x7803c0, + 0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c0f8000,0x7800001e,0x701cf079,0xe01e01e0,0xf00780,0x1e07800,0x3c078001,0xe03c0000, + 0xf000,0xf0003c0,0x3c003c0f,0x3e00,0x787c001f,0x3e00000,0xf80003c,0x3c00,0x1e0,0x1f003e,0x0,0x0,0x1fffc001,0xe7ff0000,0x3ffe000f, + 0xfe78003f,0xfc001fff,0xfe001ffc,0xf0078ffc,0x1ffc00,0x7ff000,0x7800f80,0x1e0000f,0x7f1fc01e,0x3ff0001f,0xfe00079f,0xfc0007ff, + 0x3c003c7f,0xf001fff8,0x1fffff0,0x3c003c0,0xf0000f1e,0xf1f,0x7c1f0,0x1f00ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x3c00000,0x100000, + 0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1818000,0x7800000,0x1,0xe00f0000,0x1000000,0xf80000,0x40000002,0xf,0x80001f00,0x7e0f8,0x1f07c0, + 0x0,0x0,0x0,0x70,0x38c7003f,0xff000000,0xff8f,0xf8000100,0xffffe,0x7c0f80,0x0,0x0,0x3ffc000,0xf0000020,0x1001f0,0x3c00003c, + 0x1f80,0x0,0x1c3ffc7,0x7c0780,0x0,0x0,0xe3,0xff038000,0xe0,0x38000078,0x78,0x1ff0,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x380000f0, + 0x7800000,0x1c00,0xe000,0xe00,0xf000,0x78f000,0x3c78000,0x1e3c0000,0xf1e00007,0x8f00003c,0x78000787,0x8001e000,0x78000,0x3c0000, + 0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00, + 0x4000200f,0x3f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7801f03e,0x1ffffe,0xf01fe0,0x3fff800,0x1fffc000,0xfffe0007,0xfff0003f, + 0xff8001ff,0xfc003ff3,0xfe0003ff,0xe0007ff8,0x3ffc0,0x1ffe00,0xfff000,0x3ff80001,0xffc0000f,0xfe00007f,0xf000003f,0xf8003c7f, + 0xe0003ffc,0x1ffe0,0xfff00,0x7ff800,0x3ffc000,0x1f80000,0xfff1c03c,0x3c01e0,0x1e00f00,0xf007800,0x781f0001,0xf01e7ff0,0x7c0007c, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000, + 0x3c1e003f,0xfffff078,0x30003803,0x80000f00,0x1e0,0x1f00,0x7800,0x7f000,0x1e0000,0x0,0x0,0x0,0x3c000f00,0x780001e,0x0,0x7800000f, + 0x78780,0x3c00000,0x3c000000,0x7c00f,0x780f0,0x3c0007,0xe000003f,0x0,0xfe000000,0xfe0000,0x3c0,0x1f000070,0x7c7c003,0xc000f01e, + 0x1e0,0x7803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c1f0000,0x7800001e,0x783cf079,0xe01e03c0,0xf00780,0x1e0f000,0x3c078001, + 0xe03c0000,0xf000,0xf0003c0,0x3c003c07,0x81f03c00,0x7c7c000f,0x87c00000,0xf00003c,0x1e00,0x1e0,0x3e001f,0x0,0x0,0x3fffe001, + 0xefff8000,0x7fff001f,0xff78007f,0xfe001fff,0xfe003ffe,0xf0079ffe,0x1ffc00,0x7ff000,0x7801f00,0x1e0000f,0xffbfe01e,0x7ff8003f, + 0xff0007bf,0xfe000fff,0xbc003cff,0xf803fffc,0x1fffff0,0x3c003c0,0x78001e1e,0xf0f,0x800f80f0,0x1e00ff,0xffe0001e,0xf0,0x780, + 0x0,0x0,0x3c00000,0x380000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1008000,0x7800000,0x3,0xe00f0000,0x3800000,0xf00000,0xe0000007, + 0xf,0x80001f00,0x3e0f8,0x1e07c0,0x0,0x0,0x0,0x70,0x3807007f,0xff800000,0x1ffdf,0xfc000380,0xffffe,0x3e1f00,0x0,0x0,0xfffe000, + 0xf0000030,0x3800f8,0x7c00003c,0xfc0,0x0,0x18780c3,0xf00780,0x80100,0x0,0xc3,0xffc18000,0xf0,0x78000078,0xf0,0xf0,0x0,0x3c003c0, + 0xfffe1c00,0x0,0x0,0x380000f0,0x7800801,0x1c00,0xe000,0x1e00,0xf000,0xf8f800,0x7c7c000,0x3e3e0001,0xf1f0000f,0x8f80007c,0x7c000787, + 0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c078001,0xe03c000f, + 0x1e00078,0xf0003c0,0x78001e00,0xe000701f,0x3fc0f0,0x3c0780,0x1e03c00,0xf01e000,0x7800f87c,0x1e007f,0xf07e00,0x7fffc00,0x3fffe001, + 0xffff000f,0xfff8007f,0xffc003ff,0xfe007ff7,0xff0007ff,0xf000fffc,0x7ffe0,0x3fff00,0x1fff800,0x3ff80001,0xffc0000f,0xfe00007f, + 0xf00000ff,0xf8003cff,0xf0007ffe,0x3fff0,0x1fff80,0xfffc00,0x7ffe000,0x1f80001,0xfffb803c,0x3c01e0,0x1e00f00,0xf007800,0x780f0001, + 0xe01efff8,0x3c00078,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e003f,0xfffff078,0x30001c07,0xf80,0x1e0,0x1e00,0x3c00,0xff800,0x1e0000,0x0,0x0,0x0,0x3c001e00, + 0x3c0001e,0x0,0x7800001e,0x70780,0x3c00000,0x78000000,0x78007,0x800f00f0,0x3e0007,0xe000003f,0x3,0xfe000000,0xff8000,0x7c0, + 0x1e000070,0x783c003,0xc001f01e,0x1e0,0x7803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c3e0000,0x7800001e,0x3838f079, + 0xe01e03c0,0x780780,0x1e0f000,0x1e078001,0xe03c0000,0xf000,0xf0003c0,0x3c007c07,0x81f03c00,0x3ef80007,0x87800000,0x1f00003c, + 0x1e00,0x1e0,0x7c000f,0x80000000,0x0,0x3ffff001,0xffffc000,0xffff003f,0xff7800ff,0xff001fff,0xfe007ffe,0xf007bffe,0x1ffc00, + 0x7ff000,0x7803e00,0x1e0000f,0xffffe01e,0xfff8007f,0xff8007ff,0xff001fff,0xbc003dff,0xf807fffc,0x1fffff0,0x3c003c0,0x78001e0f, + 0x1e07,0xc01f00f0,0x1e00ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x7c00000,0x7c0000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1018000,0x7800000, + 0x3,0xc00f0000,0x7c00000,0x1f00001,0xf000000f,0x80000007,0xc0003e00,0x1e07c,0x3e0780,0x0,0x0,0x0,0x70,0x380700ff,0xff800000, + 0x3ffff,0xfe0007c0,0xffffe,0x1e1e00,0x0,0x780000,0x1fffe000,0xf0000078,0x7c0078,0x7800003c,0xff0,0x0,0x38e0003,0x80f00780, + 0x180300,0x0,0x1c3,0x81e1c000,0x7f,0xf0000078,0x1e0,0x38,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800c01,0x80001c00, + 0xe000,0x603e00,0xf000,0xf07800,0x783c000,0x3c1e0001,0xe0f0000f,0x7800078,0x3c000f87,0x8001e000,0x78000,0x3c0000,0x1e00000, + 0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f01,0xf000f81e, + 0x7bc0f0,0x3c0780,0x1e03c00,0xf01e000,0x78007878,0x1e001f,0xf0f800,0x7fffe00,0x3ffff001,0xffff800f,0xfffc007f,0xffe003ff, + 0xff007fff,0xff800fff,0xf001fffe,0xffff0,0x7fff80,0x3fffc00,0x3ff80001,0xffc0000f,0xfe00007f,0xf00001ff,0xfc003dff,0xf000ffff, + 0x7fff8,0x3fffc0,0x1fffe00,0xffff000,0x1f80003,0xffff803c,0x3c01e0,0x1e00f00,0xf007800,0x780f0001,0xe01ffffc,0x3c00078,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000, + 0x3c1e003f,0xfffff078,0x30001e0f,0x300780,0x1e0,0x1e00,0x3c00,0x3dde00,0x1e0000,0x0,0x0,0x0,0x78001e00,0x3c0001e,0x0,0xf800003e, + 0xf0780,0x3dfc000,0x783f8000,0xf8007,0xc01f00f0,0x3e0007,0xe000003f,0x1f,0xfc000000,0x7ff000,0xf80,0x3e007c70,0x783c003,0xc001e03c, + 0x1e0,0x3c03c0,0x1e00,0x3c000,0x1e0007,0x80007800,0x780,0x3c7c0000,0x7800001e,0x3878f078,0xf01e03c0,0x780780,0x1e0f000,0x1e078001, + 0xe03e0000,0xf000,0xf0003c0,0x1e007807,0x83f03c00,0x3ef00007,0xcf800000,0x3e00003c,0xf00,0x1e0,0xf80007,0xc0000000,0x0,0x3e01f801, + 0xfe07e001,0xf80f007e,0x7f801f8,0x1f801fff,0xfe00fc0f,0xf007f83f,0x1ffc00,0x7ff000,0x7807c00,0x1e0000f,0x87e1e01f,0xe0fc00fc, + 0xfc007f8,0x1f803f03,0xfc003df0,0x3807e03c,0x1fffff0,0x3c003c0,0x78003e0f,0x1e03,0xe03e00f8,0x3e00ff,0xffe0001e,0xf0,0x780, + 0x0,0x0,0x7800000,0xfe0000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1818000,0x7c00000,0x3,0xc00f0000,0xfe00000,0x3e00003,0xf800001f, + 0xc0000007,0xc0003e00,0x1e03c,0x3c0f80,0x0,0x0,0x0,0x70,0x380700fc,0x7800000,0x7c1fe,0x3e000fe0,0xffffe,0x1f3e00,0x0,0x780000, + 0x3f98e000,0xf000003c,0xfcf8007c,0xf800003c,0x3ffc,0x0,0x31c0001,0x80f00f80,0x380700,0x0,0x183,0x80e0c000,0x3f,0xe0000078, + 0x3c0,0x38,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x38000078,0xf000e01,0xc003ffe0,0x1fff00,0x7ffc00,0xf000,0xf07800,0x783c000,0x3c1e0001, + 0xe0f0000f,0x7800078,0x3c000f07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00, + 0x3c0f1e0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf801f01e,0xf3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78007cf8, + 0x1e000f,0x80f0f000,0x7c03f00,0x3e01f801,0xf00fc00f,0x807e007c,0x3f003e0,0x1f80707f,0x8f801f80,0xf003f03f,0x1f81f8,0xfc0fc0, + 0x7e07e00,0x3ff80001,0xffc0000f,0xfe00007f,0xf00003ff,0xfc003fc1,0xf801f81f,0x800fc0fc,0x7e07e0,0x3f03f00,0x1f81f800,0x1f80007, + 0xe07f003c,0x3c01e0,0x1e00f00,0xf007800,0x780f8003,0xe01fe07e,0x3e000f8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3f,0xfffff078,0x30000ffe,0x1f007c0,0x0,0x1e00, + 0x3c00,0xf9cf80,0x1e0000,0x0,0x0,0x0,0x78001e00,0x3c0001e,0x0,0xf00000fc,0x1e0780,0x3fff800,0x78ffe000,0xf0003,0xe03e00f0, + 0x3e0007,0xe000003f,0x7f,0xe01fffff,0xf00ffc00,0x1f80,0x3c01ff70,0x783c003,0xc007e03c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x1e0007, + 0x80007800,0x780,0x3cfc0000,0x7800001e,0x3c78f078,0xf01e03c0,0x780780,0x3e0f000,0x1e078003,0xc01f0000,0xf000,0xf0003c0,0x1e007807, + 0x83f83c00,0x1ff00003,0xcf000000,0x3e00003c,0xf00,0x1e0,0x0,0x0,0x0,0x20007801,0xfc03e003,0xe003007c,0x3f803e0,0x7c0003c, + 0xf807,0xf007e00f,0x3c00,0xf000,0x780f800,0x1e0000f,0x87e1f01f,0x803c00f8,0x7c007f0,0xf803e01,0xfc003f80,0x80f8004,0x3c000, + 0x3c003c0,0x3c003c0f,0x1e03,0xe03e0078,0x3c0000,0x7c0001e,0xf0,0x780,0x0,0x0,0x3ffff800,0x1ff0000,0x0,0x7800000,0x0,0x18, + 0xc0,0x0,0x1818000,0x3e00000,0x3,0xc00f0000,0x1ff00000,0x3e00007,0xfc00003f,0xe0000003,0xc0003c00,0xf03c,0x3c0f00,0x0,0x0, + 0x0,0x70,0x380701f0,0x800000,0x780fc,0x1e001ff0,0x7c,0xf3c00,0x0,0x780000,0x7e182000,0xf000001f,0xfff00ffc,0xffc0003c,0x3cfe, + 0x0,0x31c0001,0x80f01f80,0x780f00,0x0,0x183,0x80e0c000,0xf,0x80000078,0x780,0x38,0x0,0x3c003c0,0x7ffe1c00,0x0,0x0,0x38000078, + 0xf000f01,0xe003ffe0,0x1fff00,0x7ff800,0xf000,0xf07800,0x783c000,0x3c1e0001,0xe0f0000f,0x78000f8,0x3e000f07,0x8003c000,0x78000, + 0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f1e0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0, + 0x78000f00,0x7c03e01e,0x1e3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78003cf0,0x1e0007,0x80f1e000,0x4000f00,0x20007801,0x3c008, + 0x1e0040,0xf00200,0x780403f,0x7803e00,0x3007c00f,0x803e007c,0x1f003e0,0xf801f00,0x780000,0x3c00000,0x1e000000,0xf00007f0, + 0x3e003f00,0x7801f00f,0x800f807c,0x7c03e0,0x3e01f00,0x1f00f800,0x1f80007,0xc03e003c,0x3c01e0,0x1e00f00,0xf007800,0x78078003, + 0xc01fc03e,0x1e000f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x780000,0x0,0xf078007c,0x300007fc,0x7e00fe0,0x0,0x1e00,0x3c00,0x3e1c3e0,0x1e0000,0x0,0x0,0x0,0xf0001e00, + 0x3c0001e,0x1,0xf000fff8,0x1e0780,0x3fffe00,0x79fff000,0x1f0001,0xfffc00f0,0x7e0007,0xe000003f,0x3ff,0x801fffff,0xf003ff80, + 0x3f00,0x3c03fff0,0xf01e003,0xffffc03c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,0x1fffff,0x80007800,0x780,0x3df80000,0x7800001e, + 0x1c70f078,0x781e03c0,0x780780,0x3c0f000,0x1e078007,0xc01f8000,0xf000,0xf0003c0,0x1e007807,0x83f83c00,0xfe00003,0xff000000, + 0x7c00003c,0x780,0x1e0,0x0,0x0,0x0,0x7c01,0xf801f007,0xc00100f8,0x1f803c0,0x3c0003c,0x1f003,0xf007c00f,0x80003c00,0xf000, + 0x783f000,0x1e0000f,0x3c0f01f,0x3e01f0,0x3e007e0,0x7c07c00,0xfc003f00,0xf0000,0x3c000,0x3c003c0,0x3c003c0f,0x1e01,0xf07c007c, + 0x7c0000,0xfc0001e,0xf0,0x780,0x0,0x0,0x3ffff000,0x3838000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0xff0000,0x3f00000,0x3,0xc00fff00, + 0x38380000,0x7c0000e,0xe000070,0x70000001,0xe0003c00,0xf01e,0x780e00,0x0,0x0,0x0,0x0,0x1e0,0x0,0x780f8,0xf003838,0xfc,0xffc00, + 0x0,0x780000,0x7c180000,0xf000000f,0xffe00fff,0xffc0003c,0x783f,0x80000000,0x6380000,0xc0f83f80,0xf81f00,0x0,0x303,0x80e06000, + 0x0,0x78,0xf00,0x78,0x0,0x3c003c0,0x7ffe1c00,0x0,0x0,0x3800003c,0x3e000f81,0xf003ffe0,0x1fff00,0x1fc000,0xf000,0x1e03c00, + 0xf01e000,0x780f0003,0xc078001e,0x3c000f0,0x1e000f07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000, + 0x3c000001,0xe0001e00,0x3c0f0f0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x3e07c01e,0x1e3c0f0,0x3c0780,0x1e03c00, + 0xf01e000,0x78003ff0,0x1e0007,0x80f1e000,0xf80,0x7c00,0x3e000,0x1f0000,0xf80000,0x7c0001e,0x3c07c00,0x10078007,0x803c003c, + 0x1e001e0,0xf000f00,0x780000,0x3c00000,0x1e000000,0xf00007c0,0x1e003e00,0x7c03e007,0xc01f003e,0xf801f0,0x7c00f80,0x3e007c00, + 0xf,0x801f003c,0x3c01e0,0x1e00f00,0xf007800,0x7807c007,0xc01f801f,0x1f001f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x0,0xe078003c,0x300001f0,0x3f801ff0,0x0, + 0x3c00,0x1e00,0x3c1c1e0,0x1e0000,0x0,0x0,0x0,0xf0001e0f,0x3c0001e,0x3,0xe000fff0,0x3c0780,0x3ffff00,0x7bfff800,0x1e0000,0x7ff00078, + 0x7e0007,0xe000003f,0x1ffc,0x1fffff,0xf0007ff0,0x7e00,0x3c07c3f0,0xf01e003,0xffff003c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000, + 0x1fffff,0x80007800,0x780,0x3ffc0000,0x7800001e,0x1ef0f078,0x781e03c0,0x780780,0x7c0f000,0x1e07801f,0x800ff000,0xf000,0xf0003c0, + 0xf00f807,0x83b83c00,0xfc00001,0xfe000000,0xf800003c,0x780,0x1e0,0x0,0x0,0x0,0x3c01,0xf000f007,0xc00000f0,0xf80780,0x3c0003c, + 0x1e001,0xf007c007,0x80003c00,0xf000,0x787e000,0x1e0000f,0x3c0f01f,0x1e01e0,0x1e007c0,0x3c07800,0x7c003f00,0xf0000,0x3c000, + 0x3c003c0,0x3e007c07,0x80003c00,0xf8f8003c,0x780000,0xf80001e,0xf0,0x780,0x0,0x0,0x7ffff000,0x601c000,0x3,0xffff0000,0x0, + 0xfff,0xf8007fff,0xc0000000,0x7e003c,0x1fe0000,0xc0003,0xc00fff00,0x601c0000,0xf800018,0x70000c0,0x38000001,0xe0007800,0x701e, + 0x701e00,0x0,0x0,0x0,0x0,0x1e0,0x6,0x700f8,0xf00601c,0xf8,0x7f800,0x0,0x780000,0xf8180000,0xf000000f,0x87c00fff,0xffc0003c, + 0xf01f,0xc0000000,0x6380000,0xc07ff780,0x1f03e03,0xfffffe00,0x303,0x81c06000,0x0,0x1ffff,0xfe001e00,0x180f8,0x0,0x3c003c0, + 0x3ffe1c00,0x3f00000,0x0,0x3800003f,0xfe0007c0,0xf8000000,0x18000000,0xc0000006,0x1f000,0x1e03c00,0xf01e000,0x780f0003,0xc078001e, + 0x3c000f0,0x1e001f07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,0x3c0f0f0, + 0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x1f0f801e,0x3c3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78001fe0,0x1e0007, + 0x80f1e000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c07c00,0xf0007,0x8078003c,0x3c001e0,0x1e000f00,0x780000,0x3c00000, + 0x1e000000,0xf0000f80,0x1f003e00,0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0xf,0x3f003c,0x3c01e0,0x1e00f00,0xf007800, + 0x7803c007,0x801f000f,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe078003f,0xb0000000,0xfc003cf0,0x0,0x3c00,0x1e00,0x101c040,0x1e0000,0x0,0x0,0x1, + 0xe0001e1f,0x83c0001e,0x7,0xe000fff0,0x3c0780,0x3c03f80,0x7fc0fc00,0x1e0000,0xfff80078,0xfe0007,0xe000003f,0x7fe0,0x1fffff, + 0xf0000ffc,0xfc00,0x780f81f0,0xf01e003,0xffff003c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,0x1fffff,0x80007800,0x780,0x3ffc0000, + 0x7800001e,0x1ef0f078,0x3c1e03c0,0x780780,0x1fc0f000,0x1e07ffff,0x7ff00,0xf000,0xf0003c0,0xf00f007,0xc3b87c00,0x7c00001,0xfe000000, + 0xf800003c,0x3c0,0x1e0,0x0,0x0,0x0,0x3c01,0xf000f007,0x800000f0,0xf80780,0x1e0003c,0x1e001,0xf0078007,0x80003c00,0xf000,0x78fc000, + 0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,0x3c07800,0x7c003e00,0xf0000,0x3c000,0x3c003c0,0x1e007807,0x80003c00,0x7df0003c,0x780000, + 0x1f00001e,0xf0,0x780,0x0,0x0,0x7800000,0xe7ce000,0x3,0xffff0000,0x0,0xfff,0xf8007fff,0xc0000000,0x1f0,0xffe000,0x1c0003, + 0xc00fff00,0xe7ce0000,0xf800039,0xf38001cf,0x9c000000,0xe0007800,0x780e,0x701c00,0x0,0x0,0x0,0x0,0x1e0,0x7,0xf0078,0xf00e7ce, + 0x1f0,0x7f800,0x0,0x780000,0xf0180000,0xf000000e,0x1c0001f,0xe000003c,0xf007,0xe0000000,0x6380000,0xc03fe780,0x3e07c03,0xfffffe00, + 0x303,0xffc06000,0x0,0x1ffff,0xfe003ffe,0x1fff0,0x0,0x3c003c0,0x1ffe1c00,0x3f00000,0x7,0xffc0001f,0xfc0003e0,0x7c000001,0xfc00000f, + 0xe000007f,0x1e000,0x1e03c00,0xf01e000,0x780f0003,0xc078001e,0x3c000f0,0x1e001e07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0, + 0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf9f001e, + 0x783c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78001fe0,0x1e0007,0x80f1e000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c07800, + 0xf0003,0xc078001e,0x3c000f0,0x1e000780,0x780000,0x3c00000,0x1e000000,0xf0000f00,0xf003c00,0x3c03c003,0xc01e001e,0xf000f0, + 0x7800780,0x3c003c00,0xf,0x7f003c,0x3c01e0,0x1e00f00,0xf007800,0x7803c007,0x801f000f,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe070001f,0xf8000007, + 0xf0007cf8,0x7800000,0x3c00,0x1e00,0x1c000,0x1e0000,0x0,0x0,0x1,0xe0001e1f,0x83c0001e,0xf,0xc000fff8,0x780780,0x2000f80,0x7f803e00, + 0x3e0003,0xfffe007c,0x1fe0000,0x0,0x3ff00,0x0,0x1ff,0x8001f000,0x780f00f0,0x1f00f003,0xffffc03c,0x1e0,0x3c03ff,0xffc01fff, + 0xfe03c00f,0xf81fffff,0x80007800,0x780,0x3ffe0000,0x7800001e,0xee0f078,0x3c1e03c0,0x7807ff,0xff80f000,0x1e07fffe,0x3ffe0, + 0xf000,0xf0003c0,0xf00f003,0xc7bc7800,0xfc00000,0xfc000001,0xf000003c,0x3c0,0x1e0,0x0,0x0,0x0,0x3c01,0xe000f80f,0x800001e0, + 0xf80f00,0x1e0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x79f8000,0x1e0000f,0x3c0f01e,0x1e03c0,0x1f00780,0x3e0f000,0x7c003e00, + 0xf0000,0x3c000,0x3c003c0,0x1e007807,0x81e03c00,0x7df0003e,0xf80000,0x3e00003e,0xf0,0x7c0,0xfc000,0x80000000,0x7800000,0x1e7cf000, + 0x3,0xffff0000,0x0,0x18,0xc0,0x0,0xf80,0x7ffc00,0x380003,0xc00fff01,0xe7cf0000,0x1f000079,0xf3c003cf,0x9e000000,0xe0007000, + 0x380e,0xe01c00,0x0,0x0,0x0,0x0,0x1e0,0x3,0x800f0078,0xf01e7cf,0x3e0,0x3f000,0x0,0x780000,0xf018001f,0xfff8001e,0x1e0000f, + 0xc000003c,0xf003,0xe0000000,0x6380000,0xc00fc780,0x7c0f803,0xfffffe00,0x303,0xfe006000,0x0,0x1ffff,0xfe003ffe,0x1ffe0,0x0, + 0x3c003c0,0xffe1c00,0x3f00000,0x7,0xffc00007,0xf00001f0,0x3e00001f,0xfc0000ff,0xe00007ff,0x3e000,0x3e01e00,0x1f00f000,0xf8078007, + 0xc03c003e,0x1e001e0,0xf001e07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8, + 0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x7fe001e,0xf03c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000fc0, + 0x1e0007,0x80f1f000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c0f800,0x1e0003,0xc0f0001e,0x78000f0,0x3c000780,0x780000, + 0x3c00000,0x1e000000,0xf0000f00,0xf003c00,0x3c078003,0xe03c001f,0x1e000f8,0xf0007c0,0x78003e00,0x1e,0xf7803c,0x3c01e0,0x1e00f00, + 0xf007800,0x7803e00f,0x801e000f,0x80f803e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe0f0000f,0xff00001f,0x8000f87c,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80, + 0x0,0x0,0x3,0xc0001e1f,0x83c0001e,0x1f,0x800000fe,0xf00780,0x7c0,0x7f001e00,0x3c0007,0xe03f003f,0x3fe0000,0x0,0x3fc00,0x0, + 0x7f,0x8001e000,0x781f00f0,0x1e00f003,0xc007e03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,0xf81e0007,0x80007800,0x780,0x3f9f0000,0x7800001e, + 0xfe0f078,0x3c1e03c0,0x7807ff,0xff00f000,0x1e07fff8,0xfff8,0xf000,0xf0003c0,0xf81f003,0xc7bc7800,0xfe00000,0x78000003,0xe000003c, + 0x1e0,0x1e0,0x0,0x0,0x0,0x1fffc01,0xe000780f,0x1e0,0x780f00,0x1e0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7bf0000,0x1e0000f, + 0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xf8000,0x3c000,0x3c003c0,0x1f00f807,0x81f03c00,0x3fe0001e,0xf00000,0x7c00007c, + 0xf0,0x3e0,0x3ff801,0x80000000,0x7800000,0x3cfcf800,0x3,0xffff0000,0x0,0x18,0xc0,0x0,0x7c00,0x1fff00,0x700003,0xc00f0003, + 0xcfcf8000,0x3e0000f3,0xf3e0079f,0x9f000000,0xf000,0x1000,0x0,0x0,0x0,0x0,0x0,0x1f0,0x1,0xc00f0078,0xf03cfcf,0x800007c0,0x1e000, + 0x0,0x780001,0xe018001f,0xfff8001c,0xe00007,0x8000003c,0xf001,0xf0000000,0x6380000,0xc0000000,0xf81f003,0xfffffe00,0x303, + 0x87006000,0x0,0x1ffff,0xfe003ffe,0x7f00,0x0,0x3c003c0,0x3fe1c00,0x3f00000,0x7,0xffc00000,0xf8,0x1f0001ff,0xf0000fff,0x80007ffc, + 0xfc000,0x3c01e00,0x1e00f000,0xf0078007,0x803c003c,0x1e001e0,0xf001e07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000, + 0x7800000,0x3c000001,0xe000fff8,0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x3fc001e,0x1e03c0f0,0x3c0780, + 0x1e03c00,0xf01e000,0x78000780,0x1e0007,0x80f0fc00,0x3fff80,0x1fffc00,0xfffe000,0x7fff0003,0xfff8001f,0xffc0001e,0x3c0f000, + 0x1e0003,0xc0f0001e,0x78000f0,0x3c000780,0x780000,0x3c00000,0x1e000000,0xf0001e00,0xf803c00,0x3c078001,0xe03c000f,0x1e00078, + 0xf0003c0,0x78001e07,0xfffffe1e,0x1e7803c,0x3c01e0,0x1e00f00,0xf007800,0x7801e00f,0x1e0007,0x807803c0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3,0xc0f00007, + 0xffc0007e,0xf03e,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,0x0,0x0,0x3,0xc0001e1f,0x83c0001e,0x3f,0x3e,0xf00780,0x3c0,0x7e001e00, + 0x7c000f,0x800f001f,0xffde0000,0x0,0x3e000,0x0,0xf,0x8003e000,0x781e0070,0x1e00f003,0xc001f03c,0x1e0,0x3c03c0,0x1e00,0x3c00f, + 0xf81e0007,0x80007800,0x780,0x3f1f0000,0x7800001e,0x7c0f078,0x1e1e03c0,0x7807ff,0xfc00f000,0x1e07fffe,0xffc,0xf000,0xf0003c0, + 0x781e003,0xc71c7800,0x1ff00000,0x78000003,0xe000003c,0x1e0,0x1e0,0x0,0x0,0x0,0xffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c, + 0x3c000,0xf0078007,0x80003c00,0xf000,0x7ff0000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x7f000,0x3c000, + 0x3c003c0,0xf00f007,0xc1f07c00,0x1fc0001f,0x1f00000,0xfc000ff8,0xf0,0x1ff,0xfffe07,0x80000000,0x7800000,0x7ffcfc00,0x0,0xf000000, + 0x0,0x18,0xc0,0x0,0x3e000,0x1ff80,0xe00003,0xc00f0007,0xffcfc000,0x3e0001ff,0xf3f00fff,0x9f800000,0x6000,0x0,0x0,0x7c000, + 0x0,0x0,0x0,0xfe,0x0,0xe00f007f,0xff07ffcf,0xc0000fc0,0x1e000,0x0,0x780001,0xe018001f,0xfff8001c,0xe00007,0x80000000,0xf800, + 0xf0000000,0x6380000,0xc0000000,0x1f03c000,0x1e00,0x303,0x83806000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xfe1c00,0x3f00000,0x0, + 0x0,0x3c,0xf801fff,0xfff8,0x7ffc0,0x1f8000,0x3c01e00,0x1e00f000,0xf0078007,0x803c003c,0x1e001e0,0xf003c07,0x8003c000,0x78000, + 0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f03c,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0, + 0x78000f00,0x1f8001e,0x1e03c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e000f,0x80f0ff00,0x1ffff80,0xffffc00,0x7fffe003, + 0xffff001f,0xfff800ff,0xffc007ff,0xffc0f000,0x1fffff,0xc0fffffe,0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00, + 0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,0xfffffe1e,0x3c7803c,0x3c01e0,0x1e00f00,0xf007800,0x7801f01f, + 0x1e0007,0x807c07c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x780000,0x3,0xc0f00000,0xfff003f0,0x1f00f03e,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,0x0,0x7ff80000,0x3, + 0xc0001e0f,0x3c0001e,0x7e,0x1f,0x1e00780,0x3e0,0x7e000f00,0x78000f,0x7800f,0xff9e0000,0x0,0x3fc00,0x0,0x7f,0x8003c000,0x781e0070, + 0x3e00f803,0xc000f03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,0xf81e0007,0x80007800,0x780,0x3e0f8000,0x7800001e,0x7c0f078,0x1e1e03c0, + 0x7807ff,0xf000f000,0x1e07807f,0xfe,0xf000,0xf0003c0,0x781e003,0xc71c7800,0x3ef00000,0x78000007,0xc000003c,0x1e0,0x1e0,0x0, + 0x0,0x0,0x1ffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7ff0000,0x1e0000f,0x3c0f01e, + 0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x7ff80,0x3c000,0x3c003c0,0xf00f003,0xc1f07800,0x1fc0000f,0x1e00000,0xf8000ff0,0xf0, + 0xff,0xffffff,0x80000000,0x3fffc000,0xfff9fe00,0x0,0xf000000,0x0,0x18,0xc0,0x0,0x1f0000,0x1fc0,0x1c00003,0xc00f000f,0xff9fe000, + 0x7c0003ff,0xe7f81fff,0x3fc00000,0x0,0x0,0x0,0xfe000,0x1ffffc0f,0xfffffc00,0x0,0xff,0xf0000000,0x700f007f,0xff0fff9f,0xe0000f80, + 0x1e000,0x0,0x780001,0xe018001f,0xfff8001c,0xe00fff,0xffc00000,0xf800,0xf0000000,0x6380000,0xc0ffff80,0x3e078000,0x1e00,0x7ff80303, + 0x83c06000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x3f00000,0x0,0x7f,0xff00001e,0x7c1fff0,0xfff80,0x7ffc00,0x3f0000,0x7c01f00, + 0x3e00f801,0xf007c00f,0x803e007c,0x1f003e0,0xf803c07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001, + 0xe0001e00,0x3c0f03c,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x1f8001e,0x3c03c0f0,0x3c0780,0x1e03c00,0xf01e000, + 0x78000780,0x1e001f,0xf07f80,0x3ffff80,0x1ffffc00,0xffffe007,0xffff003f,0xfff801ff,0xffc03fff,0xffc0f000,0x1fffff,0xc0fffffe, + 0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07, + 0xfffffe1e,0x787803c,0x3c01e0,0x1e00f00,0xf007800,0x7800f01e,0x1e0007,0x803c0780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x3ff80fc0,0x7fc1e01f, + 0x7800000,0x3c00,0x1e00,0x0,0x7fffff80,0x0,0x7ff80000,0x7,0x80001e00,0x3c0001e,0xfc,0xf,0x1e00780,0x1e0,0x7c000f00,0x78000f, + 0x78007,0xff1e0000,0x0,0x3ff00,0x0,0x1ff,0x8003c000,0x781e0070,0x3c007803,0xc000f03c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x781e0007, + 0x80007800,0x780,0x3c07c000,0x7800001e,0x7c0f078,0xf1e03c0,0x780780,0xf000,0x1e07801f,0x3e,0xf000,0xf0003c0,0x781e003,0xcf1c7800, + 0x3cf80000,0x7800000f,0x8000003c,0xf0,0x1e0,0x0,0x0,0x0,0x3ffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007, + 0x80003c00,0xf000,0x7ff8000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x3fff0,0x3c000,0x3c003c0,0xf81f003, + 0xc3b87800,0xf80000f,0x1e00001,0xf0000ff0,0xf0,0xff,0xf03fff,0x80000000,0x3fff8001,0xfff1ff00,0x0,0xf000000,0x0,0x18,0xc0, + 0x0,0x380000,0x7c0,0x3c00003,0xc00f001f,0xff1ff000,0xf80007ff,0xc7fc3ffe,0x3fe00000,0x0,0x0,0x0,0x1ff000,0x7ffffe1f,0xffffff00, + 0x0,0x7f,0xfe000000,0x780f007f,0xff1fff1f,0xf0001f00,0x1e000,0x0,0x780001,0xe0180000,0xf000001c,0xe00fff,0xffc00000,0x7c00, + 0xf0000000,0x31c0001,0x80ffff80,0x3e078000,0x1e00,0x7ff80183,0x81c0c000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x3f00000, + 0x0,0x7f,0xff00001e,0x7c7ff03,0xc03ff8fe,0x1ffc0f0,0x7e0000,0x7800f00,0x3c007801,0xe003c00f,0x1e0078,0xf003c0,0x7803c07,0x8003c000, + 0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f01e,0x3c078000,0xf03c0007,0x81e0003c, + 0xf0001e0,0x78000f00,0x3fc001e,0x7803c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e007f,0xf03fe0,0x7ffff80,0x3ffffc01, + 0xffffe00f,0xffff007f,0xfff803ff,0xffc07fff,0xffc0f000,0x1fffff,0xc0fffffe,0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000, + 0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,0xfffffe1e,0x707803c,0x3c01e0,0x1e00f00,0xf007800, + 0x7800f01e,0x1e0007,0x803c0780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x30f81f00,0xffe1e00f,0x87800000,0x3c00,0x1e00,0x0,0x1e0000,0x0,0x7ff80000, + 0x7,0x80001e00,0x3c0001e,0x1f8,0x7,0x83c00780,0x1e0,0x7c000f00,0xf8001e,0x3c001,0xfc1e0000,0x0,0x7fe0,0x0,0xffc,0x3c000,0x781e0070, + 0x3ffff803,0xc000783c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x781e0007,0x80007800,0x780,0x3c07c000,0x7800001e,0x380f078,0xf1e03c0, + 0x780780,0xf000,0x1e07800f,0x8000001e,0xf000,0xf0003c0,0x3c3c003,0xcf1e7800,0x7c780000,0x7800000f,0x8000003c,0xf0,0x1e0,0x0, + 0x0,0x0,0x7f003c01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7f7c000,0x1e0000f,0x3c0f01e, + 0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xfff8,0x3c000,0x3c003c0,0x781e003,0xc3b87800,0x1fc00007,0x83e00003,0xe0000ff8,0xf0, + 0x1ff,0xc007fe,0x0,0x7fff8001,0xffe3ff00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x3c0,0x7800003,0xc00f001f,0xfe3ff000,0xf80007ff, + 0x8ffc3ffc,0x7fe00000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x1f,0xff000000,0x3c0f007f,0xff1ffe3f,0xf0003e00,0x1e000,0x0,0x780001, + 0xe0180000,0xf000001e,0x1e00fff,0xffc00000,0x3f00,0xf0000000,0x31c0001,0x80ffff80,0x1f03c000,0x1e00,0x7ff80183,0x81c0c000, + 0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x7f,0xff00003c,0xf87f007,0xc03f83ff,0x81fc01f0,0x7c0000,0x7ffff00,0x3ffff801, + 0xffffc00f,0xfffe007f,0xfff003ff,0xff807fff,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001, + 0xe0001e00,0x3c0f01e,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x7fe001e,0xf003c0f0,0x3c0780,0x1e03c00,0xf01e000, + 0x78000780,0x1ffffe,0xf00ff0,0xfe00780,0x7f003c03,0xf801e01f,0xc00f00fe,0x7807f0,0x3c0ffff,0xffc0f000,0x1fffff,0xc0fffffe, + 0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00, + 0x1e,0xf07803c,0x3c01e0,0x1e00f00,0xf007800,0x7800783e,0x1e0007,0x801e0f80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x307c0801,0xe1f1e00f,0x87000000, + 0x3c00,0x1e00,0x0,0x1e0000,0x0,0x7ff80000,0xf,0x1e00,0x3c0001e,0x3f0,0x7,0x83fffffc,0x1e0,0x7c000f00,0xf0001e,0x3c000,0x3e0000, + 0x0,0x1ffc,0x1fffff,0xf0007ff0,0x3c000,0x781e0070,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x3c000,0x781e0007,0x80007800, + 0x780,0x3c03e000,0x7800001e,0xf078,0x79e03c0,0x780780,0xf000,0x1e078007,0x8000000f,0xf000,0xf0003c0,0x3c3c001,0xee0ef000, + 0xf87c0000,0x7800001f,0x3c,0x78,0x1e0,0x0,0x0,0x0,0x7c003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,0x80003c00, + 0xf000,0x7e3e000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x1ffc,0x3c000,0x3c003c0,0x781e003,0xe3b8f800, + 0x1fc00007,0x83c00007,0xc00000fc,0xf0,0x3e0,0x8001f8,0x0,0x7800000,0xffc7fe00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x1e0, + 0xf000003,0xc00f000f,0xfc7fe001,0xf00003ff,0x1ff81ff8,0xffc00000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x3,0xff800000,0x1e0f0078, + 0xffc7f,0xe0007c00,0x1e000,0x0,0x780001,0xe0180000,0xf000000e,0x1c00007,0x80000000,0x1f81,0xe0000000,0x38e0003,0x80000000, + 0xf81f000,0x1e00,0x7ff801c3,0x80e1c000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0xf8,0x1f070007,0xc03803ff,0xc1c001f0, + 0xf80000,0xfffff00,0x7ffff803,0xffffc01f,0xfffe00ff,0xfff007ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000, + 0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f00f,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf9f001e,0xf003c0f0, + 0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1ffffc,0xf003f8,0xf800780,0x7c003c03,0xe001e01f,0xf00f8,0x7807c0,0x3c0fc1e,0xf000, + 0x1e0000,0xf00000,0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078, + 0xf0003c0,0x78001e00,0x1e,0x1e07803c,0x3c01e0,0x1e00f00,0xf007800,0x7800783c,0x1e0007,0x801e0f00,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xffff8000,0x303c0001, + 0xc071e007,0xcf000000,0x3c00,0x1e00,0x0,0x1e0000,0x0,0x0,0xf,0xf00,0x780001e,0x7e0,0x7,0x83fffffc,0x1e0,0x7c000f00,0x1f0001e, + 0x3c000,0x3c0000,0x0,0x3ff,0x801fffff,0xf003ff80,0x3c000,0x781e0070,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x1e000,0x781e0007, + 0x80007800,0x780,0x3c01f000,0x7800001e,0xf078,0x79e03c0,0xf00780,0xf000,0x3e078007,0xc000000f,0xf000,0xf0003c0,0x3c3c001, + 0xee0ef000,0xf03e0000,0x7800003e,0x3c,0x78,0x1e0,0x0,0x0,0x0,0xf8003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007, + 0x80003c00,0xf000,0x7c3e000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xfc,0x3c000,0x3c003c0,0x3c3e001,0xe7b8f000, + 0x3fe00007,0xc7c0000f,0xc000003e,0xf0,0x7c0,0x0,0x0,0x7c00000,0x7fcffc00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x1e0,0x1e000003, + 0xc00f0007,0xfcffc003,0xe00001ff,0x3ff00ff9,0xff800000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x0,0x1f800000,0xf0f0078,0x7fcff, + 0xc000fc00,0x1e000,0x0,0x780001,0xe0180000,0xf000000f,0x87c00007,0x80000000,0xfe3,0xe0000000,0x18780c3,0x0,0x7c0f800,0x1e00, + 0xc3,0x80e18000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0x1f0,0x3e00000f,0xc0000303,0xe00003f0,0xf00000,0xfffff80, + 0x7ffffc03,0xffffe01f,0xffff00ff,0xfff807ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000, + 0x3c000001,0xe0001e00,0x780f00f,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,0x1f0f801f,0xe00780f0,0x3c0780,0x1e03c00, + 0xf01e000,0x78000780,0x1ffff8,0xf000f8,0x1f000780,0xf8003c07,0xc001e03e,0xf01f0,0x780f80,0x3c1f01e,0xf000,0x1e0000,0xf00000, + 0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00, + 0x1e,0x3c07803c,0x3c01e0,0x1e00f00,0xf007800,0x78007c7c,0x1e0007,0x801f1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x81c00000,0x303c0003,0x8039e003,0xef000000, + 0x3c00,0x1e00,0x0,0x1e0000,0x0,0x0,0x1e,0xf00,0x780001e,0xfc0,0x7,0x83fffffc,0x1e0,0x3c000f00,0x1e0001e,0x3c000,0x3c0000, + 0x0,0x7f,0xe01fffff,0xf00ffc00,0x3c000,0x781f00f0,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x1e000,0x781e0007,0x80007800, + 0x780,0x3c01f000,0x7800001e,0xf078,0x7de01e0,0xf00780,0x7800,0x3c078003,0xc000000f,0xf000,0xf0003c0,0x3e7c001,0xee0ef001, + 0xf01e0000,0x7800003e,0x3c,0x3c,0x1e0,0x0,0x0,0x0,0xf0003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,0x80003c00, + 0xf000,0x781f000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x3e,0x3c000,0x3c003c0,0x3c3c001,0xe71cf000,0x7df00003, + 0xc780000f,0x8000003e,0xf0,0x780,0x0,0x0,0x3c00000,0x3fcff800,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x1f00fc,0x1e0,0x1e000001, + 0xe00f0003,0xfcff8003,0xe00000ff,0x3fe007f9,0xff000000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x0,0x7c00000,0xf0f0078,0x3fcff,0x8000f800, + 0x1e000,0x0,0x780001,0xe0180000,0xf000001f,0xffe00007,0x8000003c,0x7ff,0xc0000000,0x1c3ffc7,0x0,0x3e07c00,0x1e00,0xe3,0x80738000, + 0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0x3e0,0x7c00001d,0xc0000001,0xe0000770,0x1f00000,0xfffff80,0x7ffffc03, + 0xffffe01f,0xffff00ff,0xfff807ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001, + 0xe0001e00,0x780f00f,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0x3e07c01f,0xc00780f0,0x3c0780,0x1e03c00,0xf01e000, + 0x78000780,0x1fffc0,0xf0007c,0x1e000780,0xf0003c07,0x8001e03c,0xf01e0,0x780f00,0x3c1e01e,0xf000,0x1e0000,0xf00000,0x7800000, + 0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,0x1e,0x7807803c, + 0x3c01e0,0x1e00f00,0xf007800,0x78003c78,0x1e0007,0x800f1e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x83c00000,0x303c0003,0x8039e001,0xee000000,0x1e00,0x3c00, + 0x0,0x1e0000,0x0,0x0,0x1e,0xf00,0x780001e,0x1f80,0x7,0x83fffffc,0x1e0,0x3c000f00,0x1e0001e,0x3c000,0x3c0000,0x0,0x1f,0xfc1fffff, + 0xf07ff000,0x0,0x780f00f0,0x78003c03,0xc000781e,0x1e0,0xf803c0,0x1e00,0x1e000,0x781e0007,0x80007800,0x780,0x3c00f800,0x7800001e, + 0xf078,0x3de01e0,0xf00780,0x7800,0x3c078003,0xe000000f,0xf000,0xf0003c0,0x1e78001,0xfe0ff003,0xe01f0000,0x7800007c,0x3c,0x3c, + 0x1e0,0x0,0x0,0x0,0xf0007c01,0xe000f80f,0x800001e0,0xf80f00,0x3c,0x1e001,0xf0078007,0x80003c00,0xf000,0x780f800,0x1e0000f, + 0x3c0f01e,0x1e03c0,0x1f00780,0x3e0f000,0x7c003c00,0x1e,0x3c000,0x3c003c0,0x3c3c001,0xe71cf000,0xf8f80003,0xe780001f,0x1e, + 0xf0,0x780,0x0,0x0,0x3c00000,0x1ffff000,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x3bc1de,0x1e0,0xf000001,0xe00f0001,0xffff0007,0xc000007f, + 0xffc003ff,0xfe000000,0x0,0x0,0x0,0xfe000,0x0,0x0,0x0,0x0,0x3c00000,0x1e0f0078,0x1ffff,0x1f000,0x1e000,0x0,0x780000,0xf0180000, + 0xf000001f,0xfff00007,0x8000003c,0x1ff,0x80000000,0xe0ff0e,0x0,0x1f03e00,0x1e00,0x70,0x70000,0x0,0x78,0x0,0x0,0x0,0x3c003c0, + 0xe1c00,0x0,0x0,0x0,0x7c0,0xf8000019,0xc0000000,0xe0000670,0x1e00000,0xf000780,0x78003c03,0xc001e01e,0xf00f0,0x780780,0x3c0f807, + 0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf80f007,0xbc03c001,0xe01e000f, + 0xf00078,0x78003c0,0x3c001e00,0x7c03e00f,0x800780f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80, + 0xf0007c07,0x8003e03c,0x1f01e0,0xf80f00,0x7c1e01e,0xf800,0x1e0000,0xf00000,0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000, + 0xf0001e00,0x7803c00,0x3c078003,0xe03c001f,0x1e000f8,0xf0007c0,0x78003e00,0x1f8001f,0xf00f803c,0x3c01e0,0x1e00f00,0xf007800, + 0x78003e78,0x1e000f,0x800f9e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf,0x3c00000,0x303c0003,0x8039f001,0xfe000000,0x1e00,0x3c00,0x0,0x1e0000,0x0,0x0,0x3c,0xf00, + 0x780001e,0x3f00,0x7,0x80000780,0x3e0,0x3e000f00,0x3c0001e,0x3c000,0x7c0000,0x0,0x3,0xfe000000,0xff8000,0x0,0x3c0f81f0,0xf0001e03, + 0xc000780f,0x1e0,0xf003c0,0x1e00,0xf000,0x781e0007,0x80007800,0x780,0x3c007c00,0x7800001e,0xf078,0x3de01e0,0xf00780,0x7800, + 0x3c078001,0xe000000f,0xf000,0xf0003c0,0x1e78001,0xfc07f003,0xe00f0000,0x78000078,0x3c,0x1e,0x1e0,0x0,0x0,0x0,0xf0007c01, + 0xf000f007,0x800000f0,0xf80780,0x3c,0x1e001,0xf0078007,0x80003c00,0xf000,0x7807c00,0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0, + 0x3c07800,0x7c003c00,0x1e,0x3c000,0x3c007c0,0x1e78001,0xe71df000,0xf8f80001,0xef80003e,0x1e,0xf0,0x780,0x0,0x0,0x3c00000, + 0xfffe000,0x0,0x3e000000,0x0,0x18,0x7fff,0xc0000000,0x60c306,0x1e0,0x7800001,0xe00f0000,0xfffe0007,0x8000003f,0xff8001ff, + 0xfc000000,0x0,0x0,0x0,0x7c000,0x0,0x0,0x0,0x0,0x3c00000,0x3c0f0078,0xfffe,0x3e000,0x1e000,0x0,0x780000,0xf0180000,0xf000003c, + 0xfcf80007,0x8000003c,0x7f,0x0,0x70001c,0x0,0xf81f00,0x0,0x38,0xe0000,0x0,0x0,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0xf81, + 0xf0000039,0xc0000000,0xe0000e70,0x1e00000,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,0x8000f000,0x78000, + 0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf00f007,0xbc03c001,0xe01e000f,0xf00078,0x78003c0, + 0x3c001e00,0xf801f00f,0x800780f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,0xf0007c07,0x8003e03c, + 0x1f01e0,0xf80f00,0x7c1e01e,0x7800,0xf0000,0x780000,0x3c00000,0x1e000000,0x780000,0x3c00000,0x1e000000,0xf0000f00,0xf003c00, + 0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0x1f8000f,0xe00f003c,0x7c01e0,0x3e00f00,0x1f007800,0xf8001ef8,0x1f000f, + 0x7be00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0xf,0x3c00000,0x307c0003,0x8038f000,0xfc000000,0x1e00,0x3c00,0x0,0x1e0000,0xfc0000,0x0,0x7e00003c,0x780,0xf00001e, + 0x7e00,0xf,0x80000780,0x3c0,0x3e001e00,0x3c0001f,0x7c000,0x780007,0xe000003f,0x0,0xfe000000,0xfe0000,0x0,0x3c07c3f0,0xf0001e03, + 0xc000f80f,0x800001e0,0x1f003c0,0x1e00,0xf000,0x781e0007,0x80007800,0x4000f80,0x3c003c00,0x7800001e,0xf078,0x1fe01f0,0x1f00780, + 0x7c00,0x7c078001,0xf000001f,0xf000,0xf0003c0,0x1e78001,0xfc07f007,0xc00f8000,0x780000f8,0x3c,0x1e,0x1e0,0x0,0x0,0x0,0xf0007c01, + 0xf000f007,0xc00000f0,0xf80780,0x3c,0x1f003,0xf0078007,0x80003c00,0xf000,0x7807c00,0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0, + 0x3c07800,0x7c003c00,0x1e,0x3c000,0x3c007c0,0x1e78000,0xfe0fe001,0xf07c0001,0xef00007c,0x1e,0xf0,0x780,0x0,0x0,0x1e00000, + 0x7cfc000,0xfc00000,0x3c00000f,0xc3f00000,0x18,0x7fff,0xc0000000,0x406303,0x3e0,0x3c00001,0xf00f0000,0x7cfc000f,0x8000001f, + 0x3f0000f9,0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x780700f8,0x7cfc,0x7c000,0x1e000,0x0,0x780000,0xf8180000, + 0xf0000070,0x3c0007,0x8000003c,0x3f,0x80000000,0x3c0078,0x0,0x780f00,0x0,0x1e,0x3c0000,0x0,0x0,0x0,0x0,0x0,0x3e007c0,0xe1c00, + 0x0,0x0,0x0,0xf01,0xe0000071,0xc0000000,0xe0001c70,0x1e00000,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007, + 0x8000f800,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x1f00f003,0xfc03e003,0xe01f001f, + 0xf800f8,0x7c007c0,0x3e003e01,0xf000f80f,0xf00f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,0xf0007c07, + 0x8003e03c,0x1f01e0,0xf80f00,0x7c1e01e,0x7c00,0xf0000,0x780000,0x3c00000,0x1e000000,0x780000,0x3c00000,0x1e000000,0xf0000f00, + 0xf003c00,0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0x1f8000f,0xc00f003c,0x7c01e0,0x3e00f00,0x1f007800,0xf8001ef0, + 0x1f000f,0x7bc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x780000,0xf,0x3800040,0x30780003,0x8038f800,0x78000000,0x1e00,0x3c00,0x0,0x1e0000,0xfc0000,0x0,0x7e000078, + 0x780,0x1f00001e,0xfc00,0x20001f,0x780,0x80007c0,0x1f001e00,0x7c0000f,0x78000,0xf80007,0xe000003f,0x0,0x1e000000,0xf00000, + 0x3c000,0x3c03fff0,0xf0001e03,0xc001f007,0x800101e0,0x7e003c0,0x1e00,0x7800,0x781e0007,0x80007800,0x6000f00,0x3c003e00,0x7800001e, + 0xf078,0x1fe00f0,0x1e00780,0x3c00,0x78078000,0xf020001e,0xf000,0x7800780,0xff0001,0xfc07f00f,0x8007c000,0x780001f0,0x3c,0xf, + 0x1e0,0x0,0x0,0x0,0xf800fc01,0xf801f007,0xc00100f8,0x1f807c0,0x40003c,0xf807,0xf0078007,0x80003c00,0xf000,0x7803e00,0x1f0000f, + 0x3c0f01e,0x1e01f0,0x3e007e0,0x7c07c00,0xfc003c00,0x1e,0x3e000,0x3e007c0,0x1ff8000,0xfe0fe003,0xe03e0001,0xff0000fc,0x1e, + 0xf0,0x780,0x0,0x0,0x1f00080,0x3cf8000,0xfc00000,0x3c00001f,0x83f00000,0x18,0xc0,0x0,0xc06203,0x40003c0,0x1c00000,0xf80f0000, + 0x3cf8001f,0xf,0x3e000079,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x700780fc,0x3cf8,0xfc000,0x1e000,0x0,0x780000, + 0x7c180000,0xf0000020,0x100007,0x8000003c,0xf,0x80000000,0x1f01f0,0x0,0x380700,0x0,0xf,0x80f80000,0x0,0x0,0x0,0x0,0x0,0x3e007c0, + 0xe1c00,0x0,0x0,0x0,0xe01,0xc0000071,0xc0000001,0xc0001c70,0x1e00040,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007, + 0x80007800,0x10078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x7e00f003,0xfc01e003,0xc00f001e, + 0x7800f0,0x3c00780,0x1e003c00,0xe000700f,0x800f0078,0x7803c0,0x3c01e00,0x1e00f000,0xf0000780,0x1e0000,0xf0003c,0x1f001f80, + 0xf800fc07,0xc007e03e,0x3f01f0,0x1f80f80,0xfc1e01f,0x7c00,0x100f8000,0x807c0004,0x3e00020,0x1f000100,0x780000,0x3c00000,0x1e000000, + 0xf0000f80,0x1f003c00,0x3c03e007,0xc01f003e,0xf801f0,0x7c00f80,0x3e007c00,0x1f8000f,0x801f003e,0x7c01f0,0x3e00f80,0x1f007c00, + 0xf8001ff0,0x1f801f,0x7fc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0xf,0x7800078,0x31f80001,0xc070fc00,0xfc000000,0x1e00,0x7c00,0x0,0x1e0000,0xfc0000,0x0,0x7e000078, + 0x7c0,0x1f00001e,0x1f000,0x38003f,0x780,0xe000f80,0x1f803e00,0x780000f,0x800f8000,0x1f00007,0xe000003f,0x0,0x2000000,0x800000, + 0x3c000,0x3e01ff71,0xf0001f03,0xc007f007,0xc00301e0,0x1fc003c0,0x1e00,0x7c00,0x781e0007,0x80007800,0x7801f00,0x3c001f00,0x7800001e, + 0xf078,0xfe00f8,0x3e00780,0x3e00,0xf8078000,0xf838003e,0xf000,0x7c00f80,0xff0000,0xfc07e00f,0x8003c000,0x780001e0,0x3c,0xf, + 0x1e0,0x0,0x0,0x0,0xf801fc01,0xfc03e003,0xe003007c,0x3f803e0,0x1c0003c,0xfc0f,0xf0078007,0x80003c00,0xf000,0x7801f00,0xf8000f, + 0x3c0f01e,0x1e00f8,0x7c007f0,0xf803e01,0xfc003c00,0x8003e,0x1f000,0x1e00fc0,0xff0000,0xfe0fe007,0xc01f0000,0xfe0000f8,0x1e, + 0xf0,0x780,0x0,0x0,0xf80180,0x1cf0000,0x1f800000,0x3c00001f,0x83e00000,0x18,0xc0,0x0,0xc06203,0x70007c0,0xe00000,0x7e0f0000, + 0x1cf0001e,0x7,0x3c000039,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100,0x7c00000,0xe00780fc,0x2001cf0,0xf8000,0x1e000,0x0, + 0x780000,0x7e182000,0xf0000000,0x7,0x8000003c,0x7,0xc0000000,0x7ffc0,0x0,0x180300,0x0,0x3,0xffe00000,0x0,0x0,0x0,0x0,0x0, + 0x3f00fc0,0xe1c00,0x0,0x0,0x0,0xc01,0x800000e1,0xc0000003,0xc0003870,0x1f001c0,0x3e0003e1,0xf0001f0f,0x8000f87c,0x7c3e0,0x3e1f00, + 0x1f1e007,0x80007c00,0x30078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e03,0xfc00f001,0xfc01f007, + 0xc00f803e,0x7c01f0,0x3e00f80,0x1f007c00,0x4000201f,0xc01f007c,0xf803e0,0x7c01f00,0x3e00f801,0xf0000780,0x1e0000,0xf0007c, + 0x1f003f80,0xf801fc07,0xc00fe03e,0x7f01f0,0x3f80f80,0x1fc1f03f,0x803e00,0x3007c003,0x803e001c,0x1f000e0,0xf800700,0x780000, + 0x3c00000,0x1e000000,0xf00007c0,0x3e003c00,0x3c01f00f,0x800f807c,0x7c03e0,0x3e01f00,0x1f00f800,0x1f80007,0xc03e001e,0xfc00f0, + 0x7e00780,0x3f003c01,0xf8000fe0,0x1fc03e,0x3f800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x780007f,0xfff00001,0xe0f07f03,0xfe000000,0xf00,0x7800,0x0, + 0x1e0000,0xfc0000,0x0,0x7e0000f0,0x3f0,0x7e000fff,0xfc03ffff,0xf83f00fe,0x780,0xfc03f80,0xfc0fc00,0xf800007,0xe03f0018,0x7e00007, + 0xe000003f,0x0,0x0,0x0,0x3c000,0x1e007c71,0xe0000f03,0xffffe003,0xf01f01ff,0xff8003ff,0xffe01e00,0x3f01,0xf81e0007,0x803ffff0, + 0x7e03f00,0x3c000f00,0x7ffffe1e,0xf078,0xfe007e,0xfc00780,0x1f83,0xf0078000,0x783f00fe,0xf000,0x3f03f00,0xff0000,0xfc07e01f, + 0x3e000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x7e07fc01,0xfe07e001,0xf80f007e,0x7f801f8,0xfc0003c,0x7ffe,0xf0078007, + 0x807ffffe,0xf000,0x7801f00,0xfff00f,0x3c0f01e,0x1e00fc,0xfc007f8,0x1f803f03,0xfc003c00,0xf80fc,0x1fff0,0x1f83fc0,0xff0000, + 0xfc07e007,0xc01f0000,0xfe0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0xfe0780,0xfe0000,0x1f000000,0x3c00001f,0x7c00e03,0x81c00018, + 0xc0,0x0,0x406203,0x7e01fc0,0x700000,0x7fffff80,0xfe0003f,0xffffc003,0xf800001f,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f0, + 0x1f800001,0xc007c1fe,0x6000fe0,0x1ffffe,0x1e000,0x0,0x780000,0x3f98e03f,0xffff8000,0x7,0x8000003c,0x7,0xc0000000,0xfe00, + 0x0,0x80100,0x0,0x0,0x7f000000,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3f83fe8,0xe1c00,0x0,0x0,0x0,0x801,0xc1,0xc0000007,0x80003070, + 0xfc0fc0,0x3c0001e1,0xe0000f0f,0x7878,0x3c3c0,0x1e1e00,0xf1e007,0xffc03f01,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003, + 0xffff001f,0xfff800ff,0xffc01fff,0xf800f001,0xfc00fc1f,0x8007e0fc,0x3f07e0,0x1f83f00,0xfc1f800,0x1f,0xf07e003f,0x3f001f8, + 0x1f800fc0,0xfc007e07,0xe0000780,0x1e0000,0xf301f8,0xfc0ff80,0x7e07fc03,0xf03fe01f,0x81ff00fc,0xff807e0,0x7fc0f87f,0x81801f80, + 0xf003f01f,0x801f80fc,0xfc07e0,0x7e03f00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff807e0,0x7e003c00,0x3c01f81f,0x800fc0fc,0x7e07e0, + 0x3f03f00,0x1f81f800,0x1f8000f,0xe07e001f,0x83fc00fc,0x1fe007e0,0xff003f07,0xf8000fe0,0x1fe07e,0x3f800,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x780007f, + 0xffe00000,0xffe03fff,0xdf000000,0xf00,0x7800,0x0,0x0,0xfc0000,0x0,0x7e0000f0,0x1ff,0xfc000fff,0xfc03ffff,0xf83ffffc,0x780, + 0xfffff00,0x7fff800,0xf000007,0xffff001f,0xffe00007,0xe000003f,0x0,0x0,0x0,0x3c000,0x1e000001,0xe0000f03,0xffffc001,0xffff01ff, + 0xff0003ff,0xffe01e00,0x1fff,0xf81e0007,0x803ffff0,0x7fffe00,0x3c000f80,0x7ffffe1e,0xf078,0xfe003f,0xff800780,0xfff,0xf0078000, + 0x7c3ffffc,0xf000,0x3ffff00,0xff0000,0xf803e01e,0x1e000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x7fffbc01,0xffffc000, + 0xffff003f,0xfff800ff,0xffc0003c,0x3ffe,0xf0078007,0x807ffffe,0xf000,0x7800f80,0x7ff00f,0x3c0f01e,0x1e007f,0xff8007ff,0xff001fff, + 0xbc003c00,0xffffc,0x1fff0,0x1fffbc0,0xff0000,0x7c07c00f,0x800f8000,0x7e0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x7fff80,0x7c0000, + 0x1f000000,0x3c00001e,0x7c00f07,0xc1e00018,0xc0,0x0,0x60e303,0x7ffff80,0x380000,0x3fffff80,0x7c0003f,0xffffc001,0xf000000f, + 0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xff800003,0x8003ffff,0xfe0007c0,0x1ffffe,0x1e000,0x0,0x780000,0x1fffe03f,0xffff8000, + 0x7,0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3fffdf8,0xe1c00,0x0,0x0,0x0,0x0,0x1c1, + 0xc000000f,0x7070,0x7fffc0,0x3c0001e1,0xe0000f0f,0x7878,0x3c3c0,0x1e1e00,0xf1e007,0xffc01fff,0xf007ffff,0xc03ffffe,0x1fffff0, + 0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf000f001,0xfc007fff,0x3fff8,0x1fffc0,0xfffe00,0x7fff000,0x3b,0xfffc003f, + 0xfff001ff,0xff800fff,0xfc007fff,0xe0000780,0x1e0000,0xf3fff8,0xffff780,0x7fffbc03,0xfffde01f,0xffef00ff,0xff7807ff,0xfbc0ffff, + 0xff800fff,0xf001ffff,0x800ffffc,0x7fffe0,0x3ffff00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff803ff,0xfc003c00,0x3c00ffff,0x7fff8, + 0x3fffc0,0x1fffe00,0xffff000,0x1f,0xfffc001f,0xffbc00ff,0xfde007ff,0xef003fff,0x780007e0,0x1ffffc,0x1f800,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x700003f, + 0xffc00000,0x7fc01fff,0x9f800000,0xf80,0xf800,0x0,0x0,0xfc0000,0x0,0x7e0000f0,0xff,0xf8000fff,0xfc03ffff,0xf83ffff8,0x780, + 0xffffe00,0x7fff000,0xf000003,0xfffe001f,0xffc00007,0xe000003f,0x0,0x0,0x0,0x3c000,0xf000003,0xe0000f83,0xffff0000,0xffff01ff, + 0xfc0003ff,0xffe01e00,0xfff,0xf01e0007,0x803ffff0,0x7fffc00,0x3c0007c0,0x7ffffe1e,0xf078,0x7e003f,0xff000780,0x7ff,0xe0078000, + 0x3c3ffff8,0xf000,0x1fffe00,0x7e0000,0xf803e03e,0x1f000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x3fff3c01,0xefff8000, + 0x7ffe001f,0xff78007f,0xff80003c,0x1ffc,0xf0078007,0x807ffffe,0xf000,0x78007c0,0x3ff00f,0x3c0f01e,0x1e003f,0xff0007bf,0xfe000fff, + 0xbc003c00,0xffff8,0xfff0,0xfff3c0,0x7e0000,0x7c07c01f,0x7c000,0x7c0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x3fff80,0x380000, + 0x3e000000,0x7c00003e,0x7801f07,0xc1e00018,0xc0,0x0,0x39c1ce,0x7ffff00,0x1c0000,0xfffff80,0x380003f,0xffffc000,0xe0000007, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xff000007,0x1ffcf,0xfe000380,0x1ffffe,0x1e000,0x0,0x780000,0xfffe03f,0xffff8000,0x7, + 0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3dffdf8,0xe1c00,0x0,0x0,0x0,0x0,0x381, + 0xc000001e,0xe070,0x7fff80,0x7c0001f3,0xe0000f9f,0x7cf8,0x3e7c0,0x1f3e00,0xfbe007,0xffc00fff,0xf007ffff,0xc03ffffe,0x1fffff0, + 0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xc000f000,0xfc007ffe,0x3fff0,0x1fff80,0xfffc00,0x7ffe000,0x79,0xfff8001f, + 0xffe000ff,0xff0007ff,0xf8003fff,0xc0000780,0x1e0000,0xf3fff0,0x7ffe780,0x3fff3c01,0xfff9e00f,0xffcf007f,0xfe7803ff,0xf3c07ff3, + 0xff8007ff,0xe000ffff,0x7fff8,0x3fffc0,0x1fffe00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff801ff,0xf8003c00,0x3c007ffe,0x3fff0, + 0x1fff80,0xfffc00,0x7ffe000,0x1d,0xfff8000f,0xff3c007f,0xf9e003ff,0xcf001ffe,0x780007c0,0x1efff8,0x1f000,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0xf000003, + 0xfe000000,0x1f000fff,0xfc00000,0x780,0xf000,0x0,0x0,0xf80000,0x0,0x7e0001e0,0x7f,0xf0000fff,0xfc03ffff,0xf81ffff0,0x780, + 0x7fff800,0x1ffe000,0x1f000000,0xfff8001f,0xff000007,0xe000003e,0x0,0x0,0x0,0x3c000,0xf800003,0xc0000783,0xfff80000,0x3ffe01ff, + 0xe00003ff,0xffe01e00,0x7ff,0xc01e0007,0x803ffff0,0x3fff800,0x3c0003c0,0x7ffffe1e,0xf078,0x7e000f,0xfe000780,0x3ff,0xc0078000, + 0x3e1fffe0,0xf000,0x7ff800,0x7e0000,0xf803e07c,0xf800,0x780003ff,0xfffc003c,0x3,0xc00001e0,0x0,0x0,0x0,0xffe3c01,0xe7ff0000, + 0x3ffc000f,0xfe78003f,0xfe00003c,0x7f0,0xf0078007,0x807ffffe,0xf000,0x78003e0,0xff00f,0x3c0f01e,0x1e001f,0xfe00079f,0xfc0007ff, + 0x3c003c00,0x7ffe0,0x1ff0,0x7fe3c0,0x7e0000,0x7c07c03e,0x3e000,0x7c0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0xfff00,0x100000, + 0x3e000000,0x7800003c,0xf800f07,0xc1e00018,0xc0,0x0,0x1f80fc,0x3fffc00,0xc0000,0x3ffff80,0x100003f,0xffffc000,0x40000002, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xfc000006,0xff87,0xfc000100,0x1ffffe,0x1e000,0x0,0x780000,0x3ffc03f,0xffff8000,0x7, + 0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3dff9f8,0xe1c00,0x0,0x0,0x0,0x0,0x3ff, + 0xf800003c,0xfffe,0x1ffe00,0x780000f3,0xc000079e,0x3cf0,0x1e780,0xf3c00,0x7bc007,0xffc003ff,0xe007ffff,0xc03ffffe,0x1fffff0, + 0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01ffc,0xf000,0xfc001ffc,0xffe0,0x7ff00,0x3ff800,0x1ffc000,0x70,0xfff00007, + 0xff80003f,0xfc0001ff,0xe0000fff,0x780,0x1e0000,0xf3ffe0,0x1ffc780,0xffe3c00,0x7ff1e003,0xff8f001f,0xfc7800ff,0xe3c03fe1, + 0xff0003ff,0xc0007ffc,0x3ffe0,0x1fff00,0xfff800,0xfffffc07,0xffffe03f,0xffff01ff,0xfff800ff,0xf0003c00,0x3c003ffc,0x1ffe0, + 0xfff00,0x7ff800,0x3ffc000,0x38,0xfff00007,0xfe3c003f,0xf1e001ff,0x8f000ffc,0x780007c0,0x1e7ff0,0x1f000,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000, + 0x1fc,0x0,0x780,0xf000,0x0,0x0,0x1f80000,0x0,0x1e0,0x1f,0xc0000000,0x0,0x1ff80,0x0,0xffc000,0x7f8000,0x0,0x3fe00007,0xfc000000, + 0x7e,0x0,0x0,0x0,0x0,0x7c00000,0x0,0x0,0xff00000,0x0,0x0,0xfe,0x0,0x0,0x3fc000,0x0,0x0,0x0,0x3,0xf8000000,0xff,0xc0000000, + 0x1ff00,0x0,0x1fe000,0x0,0x0,0x0,0x0,0x3c,0x3,0xc00001e0,0x0,0x0,0x0,0x3f80000,0x1fc0000,0x7f00003,0xf8000007,0xf0000000, + 0x0,0xf0000000,0x0,0xf000,0x0,0x0,0x0,0x7,0xf8000787,0xf00001fc,0x3c000000,0x7f80,0x0,0x1f8000,0x0,0x0,0x0,0x7c000000,0x1e, + 0xf0,0x780,0x0,0x0,0x3fc00,0x0,0x3c000000,0x7800003c,0xf000601,0xc00018,0xc0,0x0,0x0,0x3fe000,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0xf,0xf0000000,0x7e03,0xf0000000,0x0,0x0,0x0,0x0,0xfe0000,0x0,0x0,0x3c,0x2007,0x80000000,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c7e0f0,0xe1c00,0x0,0x3800000,0x0,0x0,0x3ff,0xf8000078,0xfffe,0x7f800,0x0,0x0,0x0,0x0, + 0x0,0x0,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f0,0x3f80,0x1fc00,0xfe000,0x7f0000,0x70,0x3fc00001,0xfe00000f,0xf000007f, + 0x800003fc,0x0,0x0,0xff00,0x7f0000,0x3f80000,0x1fc00000,0xfe000007,0xf000003f,0x80001f80,0xfc00007f,0xfe0,0x7f00,0x3f800, + 0x1fc000,0x0,0x0,0x0,0x3f,0xc0000000,0xff0,0x7f80,0x3fc00,0x1fe000,0xff0000,0x78,0x3fc00001,0xf800000f,0xc000007e,0x3f0,0x7c0, + 0x1e1fc0,0x1f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x3c0,0x1e000,0x0,0x0,0x1f00000,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0x3e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xe0000000,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x3c,0x1,0xe00001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x78000000,0x1e,0xf0,0x780,0x0,0x0,0x0,0x0,0x3c000000,0x78000078,0xf000000,0x18,0xc0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3c0f,0x80000000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0x1800000,0x0,0x0,0x3ff,0xf80000f0,0xfffe,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0xc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0xc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,0x780,0x1e0000,0x1e000,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000, + 0x0,0x0,0x3c0,0x1e000,0x0,0x0,0x1f00000,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0x1f80000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x1,0xe00001e0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe0000000,0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0xf8000000, + 0x1f,0xf0,0xf80,0x0,0x0,0x0,0x0,0x78000000,0xf8000078,0x1e000000,0x8,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3fff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x3c00000,0xe1c00,0x0,0x1c00000,0x0,0x0,0x1,0xc00001e0,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x1e0000,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x1e0,0x3c000,0x0,0x0,0x1f00000, + 0x0,0x780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0xfe0100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0xf0007fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe0000000, + 0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x1f,0x800000f0,0x1f80,0x0,0x0,0x0,0x0, + 0x78000000,0xf0000070,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3ffe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0xe00000, + 0x0,0x0,0x1,0xc00003ff,0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0xf00,0x1e0000,0x3c000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x1e0,0x7c000,0x0,0x0,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x78,0x0,0x0,0x0,0x0,0x7fff80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78000000, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4003,0xe0000000,0x0,0x1f000,0x0,0x0, + 0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x1,0xf0000000,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x0,0x0,0x70000001,0xf00000e0, + 0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000, + 0x0,0x0,0x3c,0xff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0xe00000,0x0,0x0,0x1,0xc00003ff, + 0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00,0x1e0000, + 0x7c000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0xf0,0x78000,0x0,0x0,0x3e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf8,0x0, + 0x0,0x0,0x0,0x1fff80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f, + 0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780f,0xc0000000,0x0,0x3e000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0, + 0x0,0x0,0x0,0x0,0x3,0xe0000000,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x0,0x0,0xf0000103,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x21e00000,0x0,0x0,0x1,0xc00003ff,0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10f, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10f,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e00,0x1e0000,0xf8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0, + 0xf8,0xf8000,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x1fe00,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x7fff,0xc0000000,0x0,0x3ffe000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7f,0xe0000000,0x7,0xfc0000f0, + 0x3fe00,0x0,0x0,0x0,0x0,0x600001ff,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0, + 0x3fe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x7fe00,0x1e0000,0x1ff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff,0x80000000,0x0,0x3ffc000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0, + 0x0,0x0,0x0,0x0,0x7f,0xc0000000,0x0,0xfc0000f0,0x3f000,0x0,0x0,0x0,0x0,0x1ff,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x3fc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fe,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fe,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fc00,0x1e0000,0x1ff0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x3ffe,0x0,0x0,0x3ff8000,0x0,0x0,0x0, + 0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7f,0x80000000,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x80000000,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x3f800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fc,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fc,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f800,0x1e0000,0x1fe0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f8,0x0,0x0,0x3fe0000, + 0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7e,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0xfe,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7e000,0x1e0000,0x1f80000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 }; + + // Definition of a 40x38 'danger' color logo. + const unsigned char logo40x38[4576] = { + 177,200,200,200,3,123,123,0,36,200,200,200,1,123,123,0,2,255,255,0,1,189,189,189,1,0,0,0,34,200,200,200, + 1,123,123,0,4,255,255,0,1,189,189,189,1,0,0,0,1,123,123,123,32,200,200,200,1,123,123,0,5,255,255,0,1,0,0, + 0,2,123,123,123,30,200,200,200,1,123,123,0,6,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,29,200,200,200, + 1,123,123,0,7,255,255,0,1,0,0,0,2,123,123,123,28,200,200,200,1,123,123,0,8,255,255,0,1,189,189,189,1,0,0,0, + 2,123,123,123,27,200,200,200,1,123,123,0,9,255,255,0,1,0,0,0,2,123,123,123,26,200,200,200,1,123,123,0,10,255, + 255,0,1,189,189,189,1,0,0,0,2,123,123,123,25,200,200,200,1,123,123,0,3,255,255,0,1,189,189,189,3,0,0,0,1,189, + 189,189,3,255,255,0,1,0,0,0,2,123,123,123,24,200,200,200,1,123,123,0,4,255,255,0,5,0,0,0,3,255,255,0,1,189, + 189,189,1,0,0,0,2,123,123,123,23,200,200,200,1,123,123,0,4,255,255,0,5,0,0,0,4,255,255,0,1,0,0,0,2,123,123,123, + 22,200,200,200,1,123,123,0,5,255,255,0,5,0,0,0,4,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,21,200,200,200, + 1,123,123,0,5,255,255,0,5,0,0,0,5,255,255,0,1,0,0,0,2,123,123,123,20,200,200,200,1,123,123,0,6,255,255,0,5,0,0, + 0,5,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,19,200,200,200,1,123,123,0,6,255,255,0,1,123,123,0,3,0,0,0,1, + 123,123,0,6,255,255,0,1,0,0,0,2,123,123,123,18,200,200,200,1,123,123,0,7,255,255,0,1,189,189,189,3,0,0,0,1,189, + 189,189,6,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,17,200,200,200,1,123,123,0,8,255,255,0,3,0,0,0,8,255,255, + 0,1,0,0,0,2,123,123,123,16,200,200,200,1,123,123,0,9,255,255,0,1,123,123,0,1,0,0,0,1,123,123,0,8,255,255,0,1,189, + 189,189,1,0,0,0,2,123,123,123,15,200,200,200,1,123,123,0,9,255,255,0,1,189,189,189,1,0,0,0,1,189,189,189,9,255,255, + 0,1,0,0,0,2,123,123,123,14,200,200,200,1,123,123,0,11,255,255,0,1,0,0,0,10,255,255,0,1,189,189,189,1,0,0,0,2,123, + 123,123,13,200,200,200,1,123,123,0,23,255,255,0,1,0,0,0,2,123,123,123,12,200,200,200,1,123,123,0,11,255,255,0,1,189, + 189,189,2,0,0,0,1,189,189,189,9,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,11,200,200,200,1,123,123,0,11,255,255, + 0,4,0,0,0,10,255,255,0,1,0,0,0,2,123,123,123,10,200,200,200,1,123,123,0,12,255,255,0,4,0,0,0,10,255,255,0,1,189,189, + 189,1,0,0,0,2,123,123,123,9,200,200,200,1,123,123,0,12,255,255,0,1,189,189,189,2,0,0,0,1,189,189,189,11,255,255,0,1, + 0,0,0,2,123,123,123,9,200,200,200,1,123,123,0,27,255,255,0,1,0,0,0,3,123,123,123,8,200,200,200,1,123,123,0,26,255, + 255,0,1,189,189,189,1,0,0,0,3,123,123,123,9,200,200,200,1,123,123,0,24,255,255,0,1,189,189,189,1,0,0,0,4,123,123, + 123,10,200,200,200,1,123,123,0,24,0,0,0,5,123,123,123,12,200,200,200,27,123,123,123,14,200,200,200,25,123,123,123,86, + 200,200,200,91,49,124,118,124,71,32,124,95,49,56,114,52,82,121,0}; + + //! Display a warning message. + /** + \param format is a C-string describing the format of the message, as in std::printf(). + **/ + inline void warn(const char *format, ...) { + if (cimg::exception_mode()>=1) { + char message[8192]; + cimg_std::va_list ap; + va_start(ap,format); + cimg_std::vsprintf(message,format,ap); + va_end(ap); +#ifdef cimg_strict_warnings + throw CImgWarningException(message); +#else + cimg_std::fprintf(cimg_stdout,"\n%s# CImg Warning%s :\n%s\n",cimg::t_red,cimg::t_normal,message); +#endif + } + } + + // Execute an external system command. + /** + \note This function is similar to std::system() + and is here because using the std:: version on + Windows may open undesired consoles. + **/ + inline int system(const char *const command, const char *const module_name=0) { +#if cimg_OS==2 + PROCESS_INFORMATION pi; + STARTUPINFO si; + cimg_std::memset(&pi,0,sizeof(PROCESS_INFORMATION)); + cimg_std::memset(&si,0,sizeof(STARTUPINFO)); + GetStartupInfo(&si); + si.cb = sizeof(si); + si.wShowWindow = SW_HIDE; + si.dwFlags |= SW_HIDE; + const BOOL res = CreateProcess((LPCTSTR)module_name,(LPTSTR)command,0,0,FALSE,0,0,0,&si,&pi); + if (res) { + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return 0; + } else +#endif + return cimg_std::system(command); + return module_name?0:1; + } + + //! Return a reference to a temporary variable of type T. + template + inline T& temporary(const T&) { + static T temp; + return temp; + } + + //! Exchange values of variables \p a and \p b. + template + inline void swap(T& a, T& b) { T t = a; a = b; b = t; } + + //! Exchange values of variables (\p a1,\p a2) and (\p b1,\p b2). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2) { + cimg::swap(a1,b1); cimg::swap(a2,b2); + } + + //! Exchange values of variables (\p a1,\p a2,\p a3) and (\p b1,\p b2,\p b3). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3) { + cimg::swap(a1,b1,a2,b2); cimg::swap(a3,b3); + } + + //! Exchange values of variables (\p a1,\p a2,...,\p a4) and (\p b1,\p b2,...,\p b4). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4) { + cimg::swap(a1,b1,a2,b2,a3,b3); cimg::swap(a4,b4); + } + + //! Exchange values of variables (\p a1,\p a2,...,\p a5) and (\p b1,\p b2,...,\p b5). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4); cimg::swap(a5,b5); + } + + //! Exchange values of variables (\p a1,\p a2,...,\p a6) and (\p b1,\p b2,...,\p b6). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5); cimg::swap(a6,b6); + } + + //! Exchange values of variables (\p a1,\p a2,...,\p a7) and (\p b1,\p b2,...,\p b7). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6, + T7& a7, T7& b7) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6); cimg::swap(a7,b7); + } + + //! Exchange values of variables (\p a1,\p a2,...,\p a8) and (\p b1,\p b2,...,\p b8). + template + inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6, + T7& a7, T7& b7, T8& a8, T8& b8) { + cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6,a7,b7); cimg::swap(a8,b8); + } + + //! Return the current endianness of the CPU. + /** + \return \c false for "Little Endian", \c true for "Big Endian". + **/ + inline bool endianness() { + const int x = 1; + return ((unsigned char*)&x)[0]?false:true; + } + + //! Invert endianness of a memory buffer. + template + inline void invert_endianness(T* const buffer, const unsigned int size) { + if (size) switch (sizeof(T)) { + case 1 : break; + case 2 : { for (unsigned short *ptr = (unsigned short*)buffer+size; ptr>(unsigned short*)buffer; ) { + const unsigned short val = *(--ptr); + *ptr = (unsigned short)((val>>8)|((val<<8))); + }} break; + case 4 : { for (unsigned int *ptr = (unsigned int*)buffer+size; ptr>(unsigned int*)buffer; ) { + const unsigned int val = *(--ptr); + *ptr = (val>>24)|((val>>8)&0xff00)|((val<<8)&0xff0000)|(val<<24); + }} break; + default : { for (T* ptr = buffer+size; ptr>buffer; ) { + unsigned char *pb = (unsigned char*)(--ptr), *pe = pb + sizeof(T); + for (int i=0; i<(int)sizeof(T)/2; ++i) swap(*(pb++),*(--pe)); + }} + } + } + + //! Invert endianness of a single variable. + template + inline T& invert_endianness(T& a) { + invert_endianness(&a,1); + return a; + } + + //! Get the value of a system timer with a millisecond precision. + inline unsigned long time() { +#if cimg_OS==1 + struct timeval st_time; + gettimeofday(&st_time,0); + return (unsigned long)(st_time.tv_usec/1000 + st_time.tv_sec*1000); +#elif cimg_OS==2 + static SYSTEMTIME st_time; + GetSystemTime(&st_time); + return (unsigned long)(st_time.wMilliseconds + 1000*(st_time.wSecond + 60*(st_time.wMinute + 60*st_time.wHour))); +#else + return 0; +#endif + } + + //! Sleep for a certain numbers of milliseconds. + /** + This function frees the CPU ressources during the sleeping time. + It may be used to temporize your program properly, without wasting CPU time. + **/ + inline void sleep(const unsigned int milliseconds) { +#if cimg_OS==1 + struct timespec tv; + tv.tv_sec = milliseconds/1000; + tv.tv_nsec = (milliseconds%1000)*1000000; + nanosleep(&tv,0); +#elif cimg_OS==2 + Sleep(milliseconds); +#endif + } + + inline unsigned int _sleep(const unsigned int milliseconds, unsigned long& timer) { + if (!timer) timer = cimg::time(); + const unsigned long current_time = cimg::time(); + if (current_time>=timer+milliseconds) { timer = current_time; return 0; } + const unsigned long time_diff = timer + milliseconds - current_time; + timer = current_time + time_diff; + cimg::sleep(time_diff); + return (unsigned int)time_diff; + } + + //! Wait for a certain number of milliseconds since the last call. + /** + This function is equivalent to sleep() but the waiting time is computed with regard to the last call + of wait(). It may be used to temporize your program properly. + **/ + inline unsigned int wait(const unsigned int milliseconds) { + static unsigned long timer = 0; + if (!timer) timer = cimg::time(); + return _sleep(milliseconds,timer); + } + + // Use a specific srand initialization to avoid multi-threads to have to the + // same series of random numbers (executed only once for a single program). + inline void srand() { + static bool first_time = true; + if (first_time) { + cimg_std::srand(cimg::time()); + unsigned char *const rand_ptr = new unsigned char[1+cimg_std::rand()%2048]; + cimg_std::srand((unsigned int)cimg_std::rand() + *(unsigned int*)(void*)rand_ptr); + delete[] rand_ptr; + first_time = false; + } + } + + //! Return a left bitwise-rotated number. + template + inline const T rol(const T a, const unsigned int n=1) { + return n?(T)((a<>((sizeof(T)<<3)-n))):a; + } + + //! Return a right bitwise-rotated number. + template + inline const T ror(const T a, const unsigned int n=1) { + return n?(T)((a>>n)|(a<<((sizeof(T)<<3)-n))):a; + } + + //! Return the absolute value of a number. + /** + \note This function is different from std::abs() or std::fabs() + because it is able to consider a variable of any type, without cast needed. + **/ + template + inline T abs(const T a) { + return a>=0?a:-a; + } + inline bool abs(const bool a) { + return a; + } + inline unsigned char abs(const unsigned char a) { + return a; + } + inline unsigned short abs(const unsigned short a) { + return a; + } + inline unsigned int abs(const unsigned int a) { + return a; + } + inline unsigned long abs(const unsigned long a) { + return a; + } + inline double abs(const double a) { + return cimg_std::fabs(a); + } + inline float abs(const float a) { + return (float)cimg_std::fabs((double)a); + } + inline int abs(const int a) { + return cimg_std::abs(a); + } + + //! Return the square of a number. + template + inline T sqr(const T val) { + return val*val; + } + + //! Return 1 + log_10(x). + inline int xln(const int x) { + return x>0?(int)(1+cimg_std::log10((double)x)):1; + } + + //! Return the minimum value between two numbers. + template + inline typename cimg::superset::type min(const t1& a, const t2& b) { + typedef typename cimg::superset::type t1t2; + return (t1t2)(a<=b?a:b); + } + + //! Return the minimum value between three numbers. + template + inline typename cimg::superset2::type min(const t1& a, const t2& b, const t3& c) { + typedef typename cimg::superset2::type t1t2t3; + return (t1t2t3)cimg::min(cimg::min(a,b),c); + } + + //! Return the minimum value between four numbers. + template + inline typename cimg::superset3::type min(const t1& a, const t2& b, const t3& c, const t4& d) { + typedef typename cimg::superset3::type t1t2t3t4; + return (t1t2t3t4)cimg::min(cimg::min(a,b,c),d); + } + + //! Return the maximum value between two numbers. + template + inline typename cimg::superset::type max(const t1& a, const t2& b) { + typedef typename cimg::superset::type t1t2; + return (t1t2)(a>=b?a:b); + } + + //! Return the maximum value between three numbers. + template + inline typename cimg::superset2::type max(const t1& a, const t2& b, const t3& c) { + typedef typename cimg::superset2::type t1t2t3; + return (t1t2t3)cimg::max(cimg::max(a,b),c); + } + + //! Return the maximum value between four numbers. + template + inline typename cimg::superset3::type max(const t1& a, const t2& b, const t3& c, const t4& d) { + typedef typename cimg::superset3::type t1t2t3t4; + return (t1t2t3t4)cimg::max(cimg::max(a,b,c),d); + } + + //! Return the sign of a number. + template + inline T sign(const T x) { + return (x<0)?(T)(-1):(x==0?(T)0:(T)1); + } + + //! Return the nearest power of 2 higher than a given number. + template + inline unsigned long nearest_pow2(const T x) { + unsigned long i = 1; + while (x>i) i<<=1; + return i; + } + + //! Return the modulo of a number. + /** + \note This modulo function accepts negative and floating-points modulo numbers, as well as + variable of any type. + **/ + template + inline T mod(const T& x, const T& m) { + const double dx = (double)x, dm = (double)m; + if (x<0) { return (T)(dm+dx+dm*cimg_std::floor(-dx/dm)); } + return (T)(dx-dm*cimg_std::floor(dx/dm)); + } + inline int mod(const bool x, const bool m) { + return m?(x?1:0):0; + } + inline int mod(const char x, const char m) { + return x>=0?x%m:(x%m?m+x%m:0); + } + inline int mod(const short x, const short m) { + return x>=0?x%m:(x%m?m+x%m:0); + } + inline int mod(const int x, const int m) { + return x>=0?x%m:(x%m?m+x%m:0); + } + inline int mod(const long x, const long m) { + return x>=0?x%m:(x%m?m+x%m:0); + } + inline int mod(const unsigned char x, const unsigned char m) { + return x%m; + } + inline int mod(const unsigned short x, const unsigned short m) { + return x%m; + } + inline int mod(const unsigned int x, const unsigned int m) { + return x%m; + } + inline int mod(const unsigned long x, const unsigned long m) { + return x%m; + } + + //! Return the minmod of two numbers. + /** + minmod(\p a,\p b) is defined to be : + - minmod(\p a,\p b) = min(\p a,\p b), if \p a and \p b have the same sign. + - minmod(\p a,\p b) = 0, if \p a and \p b have different signs. + **/ + template + inline T minmod(const T a, const T b) { + return a*b<=0?0:(a>0?(a=1.0); + return x1*cimg_std::sqrt((-2*cimg_std::log(w))/w); + } + + //! Return a random variable following a Poisson distribution of parameter z. + inline unsigned int prand(const double z) { + if (z<=1.0e-10) return 0; + if (z>100.0) return (unsigned int)((std::sqrt(z) * cimg::grand()) + z); + unsigned int k = 0; + const double y = std::exp(-z); + for (double s = 1.0; s>=y; ++k) s*=cimg::rand(); + return k-1; + } + + //! Return a rounded number. + /** + \param x is the number to be rounded. + \param y is the rounding precision. + \param rounding_type defines the type of rounding (0=nearest, -1=backward, 1=forward). + **/ + inline double round(const double x, const double y, const int rounding_type=0) { + if (y<=0) return x; + const double delta = cimg::mod(x,y); + if (delta==0.0) return x; + const double + backward = x - delta, + forward = backward + y; + return rounding_type<0?backward:(rounding_type>0?forward:(2*deltaabsb) { const double tmp = absb/absa; return absa*cimg_std::sqrt(1.0+tmp*tmp); } + else { const double tmp = absa/absb; return (absb==0?0:absb*cimg_std::sqrt(1.0+tmp*tmp)); } + } + + //! Remove the 'case' of an ASCII character. + inline char uncase(const char x) { + return (char)((x<'A'||x>'Z')?x:x-'A'+'a'); + } + + //! Remove the 'case' of a C string. + /** + Acts in-place. + **/ + inline void uncase(char *const string) { + if (string) for (char *ptr = string; *ptr; ++ptr) *ptr = uncase(*ptr); + } + + //! Read a float number from a C-string. + /** + \note This function is quite similar to std::atof(), + but that it allows the retrieval of fractions as in "1/2". + **/ + inline float atof(const char *const str) { + float x = 0,y = 1; + if (!str) return 0; else { cimg_std::sscanf(str,"%g/%g",&x,&y); return x/y; } + } + + //! Compute the length of a C-string. + /** + \note This function is similar to std::strlen() + and is here because some old compilers do not + define the std:: version. + **/ + inline int strlen(const char *const s) { + if (!s) return -1; + int k = 0; + for (const char *ns = s; *ns; ++ns) ++k; + return k; + } + + //! Compare the first \p n characters of two C-strings. + /** + \note This function is similar to std::strncmp() + and is here because some old compilers do not + define the std:: version. + **/ + inline int strncmp(const char *const s1, const char *const s2, const int l) { + if (!s1) return s2?-1:0; + const char *ns1 = s1, *ns2 = s2; + int k, diff = 0; for (k = 0; kstd::strncasecmp() + and is here because some old compilers do not + define the std:: version. + **/ + inline int strncasecmp(const char *const s1, const char *const s2, const int l) { + if (!s1) return s2?-1:0; + const char *ns1 = s1, *ns2 = s2; + int k, diff = 0; for (k = 0; kstd::strcmp() + and is here because some old compilers do not + define the std:: version. + **/ + inline int strcmp(const char *const s1, const char *const s2) { + const int l1 = cimg::strlen(s1), l2 = cimg::strlen(s2); + return cimg::strncmp(s1,s2,1+(l1std::strcasecmp() + and is here because some old compilers do not + define the std:: version. + **/ + inline int strcasecmp(const char *const s1, const char *const s2) { + const int l1 = cimg::strlen(s1), l2 = cimg::strlen(s2); + return cimg::strncasecmp(s1,s2,1+(l1=0 && s[l]!=c; --l) {} + return l; + } + + //! Remove useless delimiters on the borders of a C-string + inline bool strpare(char *const s, const char delimiter=' ', const bool symmetric=false) { + if (!s) return false; + const int l = cimg::strlen(s); + int p, q; + if (symmetric) for (p = 0, q = l-1; pp && s[q]==delimiter; ) --q; + } + const int n = q - p + 1; + if (n!=l) { cimg_std::memmove(s,s+p,n); s[n] = '\0'; return true; } + return false; + } + + //! Remove useless spaces and symmetric delimiters ', " and ` from a C-string. + inline void strclean(char *const s) { + if (!s) return; + strpare(s,' ',false); + for (bool need_iter = true; need_iter; ) { + need_iter = false; + need_iter |= strpare(s,'\'',true); + need_iter |= strpare(s,'\"',true); + need_iter |= strpare(s,'`',true); + } + } + + //! Replace explicit escape sequences '\x' in C-strings (where x in [ntvbrfa?'"0]). + inline void strescape(char *const s) { +#define cimg_strescape(ci,co) case ci: *nd = co; break; + char *ns, *nd; + for (ns = nd = s; *ns; ++ns, ++nd) + if (*ns=='\\') switch (*(++ns)) { + cimg_strescape('n','\n'); + cimg_strescape('t','\t'); + cimg_strescape('v','\v'); + cimg_strescape('b','\b'); + cimg_strescape('r','\r'); + cimg_strescape('f','\f'); + cimg_strescape('a','\a'); + cimg_strescape('\\','\\'); + cimg_strescape('\?','\?'); + cimg_strescape('\'','\''); + cimg_strescape('\"','\"'); + cimg_strescape('\0','\0'); + } + else *nd = *ns; + *nd = 0; + } + + //! Compute the basename of a filename. + inline const char* basename(const char *const s) { + return (cimg_OS!=2)?(s?s+1+cimg::strfind(s,'/'):0):(s?s+1+cimg::strfind(s,'\\'):0); + } + + // Generate a random filename. + inline const char* filenamerand() { + static char id[9] = { 0,0,0,0,0,0,0,0,0 }; + cimg::srand(); + for (unsigned int k=0; k<8; ++k) { + const int v = (int)cimg_std::rand()%3; + id[k] = (char)(v==0?('0'+(cimg_std::rand()%10)):(v==1?('a'+(cimg_std::rand()%26)):('A'+(cimg_std::rand()%26)))); + } + return id; + } + + // Convert filename into a Windows-style filename. + inline void winformat_string(char *const s) { + if (s && s[0]) { +#if cimg_OS==2 + char *const ns = new char[MAX_PATH]; + if (GetShortPathNameA(s,ns,MAX_PATH)) cimg_std::strcpy(s,ns); +#endif + } + } + + //! Return or set path to store temporary files. + inline const char* temporary_path(const char *const user_path=0, const bool reinit_path=false) { +#define _cimg_test_temporary_path(p) \ + if (!path_found) { \ + cimg_std::sprintf(st_path,"%s",p); \ + cimg_std::sprintf(tmp,"%s%s%s",st_path,cimg_OS==2?"\\":"/",filetmp); \ + if ((file=cimg_std::fopen(tmp,"wb"))!=0) { cimg_std::fclose(file); cimg_std::remove(tmp); path_found = true; } \ + } + static char *st_path = 0; + if (reinit_path && st_path) { delete[] st_path; st_path = 0; } + if (user_path) { + if (!st_path) st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + cimg_std::strncpy(st_path,user_path,1023); + } else if (!st_path) { + st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + bool path_found = false; + char tmp[1024], filetmp[512]; + cimg_std::FILE *file = 0; + cimg_std::sprintf(filetmp,"%s.tmp",cimg::filenamerand()); + char *tmpPath = getenv("TMP"); + if (!tmpPath) { tmpPath = getenv("TEMP"); winformat_string(tmpPath); } + if (tmpPath) _cimg_test_temporary_path(tmpPath); +#if cimg_OS==2 + _cimg_test_temporary_path("C:\\WINNT\\Temp"); + _cimg_test_temporary_path("C:\\WINDOWS\\Temp"); + _cimg_test_temporary_path("C:\\Temp"); + _cimg_test_temporary_path("C:"); + _cimg_test_temporary_path("D:\\WINNT\\Temp"); + _cimg_test_temporary_path("D:\\WINDOWS\\Temp"); + _cimg_test_temporary_path("D:\\Temp"); + _cimg_test_temporary_path("D:"); +#else + _cimg_test_temporary_path("/tmp"); + _cimg_test_temporary_path("/var/tmp"); +#endif + if (!path_found) { + st_path[0]='\0'; + cimg_std::strcpy(tmp,filetmp); + if ((file=cimg_std::fopen(tmp,"wb"))!=0) { cimg_std::fclose(file); cimg_std::remove(tmp); path_found = true; } + } + if (!path_found) + throw CImgIOException("cimg::temporary_path() : Unable to find a temporary path accessible for writing\n" + "you have to set the macro 'cimg_temporary_path' to a valid path where you have writing access :\n" + "#define cimg_temporary_path \"path\" (before including 'CImg.h')"); + } + return st_path; + } + + // Return or set path to the "Program files/" directory (windows only). +#if cimg_OS==2 + inline const char* programfiles_path(const char *const user_path=0, const bool reinit_path=false) { + static char *st_path = 0; + if (reinit_path && st_path) { delete[] st_path; st_path = 0; } + if (user_path) { + if (!st_path) st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + cimg_std::strncpy(st_path,user_path,1023); + } else if (!st_path) { + st_path = new char[MAX_PATH]; + cimg_std::memset(st_path,0,MAX_PATH); + // Note : in the following line, 0x26 = CSIDL_PROGRAM_FILES (not defined on every compiler). +#if !defined(__INTEL_COMPILER) + if (!SHGetSpecialFolderPathA(0,st_path,0x0026,false)) { + const char *pfPath = getenv("PROGRAMFILES"); + if (pfPath) cimg_std::strncpy(st_path,pfPath,MAX_PATH-1); + else cimg_std::strcpy(st_path,"C:\\PROGRA~1"); + } +#else + cimg_std::strcpy(st_path,"C:\\PROGRA~1"); +#endif + } + return st_path; + } +#endif + + //! Return or set path to the ImageMagick's \c convert tool. + inline const char* imagemagick_path(const char *const user_path=0, const bool reinit_path=false) { + static char *st_path = 0; + if (reinit_path && st_path) { delete[] st_path; st_path = 0; } + if (user_path) { + if (!st_path) st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + cimg_std::strncpy(st_path,user_path,1023); + } else if (!st_path) { + st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + bool path_found = false; + cimg_std::FILE *file = 0; +#if cimg_OS==2 + const char *pf_path = programfiles_path(); + if (!path_found) { + cimg_std::sprintf(st_path,".\\convert.exe"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + { for (int k=32; k>=10 && !path_found; --k) { + cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%.2d-\\convert.exe",pf_path,k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=9; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d-Q\\convert.exe",pf_path,k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d\\convert.exe",pf_path,k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=10 && !path_found; --k) { + cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",pf_path,k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=9; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",pf_path,k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",pf_path,k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=10 && !path_found; --k) { + cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%.2d-\\convert.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=9; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d-Q\\convert.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d\\convert.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=10 && !path_found; --k) { + cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=9; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=10 && !path_found; --k) { + cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%.2d-\\convert.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=9; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d-Q\\convert.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d\\convert.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=10 && !path_found; --k) { + cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=9; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + if (!path_found) cimg_std::strcpy(st_path,"convert.exe"); +#else + if (!path_found) { + cimg_std::sprintf(st_path,"./convert"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) cimg_std::strcpy(st_path,"convert"); +#endif + winformat_string(st_path); + } + return st_path; + } + + //! Return path of the GraphicsMagick's \c gm tool. + inline const char* graphicsmagick_path(const char *const user_path=0, const bool reinit_path=false) { + static char *st_path = 0; + if (reinit_path && st_path) { delete[] st_path; st_path = 0; } + if (user_path) { + if (!st_path) st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + cimg_std::strncpy(st_path,user_path,1023); + } else if (!st_path) { + st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + bool path_found = false; + cimg_std::FILE *file = 0; +#if cimg_OS==2 + const char* pf_path = programfiles_path(); + if (!path_found) { + cimg_std::sprintf(st_path,".\\gm.exe"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + { for (int k=32; k>=10 && !path_found; --k) { + cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%.2d-\\gm.exe",pf_path,k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=9; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d-Q\\gm.exe",pf_path,k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d\\gm.exe",pf_path,k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=10 && !path_found; --k) { + cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",pf_path,k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=9; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",pf_path,k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",pf_path,k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=10 && !path_found; --k) { + cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%.2d-\\gm.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=9; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d-Q\\gm.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d\\gm.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=10 && !path_found; --k) { + cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=9; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=10 && !path_found; --k) { + cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%.2d-\\gm.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=9; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d-Q\\gm.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d\\gm.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=10 && !path_found; --k) { + cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=9; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + { for (int k=32; k>=0 && !path_found; --k) { + cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + }} + if (!path_found) cimg_std::strcpy(st_path,"gm.exe"); +#else + if (!path_found) { + cimg_std::sprintf(st_path,"./gm"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) cimg_std::strcpy(st_path,"gm"); +#endif + winformat_string(st_path); + } + return st_path; + } + + //! Return or set path of the \c XMedcon tool. + inline const char* medcon_path(const char *const user_path=0, const bool reinit_path=false) { + static char *st_path = 0; + if (reinit_path && st_path) { delete[] st_path; st_path = 0; } + if (user_path) { + if (!st_path) st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + cimg_std::strncpy(st_path,user_path,1023); + } else if (!st_path) { + st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + bool path_found = false; + cimg_std::FILE *file = 0; +#if cimg_OS==2 + const char* pf_path = programfiles_path(); + if (!path_found) { + cimg_std::sprintf(st_path,".\\medcon.bat"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) { + cimg_std::sprintf(st_path,".\\medcon.exe"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) { + cimg_std::sprintf(st_path,"%s\\XMedCon\\bin\\medcon.bat",pf_path); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) { + cimg_std::sprintf(st_path,"%s\\XMedCon\\bin\\medcon.exe",pf_path); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) cimg_std::strcpy(st_path,"medcon.bat"); +#else + if (!path_found) { + cimg_std::sprintf(st_path,"./medcon"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) cimg_std::strcpy(st_path,"medcon"); +#endif + winformat_string(st_path); + } + return st_path; + } + + //! Return or set path to the 'ffmpeg' command. + inline const char *ffmpeg_path(const char *const user_path=0, const bool reinit_path=false) { + static char *st_path = 0; + if (reinit_path && st_path) { delete[] st_path; st_path = 0; } + if (user_path) { + if (!st_path) st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + cimg_std::strncpy(st_path,user_path,1023); + } else if (!st_path) { + st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + bool path_found = false; + cimg_std::FILE *file = 0; +#if cimg_OS==2 + if (!path_found) { + cimg_std::sprintf(st_path,".\\ffmpeg.exe"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) cimg_std::strcpy(st_path,"ffmpeg.exe"); +#else + if (!path_found) { + cimg_std::sprintf(st_path,"./ffmpeg"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) cimg_std::strcpy(st_path,"ffmpeg"); +#endif + winformat_string(st_path); + } + return st_path; + } + + //! Return or set path to the 'gzip' command. + inline const char *gzip_path(const char *const user_path=0, const bool reinit_path=false) { + static char *st_path = 0; + if (reinit_path && st_path) { delete[] st_path; st_path = 0; } + if (user_path) { + if (!st_path) st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + cimg_std::strncpy(st_path,user_path,1023); + } else if (!st_path) { + st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + bool path_found = false; + cimg_std::FILE *file = 0; +#if cimg_OS==2 + if (!path_found) { + cimg_std::sprintf(st_path,".\\gzip.exe"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) cimg_std::strcpy(st_path,"gzip.exe"); +#else + if (!path_found) { + cimg_std::sprintf(st_path,"./gzip"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) cimg_std::strcpy(st_path,"gzip"); +#endif + winformat_string(st_path); + } + return st_path; + } + + //! Return or set path to the 'gunzip' command. + inline const char *gunzip_path(const char *const user_path=0, const bool reinit_path=false) { + static char *st_path = 0; + if (reinit_path && st_path) { delete[] st_path; st_path = 0; } + if (user_path) { + if (!st_path) st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + cimg_std::strncpy(st_path,user_path,1023); + } else if (!st_path) { + st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + bool path_found = false; + cimg_std::FILE *file = 0; +#if cimg_OS==2 + if (!path_found) { + cimg_std::sprintf(st_path,".\\gunzip.exe"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) cimg_std::strcpy(st_path,"gunzip.exe"); +#else + if (!path_found) { + cimg_std::sprintf(st_path,"./gunzip"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) cimg_std::strcpy(st_path,"gunzip"); +#endif + winformat_string(st_path); + } + return st_path; + } + + //! Return or set path to the 'dcraw' command. + inline const char *dcraw_path(const char *const user_path=0, const bool reinit_path=false) { + static char *st_path = 0; + if (reinit_path && st_path) { delete[] st_path; st_path = 0; } + if (user_path) { + if (!st_path) st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + cimg_std::strncpy(st_path,user_path,1023); + } else if (!st_path) { + st_path = new char[1024]; + cimg_std::memset(st_path,0,1024); + bool path_found = false; + cimg_std::FILE *file = 0; +#if cimg_OS==2 + if (!path_found) { + cimg_std::sprintf(st_path,".\\dcraw.exe"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) cimg_std::strcpy(st_path,"dcraw.exe"); +#else + if (!path_found) { + cimg_std::sprintf(st_path,"./dcraw"); + if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; } + } + if (!path_found) cimg_std::strcpy(st_path,"dcraw"); +#endif + winformat_string(st_path); + } + return st_path; + } + + //! Split a filename into two strings 'body' and 'extension'. + inline const char *split_filename(const char *const filename, char *const body=0) { + if (!filename) { if (body) body[0]='\0'; return 0; } + int l = cimg::strfind(filename,'.'); + if (l>=0) { if (body) { cimg_std::strncpy(body,filename,l); body[l]='\0'; }} + else { if (body) cimg_std::strcpy(body,filename); l = (int)cimg::strlen(filename)-1; } + return filename+l+1; + } + + //! Create a numbered version of a filename. + inline char* number_filename(const char *const filename, const int number, const unsigned int n, char *const string) { + if (!filename) { if (string) string[0]='\0'; return 0; } + char format[1024],body[1024]; + const char *ext = cimg::split_filename(filename,body); + if (n>0) cimg_std::sprintf(format,"%s_%%.%ud.%s",body,n,ext); + else cimg_std::sprintf(format,"%s_%%d.%s",body,ext); + cimg_std::sprintf(string,format,number); + return string; + } + + //! Open a file, and check for possible errors. + inline cimg_std::FILE *fopen(const char *const path, const char *const mode) { + if(!path || !mode) + throw CImgArgumentException("cimg::fopen() : File '%s', cannot open with mode '%s'.", + path?path:"(null)",mode?mode:"(null)"); + if (path[0]=='-') return (mode[0]=='r')?stdin:stdout; + cimg_std::FILE *dest = cimg_std::fopen(path,mode); + if (!dest) + throw CImgIOException("cimg::fopen() : File '%s', cannot open file %s", + path,mode[0]=='r'?"for reading.":(mode[0]=='w'?"for writing.":"."),path); + return dest; + } + + //! Close a file, and check for possible errors. + inline int fclose(cimg_std::FILE *file) { + if (!file) warn("cimg::fclose() : Can't close (null) file"); + if (!file || file==stdin || file==stdout) return 0; + const int errn = cimg_std::fclose(file); + if (errn!=0) warn("cimg::fclose() : Error %d during file closing",errn); + return errn; + } + + //! Try to guess the image format of a filename, using its magick numbers. + inline const char *file_type(cimg_std::FILE *const file, const char *const filename) { + static const char + *const _pnm = "pnm", + *const _bmp = "bmp", + *const _gif = "gif", + *const _jpeg = "jpeg", + *const _off = "off", + *const _pan = "pan", + *const _png = "png", + *const _tiff = "tiff"; + if (!filename && !file) throw CImgArgumentException("cimg::file_type() : Cannot load (null) filename."); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + const char *ftype = 0, *head; + char header[2048], item[1024]; + const unsigned char *const uheader = (unsigned char*)header; + int err; + const unsigned int siz = (unsigned int)cimg_std::fread(header,2048,1,nfile); // Read first 2048 bytes. + if (!file) cimg::fclose(nfile); + if (!ftype) { // Check for BMP format. + if (header[0]=='B' && header[1]=='M') ftype = _bmp; + } + if (!ftype) { // Check for GIF format. + if (header[0]=='G' && header[1]=='I' && header[2]=='F' && header[3]=='8' && header[5]=='a' && + (header[4]=='7' || header[4]=='9')) ftype = _gif; + } + if (!ftype) { // Check for JPEG format. + if (uheader[0]==0xFF && uheader[1]==0xD8 && uheader[2]==0xFF) ftype = _jpeg; + } + if (!ftype) { // Check for OFF format. + if (header[0]=='O' && header[1]=='F' && header[2]=='F' && header[3]=='\n') ftype = _off; + } + if (!ftype) { // Check for PAN format. + if (header[0]=='P' && header[1]=='A' && header[2]=='N' && header[3]=='D' && header[4]=='O' && + header[5]=='R' && header[6]=='E') ftype = _pan; + } + if (!ftype) { // Check for PNG format. + if (uheader[0]==0x89 && uheader[1]==0x50 && uheader[2]==0x4E && uheader[3]==0x47 && + uheader[4]==0x0D && uheader[5]==0x0A && uheader[6]==0x1A && uheader[7]==0x0A) ftype = _png; + } + if (!ftype) { // Check for PNM format. + head = header; + while (head + inline int fread(T *const ptr, const unsigned int nmemb, cimg_std::FILE *stream) { + if (!ptr || nmemb<=0 || !stream) + throw CImgArgumentException("cimg::fread() : Can't read %u x %u bytes of file pointer '%p' in buffer '%p'", + nmemb,sizeof(T),stream,ptr); + const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T); + unsigned int toread = nmemb, alread = 0, ltoread = 0, lalread = 0; + do { + ltoread = (toread*sizeof(T))0); + if (toread>0) warn("cimg::fread() : File reading problems, only %u/%u elements read",alread,nmemb); + return alread; + } + + //! Write data to a file, and check for possible errors. + template + inline int fwrite(const T *ptr, const unsigned int nmemb, cimg_std::FILE *stream) { + if (!ptr || !stream) + throw CImgArgumentException("cimg::fwrite() : Can't write %u x %u bytes of file pointer '%p' from buffer '%p'", + nmemb,sizeof(T),stream,ptr); + if (nmemb<=0) return 0; + const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T); + unsigned int towrite = nmemb, alwrite = 0, ltowrite = 0, lalwrite = 0; + do { + ltowrite = (towrite*sizeof(T))0); + if (towrite>0) warn("cimg::fwrite() : File writing problems, only %u/%u elements written",alwrite,nmemb); + return alwrite; + } + + inline const char* option(const char *const name, const int argc, const char *const *const argv, + const char *defaut, const char *const usage=0) { + static bool first = true, visu = false; + const char *res = 0; + if (first) { + first=false; + visu = (cimg::option("-h",argc,argv,(char*)0)!=0); + visu |= (cimg::option("-help",argc,argv,(char*)0)!=0); + visu |= (cimg::option("--help",argc,argv,(char*)0)!=0); + } + if (!name && visu) { + if (usage) { + cimg_std::fprintf(cimg_stdout,"\n %s%s%s",cimg::t_red,cimg::basename(argv[0]),cimg::t_normal); + cimg_std::fprintf(cimg_stdout," : %s",usage); + cimg_std::fprintf(cimg_stdout," (%s, %s)\n\n",__DATE__,__TIME__); + } + if (defaut) cimg_std::fprintf(cimg_stdout,"%s\n",defaut); + } + if (name) { + if (argc>0) { + int k = 0; + while (k Operating System : %s%-13s%s %s('cimg_OS'=%d)%s\n", + cimg::t_bold, + cimg_OS==1?"Unix":(cimg_OS==2?"Windows":"Unknow"), + cimg::t_normal,cimg::t_green, + cimg_OS, + cimg::t_normal); + + cimg_std::fprintf(cimg_stdout," > CPU endianness : %s%s Endian%s\n", + cimg::t_bold, + cimg::endianness()?"Big":"Little", + cimg::t_normal); + +#ifdef cimg_use_visualcpp6 + cimg_std::fprintf(cimg_stdout," > Using Visual C++ 6.0 : %s%-13s%s %s('cimg_use_visualcpp6' defined)%s\n", + cimg::t_bold,"Yes",cimg::t_normal,cimg::t_green,cimg::t_normal); +#endif + + cimg_std::fprintf(cimg_stdout," > Debug messages : %s%-13s%s %s('cimg_debug'=%d)%s\n", + cimg::t_bold, + cimg_debug==0?"Quiet":(cimg_debug==1?"Console":(cimg_debug==2?"Dialog":(cimg_debug==3?"Console+Warnings":"Dialog+Warnings"))), + cimg::t_normal,cimg::t_green, + cimg_debug, + cimg::t_normal); + + cimg_std::fprintf(cimg_stdout," > Stricts warnings : %s%-13s%s %s('cimg_strict_warnings' %s)%s\n", + cimg::t_bold, +#ifdef cimg_strict_warnings + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + cimg_std::fprintf(cimg_stdout," > Using VT100 messages : %s%-13s%s %s('cimg_use_vt100' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_vt100 + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + cimg_std::fprintf(cimg_stdout," > Display type : %s%-13s%s %s('cimg_display'=%d)%s\n", + cimg::t_bold, + cimg_display==0?"No display": + (cimg_display==1?"X11": + (cimg_display==2?"Windows GDI": + (cimg_display==3?"Carbon":"Unknow"))), + cimg::t_normal,cimg::t_green, + cimg_display, + cimg::t_normal); + +#if cimg_display==1 + cimg_std::fprintf(cimg_stdout," > Using XShm for X11 : %s%-13s%s %s('cimg_use_xshm' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_xshm + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + cimg_std::fprintf(cimg_stdout," > Using XRand for X11 : %s%-13s%s %s('cimg_use_xrandr' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_xrandr + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); +#endif + cimg_std::fprintf(cimg_stdout," > Using OpenMP : %s%-13s%s %s('cimg_use_openmp' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_openmp + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + cimg_std::fprintf(cimg_stdout," > Using PNG library : %s%-13s%s %s('cimg_use_png' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_png + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + cimg_std::fprintf(cimg_stdout," > Using JPEG library : %s%-13s%s %s('cimg_use_jpeg' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_jpeg + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + cimg_std::fprintf(cimg_stdout," > Using TIFF library : %s%-13s%s %s('cimg_use_tiff' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_tiff + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + cimg_std::fprintf(cimg_stdout," > Using Magick++ library : %s%-13s%s %s('cimg_use_magick' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_magick + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + cimg_std::fprintf(cimg_stdout," > Using FFTW3 library : %s%-13s%s %s('cimg_use_fftw3' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_fftw3 + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + cimg_std::fprintf(cimg_stdout," > Using LAPACK library : %s%-13s%s %s('cimg_use_lapack' %s)%s\n", + cimg::t_bold, +#ifdef cimg_use_lapack + "Yes",cimg::t_normal,cimg::t_green,"defined", +#else + "No",cimg::t_normal,cimg::t_green,"undefined", +#endif + cimg::t_normal); + + cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::imagemagick_path()); + cimg_std::fprintf(cimg_stdout," > Path of ImageMagick : %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::graphicsmagick_path()); + cimg_std::fprintf(cimg_stdout," > Path of GraphicsMagick : %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::medcon_path()); + cimg_std::fprintf(cimg_stdout," > Path of 'medcon' : %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::temporary_path()); + cimg_std::fprintf(cimg_stdout," > Temporary path : %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_std::fprintf(cimg_stdout,"\n"); + } + + // Declare LAPACK function signatures if necessary. + // +#ifdef cimg_use_lapack + template + inline void getrf(int &N, T *lapA, int *IPIV, int &INFO) { + dgetrf_(&N,&N,lapA,&N,IPIV,&INFO); + } + + inline void getrf(int &N, float *lapA, int *IPIV, int &INFO) { + sgetrf_(&N,&N,lapA,&N,IPIV,&INFO); + } + + template + inline void getri(int &N, T *lapA, int *IPIV, T* WORK, int &LWORK, int &INFO) { + dgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO); + } + + inline void getri(int &N, float *lapA, int *IPIV, float* WORK, int &LWORK, int &INFO) { + sgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO); + } + + template + inline void gesvd(char &JOB, int &M, int &N, T *lapA, int &MN, + T *lapS, T *lapU, T *lapV, T *WORK, int &LWORK, int &INFO) { + dgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO); + } + + inline void gesvd(char &JOB, int &M, int &N, float *lapA, int &MN, + float *lapS, float *lapU, float *lapV, float *WORK, int &LWORK, int &INFO) { + sgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO); + } + + template + inline void getrs(char &TRANS, int &N, T *lapA, int *IPIV, T *lapB, int &INFO) { + int one = 1; + dgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO); + } + + inline void getrs(char &TRANS, int &N, float *lapA, int *IPIV, float *lapB, int &INFO) { + int one = 1; + sgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO); + } + + template + inline void syev(char &JOB, char &UPLO, int &N, T *lapA, T *lapW, T *WORK, int &LWORK, int &INFO) { + dsyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO); + } + + inline void syev(char &JOB, char &UPLO, int &N, float *lapA, float *lapW, float *WORK, int &LWORK, int &INFO) { + ssyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO); + } +#endif + + // End of the 'cimg' namespace + } + + /*------------------------------------------------ + # + # + # Definition of mathematical operators and + # external functions. + # + # + -------------------------------------------------*/ + // + // These functions are extern to any classes and can be used for a "functional-style" programming, + // such as writting : + // cos(img); + // instead of img.get_cos(); + // + // Note that only the arithmetic operators and functions are implemented here. + // + +#ifdef cimg_use_visualcpp6 + template + inline CImg operator+(const CImg& img, const t val) { + return CImg(img,false)+=val; + } +#else + template + inline CImg::type> operator+(const CImg& img, const t2 val) { + typedef typename cimg::superset::type t1t2; + return CImg(img,false)+=val; + } +#endif + +#ifdef cimg_use_visualcpp6 + template + inline CImg operator+(const t val, const CImg& img) { + return img + val; + } +#else + template + inline CImg::type> operator+(const t1 val, const CImg& img) { + return img + val; + } +#endif + +#ifdef cimg_use_visualcpp6 + template + inline CImgList operator+(const CImgList& list, const t val) { + return CImgList(list)+=val; + } +#else + template + inline CImgList::type> operator+(const CImgList& list, const t2 val) { + typedef typename cimg::superset::type t1t2; + return CImgList(list)+=val; + } +#endif + +#ifdef cimg_use_visualcpp6 + template + inline CImgList operator+(const t val, const CImgList& list) { + return list + val; + } +#else + template + inline CImgList::type> operator+(const t1 val, const CImgList& list) { + return list + val; + } +#endif + + template + inline CImg::type> operator+(const CImg& img1, const CImg& img2) { + typedef typename cimg::superset::type t1t2; + return CImg(img1,false)+=img2; + } + + template + inline CImgList::type> operator+(const CImg& img, const CImgList& list) { + typedef typename cimg::superset::type t1t2; + return CImgList(list)+=img; + } + + template + inline CImgList::type> operator+(const CImgList& list, const CImg& img) { + return img + list; + } + + template + inline CImgList::type> operator+(const CImgList& list1, const CImgList& list2) { + typedef typename cimg::superset::type t1t2; + return CImgList(list1)+=list2; + } + +#ifdef cimg_use_visualcpp6 + template + inline CImg operator-(const CImg& img, const t val) { + return CImg(img,false)-=val; + } +#else + template + inline CImg::type> operator-(const CImg& img, const t2 val) { + typedef typename cimg::superset::type t1t2; + return CImg(img,false)-=val; + } +#endif + +#ifdef cimg_use_visualcpp6 + template + inline CImg operator-(const t val, const CImg& img) { + return CImg(img.width,img.height,img.depth,img.dim,val)-=img; + } +#else + template + inline CImg::type> operator-(const t1 val, const CImg& img) { + typedef typename cimg::superset::type t1t2; + return CImg(img.width,img.height,img.depth,img.dim,(t1t2)val)-=img; + } +#endif + +#ifdef cimg_use_visualcpp6 + template + inline CImgList operator-(const CImgList& list, const t val) { + return CImgList(list)-=val; + } +#else + template + inline CImgList::type> operator-(const CImgList& list, const t2 val) { + typedef typename cimg::superset::type t1t2; + return CImgList(list)-=val; + } +#endif + +#ifdef cimg_use_visualcpp6 + template + inline CImgList operator-(const t val, const CImgList& list) { + CImgList res(list.size); + cimglist_for(res,l) res[l] = val - list[l]; + return res; + } +#else + template + inline CImgList::type> operator-(const t1 val, const CImgList& list) { + typedef typename cimg::superset::type t1t2; + CImgList res(list.size); + cimglist_for(res,l) res[l] = val - list[l]; + return res; + } +#endif + + template + inline CImg::type> operator-(const CImg& img1, const CImg& img2) { + typedef typename cimg::superset::type t1t2; + return CImg(img1,false)-=img2; + } + + template + inline CImgList::type> operator-(const CImg& img, const CImgList& list) { + typedef typename cimg::superset::type t1t2; + CImgList res(list.size); + cimglist_for(res,l) res[l] = img - list[l]; + return res; + } + + template + inline CImgList::type> operator-(const CImgList& list, const CImg& img) { + typedef typename cimg::superset::type t1t2; + return CImgList(list)-=img; + } + + template + inline CImgList::type> operator-(const CImgList& list1, const CImgList& list2) { + typedef typename cimg::superset::type t1t2; + return CImgList(list1)-=list2; + } + +#ifdef cimg_use_visualcpp6 + template + inline CImg operator*(const CImg& img, const double val) { + return CImg(img,false)*=val; + } +#else + template + inline CImg::type> operator*(const CImg& img, const t2 val) { + typedef typename cimg::superset::type t1t2; + return CImg(img,false)*=val; + } +#endif + +#ifdef cimg_use_visualcpp6 + template + inline CImg operator*(const double val, const CImg& img) { + return img*val; + } +#else + template + inline CImg::type> operator*(const t1 val, const CImg& img) { + return img*val; + } +#endif + +#ifdef cimg_use_visualcpp6 + template + inline CImgList operator*(const CImgList& list, const double val) { + return CImgList(list)*=val; + } +#else + template + inline CImgList::type> operator*(const CImgList& list, const t2 val) { + typedef typename cimg::superset::type t1t2; + return CImgList(list)*=val; + } +#endif + +#ifdef cimg_use_visualcpp6 + template + inline CImgList operator*(const double val, const CImgList& list) { + return list*val; + } +#else + template + inline CImgList::type> operator*(const t1 val, const CImgList& list) { + return list*val; + } +#endif + + template + inline CImg::type> operator*(const CImg& img1, const CImg& img2) { + typedef typename cimg::superset::type t1t2; + if (img1.width!=img2.height) + throw CImgArgumentException("operator*() : can't multiply a matrix (%ux%u) by a matrix (%ux%u)", + img1.width,img1.height,img2.width,img2.height); + CImg res(img2.width,img1.height); + t1t2 val; +#ifdef cimg_use_openmp +#pragma omp parallel for if (img1.size()>=1000 && img2.size()>=1000) private(val) +#endif + cimg_forXY(res,i,j) { val = 0; cimg_forX(img1,k) val+=img1(k,j)*img2(i,k); res(i,j) = val; } + return res; + } + + template + inline CImgList::type> operator*(const CImg& img, const CImgList& list) { + typedef typename cimg::superset::type t1t2; + CImgList res(list.size); + cimglist_for(res,l) res[l] = img*list[l]; + return res; + } + + template + inline CImgList::type> operator*(const CImgList& list, const CImg& img) { + typedef typename cimg::superset::type t1t2; + CImgList res(list.size); + cimglist_for(res,l) res[l] = list[l]*img; + return res; + } + + template + inline CImgList::type> operator*(const CImgList& list1, const CImgList& list2) { + typedef typename cimg::superset::type t1t2; + CImgList res(cimg::min(list1.size,list2.size)); + cimglist_for(res,l) res[l] = list1[l]*list2[l]; + return res; + } + +#ifdef cimg_use_visualcpp6 + template + inline CImg operator/(const CImg& img, const double val) { + return CImg(img,false)/=val; + } +#else + template + inline CImg::type> operator/(const CImg& img, const t2 val) { + typedef typename cimg::superset::type t1t2; + return CImg(img,false)/=val; + } +#endif + +#ifdef cimg_use_visualcpp6 + template + inline CImg operator/(const double val, CImg& img) { + return val*img.get_invert(); + } +#else + template + inline CImg::type> operator/(const t1 val, CImg& img) { + return val*img.get_invert(); + } +#endif + +#ifdef cimg_use_visualcpp6 + template + inline CImgList operator/(const CImgList& list, const double val) { + return CImgList(list)/=val; + } +#else + template + inline CImgList::type> operator/(const CImgList& list, const t2 val) { + typedef typename cimg::superset::type t1t2; + return CImgList(list)/=val; + } +#endif + +#ifdef cimg_use_visualcpp6 + template + inline CImgList operator/(const double val, const CImgList& list) { + CImgList res(list.size); + cimglist_for(res,l) res[l] = val/list[l]; + return res; + } +#else + template + inline CImgList::type> operator/(const t1 val, const CImgList& list) { + typedef typename cimg::superset::type t1t2; + CImgList res(list.size); + cimglist_for(res,l) res[l] = val/list[l]; + return res; + } +#endif + + template + inline CImg::type> operator/(const CImg& img1, const CImg& img2) { + typedef typename cimg::superset::type t1t2; + return CImg(img1,false)*=img2.get_invert(); + } + + template + inline CImg::type> operator/(const CImg& img, const CImgList& list) { + typedef typename cimg::superset::type t1t2; + CImgList res(list.size); + cimglist_for(res,l) res[l] = img/list[l]; + return res; + } + + template + inline CImgList::type> operator/(const CImgList& list, const CImg& img) { + typedef typename cimg::superset::type t1t2; + return CImgList(list)/=img; + } + + template + inline CImgList::type> operator/(const CImgList& list1, const CImgList& list2) { + typedef typename cimg::superset::type t1t2; + return CImgList(list1)/=list2; + } + + template + inline CImg<_cimg_Tfloat> sqr(const CImg& instance) { + return instance.get_sqr(); + } + + template + inline CImg<_cimg_Tfloat> sqrt(const CImg& instance) { + return instance.get_sqrt(); + } + + template + inline CImg<_cimg_Tfloat> exp(const CImg& instance) { + return instance.get_exp(); + } + + template + inline CImg<_cimg_Tfloat> log(const CImg& instance) { + return instance.get_log(); + } + + template + inline CImg<_cimg_Tfloat> log10(const CImg& instance) { + return instance.get_log10(); + } + + template + inline CImg<_cimg_Tfloat> abs(const CImg& instance) { + return instance.get_abs(); + } + + template + inline CImg<_cimg_Tfloat> cos(const CImg& instance) { + return instance.get_cos(); + } + + template + inline CImg<_cimg_Tfloat> sin(const CImg& instance) { + return instance.get_sin(); + } + + template + inline CImg<_cimg_Tfloat> tan(const CImg& instance) { + return instance.get_tan(); + } + + template + inline CImg<_cimg_Tfloat> acos(const CImg& instance) { + return instance.get_acos(); + } + + template + inline CImg<_cimg_Tfloat> asin(const CImg& instance) { + return instance.get_asin(); + } + + template + inline CImg<_cimg_Tfloat> atan(const CImg& instance) { + return instance.get_atan(); + } + + template + inline CImg transpose(const CImg& instance) { + return instance.get_transpose(); + } + + template + inline CImg<_cimg_Tfloat> invert(const CImg& instance) { + return instance.get_invert(); + } + + template + inline CImg<_cimg_Tfloat> pseudoinvert(const CImg& instance) { + return instance.get_pseudoinvert(); + } + + /*------------------------------------------- + # + # + # + # Definition of the CImgDisplay structure + # + # + # + --------------------------------------------*/ + + //! This class represents a window which can display \ref CImg images and handles mouse and keyboard events. + /** + Creating a \c CImgDisplay instance opens a window that can be used to display a \c CImg image + of a \c CImgList image list inside. When a display is created, associated window events + (such as mouse motion, keyboard and window size changes) are handled and can be easily + detected by testing specific \c CImgDisplay data fields. + See \ref cimg_displays for a complete tutorial on using the \c CImgDisplay class. + **/ + + struct CImgDisplay { + + //! Width of the display + unsigned int width; + + //! Height of the display + unsigned int height; + + //! Normalization type used for the display + unsigned int normalization; + + //! Display title + char* title; + + //! X-pos of the display on the screen + volatile int window_x; + + //! Y-pos of the display on the screen + volatile int window_y; + + //! Width of the underlying window + volatile unsigned int window_width; + + //! Height of the underlying window + volatile unsigned int window_height; + + //! X-coordinate of the mouse pointer on the display + volatile int mouse_x; + + //! Y-coordinate of the mouse pointer on the display + volatile int mouse_y; + + //! Button state of the mouse + volatile unsigned int buttons[512]; + volatile unsigned int& button; + + //! Wheel state of the mouse + volatile int wheel; + + //! Key value if pressed + volatile unsigned int& key; + volatile unsigned int keys[512]; + + //! Key value if released + volatile unsigned int& released_key; + volatile unsigned int released_keys[512]; + + //! Closed state of the window + volatile bool is_closed; + + //! Resized state of the window + volatile bool is_resized; + + //! Moved state of the window + volatile bool is_moved; + + //! Event state of the window + volatile bool is_event; + + //! Current state of the corresponding key (exists for all referenced keys). + volatile bool is_keyESC; + volatile bool is_keyF1; + volatile bool is_keyF2; + volatile bool is_keyF3; + volatile bool is_keyF4; + volatile bool is_keyF5; + volatile bool is_keyF6; + volatile bool is_keyF7; + volatile bool is_keyF8; + volatile bool is_keyF9; + volatile bool is_keyF10; + volatile bool is_keyF11; + volatile bool is_keyF12; + volatile bool is_keyPAUSE; + volatile bool is_key1; + volatile bool is_key2; + volatile bool is_key3; + volatile bool is_key4; + volatile bool is_key5; + volatile bool is_key6; + volatile bool is_key7; + volatile bool is_key8; + volatile bool is_key9; + volatile bool is_key0; + volatile bool is_keyBACKSPACE; + volatile bool is_keyINSERT; + volatile bool is_keyHOME; + volatile bool is_keyPAGEUP; + volatile bool is_keyTAB; + volatile bool is_keyQ; + volatile bool is_keyW; + volatile bool is_keyE; + volatile bool is_keyR; + volatile bool is_keyT; + volatile bool is_keyY; + volatile bool is_keyU; + volatile bool is_keyI; + volatile bool is_keyO; + volatile bool is_keyP; + volatile bool is_keyDELETE; + volatile bool is_keyEND; + volatile bool is_keyPAGEDOWN; + volatile bool is_keyCAPSLOCK; + volatile bool is_keyA; + volatile bool is_keyS; + volatile bool is_keyD; + volatile bool is_keyF; + volatile bool is_keyG; + volatile bool is_keyH; + volatile bool is_keyJ; + volatile bool is_keyK; + volatile bool is_keyL; + volatile bool is_keyENTER; + volatile bool is_keySHIFTLEFT; + volatile bool is_keyZ; + volatile bool is_keyX; + volatile bool is_keyC; + volatile bool is_keyV; + volatile bool is_keyB; + volatile bool is_keyN; + volatile bool is_keyM; + volatile bool is_keySHIFTRIGHT; + volatile bool is_keyARROWUP; + volatile bool is_keyCTRLLEFT; + volatile bool is_keyAPPLEFT; + volatile bool is_keyALT; + volatile bool is_keySPACE; + volatile bool is_keyALTGR; + volatile bool is_keyAPPRIGHT; + volatile bool is_keyMENU; + volatile bool is_keyCTRLRIGHT; + volatile bool is_keyARROWLEFT; + volatile bool is_keyARROWDOWN; + volatile bool is_keyARROWRIGHT; + volatile bool is_keyPAD0; + volatile bool is_keyPAD1; + volatile bool is_keyPAD2; + volatile bool is_keyPAD3; + volatile bool is_keyPAD4; + volatile bool is_keyPAD5; + volatile bool is_keyPAD6; + volatile bool is_keyPAD7; + volatile bool is_keyPAD8; + volatile bool is_keyPAD9; + volatile bool is_keyPADADD; + volatile bool is_keyPADSUB; + volatile bool is_keyPADMUL; + volatile bool is_keyPADDIV; + + //! Fullscreen state of the display + bool is_fullscreen; + + float fps_fps, min, max; + unsigned long timer, fps_frames, fps_timer; + +#ifdef cimgdisplay_plugin +#include cimgdisplay_plugin +#endif +#ifdef cimgdisplay_plugin1 +#include cimgdisplay_plugin1 +#endif +#ifdef cimgdisplay_plugin2 +#include cimgdisplay_plugin2 +#endif +#ifdef cimgdisplay_plugin3 +#include cimgdisplay_plugin3 +#endif +#ifdef cimgdisplay_plugin4 +#include cimgdisplay_plugin4 +#endif +#ifdef cimgdisplay_plugin5 +#include cimgdisplay_plugin5 +#endif +#ifdef cimgdisplay_plugin6 +#include cimgdisplay_plugin6 +#endif +#ifdef cimgdisplay_plugin7 +#include cimgdisplay_plugin7 +#endif +#ifdef cimgdisplay_plugin8 +#include cimgdisplay_plugin8 +#endif + + //! Create an empty display window. + CImgDisplay(): + width(0),height(0),normalization(0),title(0), + window_x(0),window_y(0),window_width(0),window_height(0), + mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys), + is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false), + min(0),max(0) {} + + //! Create a display window with a specified size \p pwidth x \p height. + /** \param dimw Width of the display window. + \param dimh Height of the display window. + \param title Title of the display window. + \param normalization_type Normalization type of the display window (0=none, 1=always, 2=once). + \param fullscreen_flag : Fullscreen mode. + \param closed_flag : Initially visible mode. + A black image will be initially displayed in the display window. + **/ + CImgDisplay(const unsigned int dimw, const unsigned int dimh, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false): + width(0),height(0),normalization(0),title(0), + window_x(0),window_y(0),window_width(0),window_height(0), + mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys), + is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false), + min(0),max(0) { + assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag); + } + + //! Create a display window from an image. + /** \param img : Image that will be used to create the display window. + \param title : Title of the display window + \param normalization_type : Normalization type of the display window. + \param fullscreen_flag : Fullscreen mode. + \param closed_flag : Initially visible mode. + **/ + template + CImgDisplay(const CImg& img, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false): + width(0),height(0),normalization(0),title(0), + window_x(0),window_y(0),window_width(0),window_height(0), + mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys), + is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),min(0),max(0) { + assign(img,title,normalization_type,fullscreen_flag,closed_flag); + } + + //! Create a display window from an image list. + /** \param list : The list of images to display. + \param title : Title of the display window + \param normalization_type : Normalization type of the display window. + \param fullscreen_flag : Fullscreen mode. + \param closed_flag : Initially visible mode. + **/ + template + CImgDisplay(const CImgList& list, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false): + width(0),height(0),normalization(0),title(0), + window_x(0),window_y(0),window_width(0),window_height(0), + mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys), + is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),min(0),max(0) { + assign(list,title,normalization_type,fullscreen_flag,closed_flag); + } + + //! Create a display window by copying another one. + /** + \param disp : Display window to copy. + **/ + CImgDisplay(const CImgDisplay& disp): + width(0),height(0),normalization(0),title(0), + window_x(0),window_y(0),window_width(0),window_height(0), + mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys), + is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),min(0),max(0) { + assign(disp); + } + + //! Destructor. + ~CImgDisplay() { + assign(); + } + + //! Assignment operator. + CImgDisplay& operator=(const CImgDisplay& disp) { + return assign(disp); + } + + //! Return true is display is empty. + bool is_empty() const { + return (!width || !height); + } + + //! Return true if display is not empty. + operator bool() const { + return !is_empty(); + } + + //! Return display width. + int dimx() const { + return (int)width; + } + + //! Return display height. + int dimy() const { + return (int)height; + } + + //! Return display window width. + int window_dimx() const { + return (int)window_width; + } + + //! Return display window height. + int window_dimy() const { + return (int)window_height; + } + + //! Return X-coordinate of the window. + int window_posx() const { + return window_x; + } + + //! Return Y-coordinate of the window. + int window_posy() const { + return window_y; + } + + //! Synchronized waiting function. Same as cimg::wait(). + CImgDisplay& wait(const unsigned int milliseconds) { + cimg::_sleep(milliseconds,timer); + return *this; + } + + //! Wait for an event occuring on the current display. + CImgDisplay& wait() { + if (!is_empty()) wait(*this); + return *this; + } + + //! Wait for any event occuring on the display \c disp1. + static void wait(CImgDisplay& disp1) { + disp1.is_event = 0; + while (!disp1.is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1 or \c disp2. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2) { + disp1.is_event = disp2.is_event = 0; + while (!disp1.is_event && !disp2.is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2 or \c disp3. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3) { + disp1.is_event = disp2.is_event = disp3.is_event = 0; + while (!disp1.is_event && !disp2.is_event && !disp3.is_event) wait_all(); + } + + //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3 or \c disp4. + static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4) { + disp1.is_event = disp2.is_event = disp3.is_event = disp4.is_event = 0; + while (!disp1.is_event && !disp2.is_event && !disp3.is_event && !disp4.is_event) wait_all(); + } + + //! Return the frame per second rate. + float frames_per_second() { + if (!fps_timer) fps_timer = cimg::time(); + const float delta = (cimg::time()-fps_timer)/1000.0f; + ++fps_frames; + if (delta>=1) { + fps_fps = fps_frames/delta; + fps_frames = 0; + fps_timer = cimg::time(); + } + return fps_fps; + } + + //! Display an image list CImgList into a display window. + /** First, all images of the list are appended into a single image used for visualization, + then this image is displayed in the current display window. + \param list : The list of images to display. + \param axis : The axis used to append the image for visualization. Can be 'x' (default),'y','z' or 'v'. + \param align : Defines the relative alignment of images when displaying images of different sizes. + Can be '\p c' (centered, which is the default), '\p p' (top alignment) and '\p n' (bottom aligment). + **/ + template + CImgDisplay& display(const CImgList& list, const char axis='x', const char align='p') { + return display(list.get_append(axis,align)); + } + + //! Display an image CImg into a display window. + template + CImgDisplay& operator<<(const CImg& img) { + return display(img); + } + + //! Display an image CImg into a display window. + template + CImgDisplay& operator<<(const CImgList& list) { + return display(list); + } + + //! Resize a display window with the size of an image. + /** \param img : Input image. \p image.width and \p image.height give the new dimensions of the display window. + \param redraw : If \p true (default), the current displayed image in the display window will + be bloc-interpolated to fit the new dimensions. If \p false, a black image will be drawn in the resized window. + **/ + template + CImgDisplay& resize(const CImg& img, const bool redraw=true) { + return resize(img.width,img.height,redraw); + } + + //! Resize a display window using the size of the given display \p disp. + CImgDisplay& resize(const CImgDisplay& disp, const bool redraw=true) { + return resize(disp.width,disp.height,redraw); + } + + //! Resize a display window in its current size. + CImgDisplay& resize(const bool redraw=true) { + resize(window_width,window_height,redraw); + return *this; + } + + //! Set fullscreen mode. + CImgDisplay& fullscreen(const bool redraw=true) { + if (is_empty() || is_fullscreen) return *this; + return toggle_fullscreen(redraw); + } + + //! Set normal screen mode. + CImgDisplay& normalscreen(const bool redraw=true) { + if (is_empty() || !is_fullscreen) return *this; + return toggle_fullscreen(redraw); + } + + // Inner routine used for fast resizing of buffer to display size. + template + static void _render_resize(const T *ptrs, const unsigned int ws, const unsigned int hs, + t *ptrd, const unsigned int wd, const unsigned int hd) { + unsigned int *const offx = new unsigned int[wd], *const offy = new unsigned int[hd+1], *poffx, *poffy; + float s, curr, old; + s = (float)ws/wd; + poffx = offx; curr = 0; for (unsigned int x=0; x=keys; --ptrs) if (*ptrs) { if (remove) *ptrs = 0; return true; } + return false; + } + + //! Test if a key has been pressed. + bool is_key(const unsigned int key1, const bool remove) { + for (unsigned int *ptrs=(unsigned int*)keys+512-1; ptrs>=keys; --ptrs) if (*ptrs==key1) { if (remove) *ptrs = 0; return true; } + return false; + } + + //! Test if a key sequence has been typed. + bool is_key(const unsigned int key1, const unsigned int key2, const bool remove) { + const unsigned int seq[] = { key1, key2 }; + return is_key(seq,2,remove); + } + + //! Test if a key sequence has been typed. + bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3, const bool remove) { + const unsigned int seq[] = { key1, key2, key3 }; + return is_key(seq,3,remove); + } + + //! Test if a key sequence has been typed. + bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3, + const unsigned int key4, const bool remove) { + const unsigned int seq[] = { key1, key2, key3, key4 }; + return is_key(seq,4,remove); + } + + //! Test if a key sequence has been typed. + bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3, + const unsigned int key4, const unsigned int key5, const bool remove) { + const unsigned int seq[] = { key1, key2, key3, key4, key5 }; + return is_key(seq,5,remove); + } + + //! Test if a key sequence has been typed. + bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3, + const unsigned int key4, const unsigned int key5, const unsigned int key6, const bool remove) { + const unsigned int seq[] = { key1, key2, key3, key4, key5, key6 }; + return is_key(seq,6,remove); + } + + //! Test if a key sequence has been typed. + bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3, + const unsigned int key4, const unsigned int key5, const unsigned int key6, + const unsigned int key7, const bool remove) { + const unsigned int seq[] = { key1, key2, key3, key4, key5, key6, key7 }; + return is_key(seq,7,remove); + } + + //! Test if a key sequence has been typed. + bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3, + const unsigned int key4, const unsigned int key5, const unsigned int key6, + const unsigned int key7, const unsigned int key8, const bool remove) { + const unsigned int seq[] = { key1, key2, key3, key4, key5, key6, key7, key8 }; + return is_key(seq,8,remove); + } + + //! Test if a key sequence has been typed. + bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3, + const unsigned int key4, const unsigned int key5, const unsigned int key6, + const unsigned int key7, const unsigned int key8, const unsigned int key9, const bool remove) { + const unsigned int seq[] = { key1, key2, key3, key4, key5, key6, key7, key8, key9 }; + return is_key(seq,9,remove); + } + + //! Test if a key sequence has been typed. + bool is_key(const unsigned int *const keyseq, const unsigned int N, const bool remove=true) { + if (keyseq && N) { + const unsigned int *const ps_end = keyseq+N-1, k = *ps_end, *const pk_end = (unsigned int*)keys+1+512-N; + for (unsigned int *pk = (unsigned int*)keys; pk1?dz:0), nh = dy + (dz>1?dz:0); + const unsigned int + sw = CImgDisplay::screen_dimx(), sh = CImgDisplay::screen_dimy(), + mw = dmin<0?(unsigned int)(sw*-dmin/100):(unsigned int)dmin, + mh = dmin<0?(unsigned int)(sh*-dmin/100):(unsigned int)dmin, + Mw = dmax<0?(unsigned int)(sw*-dmax/100):(unsigned int)dmax, + Mh = dmax<0?(unsigned int)(sh*-dmax/100):(unsigned int)dmax; + if (nwMw) { nh = nh*Mw/nw; nh+=(nh==0); nw = Mw; } + if (nh>Mh) { nw = nw*Mh/nh; nw+=(nw==0); nh = Mh; } + if (nw + CImgDisplay& assign(const CImg& img, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + throw CImgDisplayException("CImgDisplay()::assign() : Display has been required but is not available (cimg_display=0)"); + const char* avoid_warning = title + img.width + normalization_type + (int)fullscreen_flag + (int)closed_flag; + avoid_warning = 0; + return assign(0,0); + } + + //! In-place version of the previous constructor. + template + CImgDisplay& assign(const CImgList& list, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + throw CImgDisplayException("CImgDisplay()::assign() : Display has been required but is not available (cimg_display=0)"); + const char* avoid_warning = title + list.size + normalization_type + (int)fullscreen_flag + (int)closed_flag; + avoid_warning = 0; + return assign(0,0); + } + + //! In-place version of the previous constructor. + CImgDisplay& assign(const CImgDisplay &disp) { + return assign(disp.width,disp.height); + } + + //! Resize window. + CImgDisplay& resize(const int width, const int height, const bool redraw=true) { + int avoid_warning = width | height | (int)redraw; + avoid_warning = 0; + return *this; + } + + //! Toggle fullscreen mode. + CImgDisplay& toggle_fullscreen(const bool redraw=true) { + bool avoid_warning = redraw; + avoid_warning = false; + return *this; + } + + //! Show a closed display. + CImgDisplay& show() { + return *this; + } + + //! Close a visible display. + CImgDisplay& close() { + return *this; + } + + //! Move window. + CImgDisplay& move(const int posx, const int posy) { + int avoid_warning = posx | posy; + avoid_warning = 0; + return *this; + } + + //! Show mouse pointer. + CImgDisplay& show_mouse() { + return *this; + } + + //! Hide mouse pointer. + CImgDisplay& hide_mouse() { + return *this; + } + + //! Move mouse pointer to a specific location. + CImgDisplay& set_mouse(const int posx, const int posy) { + int avoid_warning = posx | posy; + avoid_warning = 0; + return *this; + } + + //! Set the window title. + CImgDisplay& set_title(const char *format, ...) { + const char *avoid_warning = format; + avoid_warning = 0; + return *this; + } + + //! Display an image in a window. + template + CImgDisplay& display(const CImg& img) { + unsigned int avoid_warning = img.width; + avoid_warning = 0; + return *this; + } + + //! Re-paint image content in window. + CImgDisplay& paint() { + return *this; + } + + //! Render image buffer into GDI native image format. + template + CImgDisplay& render(const CImg& img) { + unsigned int avoid_warning = img.width; + avoid_warning = 0; + return *this; + } + + //! Take a snapshot of the display in the specified image. + template + const CImgDisplay& snapshot(CImg& img) const { + img.assign(width,height,1,3,0); + return *this; + } + + // X11-based display + //------------------- +#elif cimg_display==1 + Atom wm_delete_window, wm_delete_protocol; + Window window, background_window; + Colormap colormap; + XImage *image; + void *data; +#ifdef cimg_use_xshm + XShmSegmentInfo *shminfo; +#endif + + static int screen_dimx() { + int res = 0; + if (!cimg::X11attr().display) { + Display *disp = XOpenDisplay((cimg_std::getenv("DISPLAY")?cimg_std::getenv("DISPLAY"):":0.0")); + if (!disp) + throw CImgDisplayException("CImgDisplay::screen_dimx() : Can't open X11 display."); + res = DisplayWidth(disp,DefaultScreen(disp)); + XCloseDisplay(disp); + } else { +#ifdef cimg_use_xrandr + if (cimg::X11attr().resolutions && cimg::X11attr().curr_resolution) + res = cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].width; + else +#endif + res = DisplayWidth(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)); + } + return res; + } + + static int screen_dimy() { + int res = 0; + if (!cimg::X11attr().display) { + Display *disp = XOpenDisplay((cimg_std::getenv("DISPLAY") ? cimg_std::getenv("DISPLAY") : ":0.0")); + if (!disp) + throw CImgDisplayException("CImgDisplay::screen_dimy() : Can't open X11 display."); + res = DisplayHeight(disp,DefaultScreen(disp)); + XCloseDisplay(disp); + } else { +#ifdef cimg_use_xrandr + if (cimg::X11attr().resolutions && cimg::X11attr().curr_resolution) + res = cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].height; + else +#endif + res = DisplayHeight(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)); + } + return res; + } + + static void wait_all() { + if (cimg::X11attr().display) { + XLockDisplay(cimg::X11attr().display); + bool flag = true; + XEvent event; + while (flag) { + XNextEvent(cimg::X11attr().display, &event); + for (unsigned int i = 0; iis_closed && event.xany.window==cimg::X11attr().wins[i]->window) { + cimg::X11attr().wins[i]->_handle_events(&event); + if (cimg::X11attr().wins[i]->is_event) flag = false; + } + } + XUnlockDisplay(cimg::X11attr().display); + } + } + + void _handle_events(const XEvent *const pevent) { + XEvent event = *pevent; + switch (event.type) { + case ClientMessage : { + if ((int)event.xclient.message_type==(int)wm_delete_protocol && + (int)event.xclient.data.l[0]==(int)wm_delete_window) { + XUnmapWindow(cimg::X11attr().display,window); + mouse_x = mouse_y = -1; + if (button) { cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button = 0; } + if (key) { cimg_std::memmove((void*)(keys+1),(void*)keys,512-1); key = 0; } + if (released_key) { cimg_std::memmove((void*)(released_keys+1),(void*)released_keys,512-1); released_key = 0; } + is_closed = is_event = true; + } + } break; + case ConfigureNotify : { + while (XCheckWindowEvent(cimg::X11attr().display,window,StructureNotifyMask,&event)) {} + const unsigned int + nw = event.xconfigure.width, + nh = event.xconfigure.height; + const int + nx = event.xconfigure.x, + ny = event.xconfigure.y; + if (nw && nh && (nw!=window_width || nh!=window_height)) { + window_width = nw; + window_height = nh; + mouse_x = mouse_y = -1; + XResizeWindow(cimg::X11attr().display,window,window_width,window_height); + is_resized = is_event = true; + } + if (nx!=window_x || ny!=window_y) { + window_x = nx; + window_y = ny; + is_moved = is_event = true; + } + } break; + case Expose : { + while (XCheckWindowEvent(cimg::X11attr().display,window,ExposureMask,&event)) {} + _paint(false); + if (is_fullscreen) { + XWindowAttributes attr; + XGetWindowAttributes(cimg::X11attr().display, window, &attr); + while (attr.map_state != IsViewable) XSync(cimg::X11attr().display, False); + XSetInputFocus(cimg::X11attr().display, window, RevertToParent, CurrentTime); + } + } break; + case ButtonPress : { + do { + mouse_x = event.xmotion.x; + mouse_y = event.xmotion.y; + if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1; + switch (event.xbutton.button) { + case 1 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button|=1; is_event = true; break; + case 2 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button|=4; is_event = true; break; + case 3 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button|=2; is_event = true; break; + } + } while (XCheckWindowEvent(cimg::X11attr().display,window,ButtonPressMask,&event)); + } break; + case ButtonRelease : { + do { + mouse_x = event.xmotion.x; + mouse_y = event.xmotion.y; + if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1; + switch (event.xbutton.button) { + case 1 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button&=~1U; is_event = true; break; + case 2 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button&=~4U; is_event = true; break; + case 3 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button&=~2U; is_event = true; break; + case 4 : ++wheel; is_event = true; break; + case 5 : --wheel; is_event = true; break; + } + } while (XCheckWindowEvent(cimg::X11attr().display,window,ButtonReleaseMask,&event)); + } break; + case KeyPress : { + char tmp; + KeySym ksym; + XLookupString(&event.xkey,&tmp,1,&ksym,0); + update_iskey((unsigned int)ksym,true); + if (key) cimg_std::memmove((void*)(keys+1),(void*)keys,512-1); + key = (unsigned int)ksym; + if (released_key) { cimg_std::memmove((void*)(released_keys+1),(void*)released_keys,512-1); released_key = 0; } + is_event = true; + } break; + case KeyRelease : { + char tmp; + KeySym ksym; + XLookupString(&event.xkey,&tmp,1,&ksym,0); + update_iskey((unsigned int)ksym,false); + if (key) { cimg_std::memmove((void*)(keys+1),(void*)keys,512-1); key = 0; } + if (released_key) cimg_std::memmove((void*)(released_keys+1),(void*)released_keys,512-1); + released_key = (unsigned int)ksym; + is_event = true; + } break; + case EnterNotify: { + while (XCheckWindowEvent(cimg::X11attr().display,window,EnterWindowMask,&event)) {} + mouse_x = event.xmotion.x; + mouse_y = event.xmotion.y; + if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1; + } break; + case LeaveNotify : { + while (XCheckWindowEvent(cimg::X11attr().display,window,LeaveWindowMask,&event)) {} + mouse_x = mouse_y =-1; + is_event = true; + } break; + case MotionNotify : { + while (XCheckWindowEvent(cimg::X11attr().display,window,PointerMotionMask,&event)) {} + mouse_x = event.xmotion.x; + mouse_y = event.xmotion.y; + if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1; + is_event = true; + } break; + } + } + + static void* _events_thread(void *arg) { + arg = 0; + XEvent event; + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0); + for (;;) { + XLockDisplay(cimg::X11attr().display); + bool event_flag = XCheckTypedEvent(cimg::X11attr().display, ClientMessage, &event); + if (!event_flag) event_flag = XCheckMaskEvent(cimg::X11attr().display, + ExposureMask|StructureNotifyMask|ButtonPressMask| + KeyPressMask|PointerMotionMask|EnterWindowMask|LeaveWindowMask| + ButtonReleaseMask|KeyReleaseMask,&event); + if (event_flag) { + for (unsigned int i=0; iis_closed && event.xany.window==cimg::X11attr().wins[i]->window) + cimg::X11attr().wins[i]->_handle_events(&event); + } + XUnlockDisplay(cimg::X11attr().display); + pthread_testcancel(); + cimg::sleep(7); + } + return 0; + } + + void _set_colormap(Colormap& colormap, const unsigned int dim) { + XColor palette[256]; + switch (dim) { + case 1 : { // palette for greyscale images + for (unsigned int index=0; index<256; ++index) { + palette[index].pixel = index; + palette[index].red = palette[index].green = palette[index].blue = (unsigned short)(index<<8); + palette[index].flags = DoRed | DoGreen | DoBlue; + } + } break; + case 2 : { // palette for RG images + for (unsigned int index=0, r=8; r<256; r+=16) + for (unsigned int g=8; g<256; g+=16) { + palette[index].pixel = index; + palette[index].red = palette[index].blue = (unsigned short)(r<<8); + palette[index].green = (unsigned short)(g<<8); + palette[index++].flags = DoRed | DoGreen | DoBlue; + } + } break; + default : { // palette for RGB images + for (unsigned int index=0, r=16; r<256; r+=32) + for (unsigned int g=16; g<256; g+=32) + for (unsigned int b=32; b<256; b+=64) { + palette[index].pixel = index; + palette[index].red = (unsigned short)(r<<8); + palette[index].green = (unsigned short)(g<<8); + palette[index].blue = (unsigned short)(b<<8); + palette[index++].flags = DoRed | DoGreen | DoBlue; + } + } + } + XStoreColors(cimg::X11attr().display,colormap,palette,256); + } + + void _map_window() { + XWindowAttributes attr; + XEvent event; + bool exposed = false, mapped = false; + XMapRaised(cimg::X11attr().display,window); + XSync(cimg::X11attr().display,False); + do { + XWindowEvent(cimg::X11attr().display,window,StructureNotifyMask | ExposureMask,&event); + switch (event.type) { + case MapNotify : mapped = true; break; + case Expose : exposed = true; break; + default : XSync(cimg::X11attr().display, False); cimg::sleep(10); + } + } while (!(exposed && mapped)); + do { + XGetWindowAttributes(cimg::X11attr().display, window, &attr); + if (attr.map_state!=IsViewable) { XSync(cimg::X11attr().display,False); cimg::sleep(10); } + } while (attr.map_state != IsViewable); + window_x = attr.x; + window_y = attr.y; + } + + void _paint(const bool wait_expose=true) { + if (!is_closed) { + if (wait_expose) { + static XEvent event; + event.xexpose.type = Expose; + event.xexpose.serial = 0; + event.xexpose.send_event = True; + event.xexpose.display = cimg::X11attr().display; + event.xexpose.window = window; + event.xexpose.x = 0; + event.xexpose.y = 0; + event.xexpose.width = dimx(); + event.xexpose.height = dimy(); + event.xexpose.count = 0; + XSendEvent(cimg::X11attr().display, window, False, 0, &event); + } else { +#ifdef cimg_use_xshm + if (shminfo) XShmPutImage(cimg::X11attr().display,window,*cimg::X11attr().gc,image,0,0,0,0,width,height,False); + else +#endif + XPutImage(cimg::X11attr().display,window,*cimg::X11attr().gc,image,0,0,0,0,width,height); + XSync(cimg::X11attr().display, False); + } + } + } + + template + void _resize(T foo, const unsigned int ndimx, const unsigned int ndimy, const bool redraw) { + foo = 0; +#ifdef cimg_use_xshm + if (shminfo) { + XShmSegmentInfo *nshminfo = new XShmSegmentInfo; + XImage *nimage = XShmCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)), + cimg::X11attr().nb_bits,ZPixmap,0,nshminfo,ndimx,ndimy); + if (!nimage) { + delete nshminfo; + return; + } else { + nshminfo->shmid = shmget(IPC_PRIVATE, ndimx*ndimy*sizeof(T), IPC_CREAT | 0777); + if (nshminfo->shmid==-1) { + XDestroyImage(nimage); + delete nshminfo; + return; + } else { + nshminfo->shmaddr = nimage->data = (char*)shmat(nshminfo->shmid,0,0); + if (nshminfo->shmaddr==(char*)-1) { + shmctl(nshminfo->shmid,IPC_RMID,0); + XDestroyImage(nimage); + delete nshminfo; + return; + } else { + nshminfo->readOnly = False; + cimg::X11attr().shm_enabled = true; + XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm); + XShmAttach(cimg::X11attr().display, nshminfo); + XSync(cimg::X11attr().display, False); + XSetErrorHandler(oldXErrorHandler); + if (!cimg::X11attr().shm_enabled) { + shmdt(nshminfo->shmaddr); + shmctl(nshminfo->shmid,IPC_RMID,0); + XDestroyImage(nimage); + delete nshminfo; + return; + } else { + T *const ndata = (T*)nimage->data; + if (redraw) _render_resize((T*)data,width,height,ndata,ndimx,ndimy); + else cimg_std::memset(ndata,0,sizeof(T)*ndimx*ndimy); + XShmDetach(cimg::X11attr().display, shminfo); + XDestroyImage(image); + shmdt(shminfo->shmaddr); + shmctl(shminfo->shmid,IPC_RMID,0); + delete shminfo; + shminfo = nshminfo; + image = nimage; + data = (void*)ndata; + } + } + } + } + } else +#endif + { + T *ndata = (T*)cimg_std::malloc(ndimx*ndimy*sizeof(T)); + if (redraw) _render_resize((T*)data,width,height,ndata,ndimx,ndimy); + else cimg_std::memset(ndata,0,sizeof(T)*ndimx*ndimy); + data = (void*)ndata; + XDestroyImage(image); + image = XCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)), + cimg::X11attr().nb_bits,ZPixmap,0,(char*)data,ndimx,ndimy,8,0); + } + } + + void _init_fullscreen() { + background_window = 0; + if (is_fullscreen && !is_closed) { +#ifdef cimg_use_xrandr + int foo; + if (XRRQueryExtension(cimg::X11attr().display,&foo,&foo)) { + XRRRotations(cimg::X11attr().display, DefaultScreen(cimg::X11attr().display), &cimg::X11attr().curr_rotation); + if (!cimg::X11attr().resolutions) { + cimg::X11attr().resolutions = XRRSizes(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display),&foo); + cimg::X11attr().nb_resolutions = (unsigned int)foo; + } + if (cimg::X11attr().resolutions) { + cimg::X11attr().curr_resolution = 0; + for (unsigned int i=0; i=width && nh>=height && + nw<=(unsigned int)(cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].width) && + nh<=(unsigned int)(cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].height)) + cimg::X11attr().curr_resolution = i; + } + if (cimg::X11attr().curr_resolution>0) { + XRRScreenConfiguration *config = XRRGetScreenInfo(cimg::X11attr().display, DefaultRootWindow(cimg::X11attr().display)); + XRRSetScreenConfig(cimg::X11attr().display, config, DefaultRootWindow(cimg::X11attr().display), + cimg::X11attr().curr_resolution, cimg::X11attr().curr_rotation, CurrentTime); + XRRFreeScreenConfigInfo(config); + XSync(cimg::X11attr().display, False); + } + } + } + if (!cimg::X11attr().resolutions) + cimg::warn("CImgDisplay::_create_window() : Xrandr extension is not supported by the X server."); +#endif + const unsigned int sx = screen_dimx(), sy = screen_dimy(); + XSetWindowAttributes winattr; + winattr.override_redirect = True; + if (sx!=width || sy!=height) { + background_window = XCreateWindow(cimg::X11attr().display, + RootWindow(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),0,0, + sx,sy,0,0,InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); + const unsigned int bufsize = sx*sy*(cimg::X11attr().nb_bits==8?1:(cimg::X11attr().nb_bits==16?2:4)); + void *background_data = cimg_std::malloc(bufsize); + cimg_std::memset(background_data,0,bufsize); + XImage *background_image = XCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)), + cimg::X11attr().nb_bits,ZPixmap,0,(char*)background_data,sx,sy,8,0); + XEvent event; + XSelectInput(cimg::X11attr().display,background_window,StructureNotifyMask); + XMapRaised(cimg::X11attr().display,background_window); + do XWindowEvent(cimg::X11attr().display,background_window,StructureNotifyMask,&event); + while (event.type!=MapNotify); +#ifdef cimg_use_xshm + if (shminfo) XShmPutImage(cimg::X11attr().display,background_window,*cimg::X11attr().gc,background_image,0,0,0,0,sx,sy,False); + else +#endif + XPutImage(cimg::X11attr().display,background_window,*cimg::X11attr().gc,background_image,0,0,0,0,sx,sy); + XWindowAttributes attr; + XGetWindowAttributes(cimg::X11attr().display, background_window, &attr); + while (attr.map_state != IsViewable) XSync(cimg::X11attr().display, False); + XDestroyImage(background_image); + } + } + } + + void _desinit_fullscreen() { + if (is_fullscreen) { + XUngrabKeyboard(cimg::X11attr().display,CurrentTime); +#ifdef cimg_use_xrandr + if (cimg::X11attr().resolutions && cimg::X11attr().curr_resolution) { + XRRScreenConfiguration *config = XRRGetScreenInfo(cimg::X11attr().display, DefaultRootWindow(cimg::X11attr().display)); + XRRSetScreenConfig(cimg::X11attr().display, config, DefaultRootWindow(cimg::X11attr().display), + 0, cimg::X11attr().curr_rotation, CurrentTime); + XRRFreeScreenConfigInfo(config); + XSync(cimg::X11attr().display, False); + cimg::X11attr().curr_resolution = 0; + } +#endif + if (background_window) XDestroyWindow(cimg::X11attr().display,background_window); + background_window = 0; + is_fullscreen = false; + } + } + + static int _assign_xshm(Display *dpy, XErrorEvent *error) { + dpy = 0; error = 0; + cimg::X11attr().shm_enabled = false; + return 0; + } + + void _assign(const unsigned int dimw, const unsigned int dimh, const char *ptitle=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + + // Allocate space for window title + const int s = cimg::strlen(ptitle)+1; + char *tmp_title = s?new char[s]:0; + if (s) cimg_std::memcpy(tmp_title,ptitle,s*sizeof(char)); + + // Destroy previous display window if existing + if (!is_empty()) assign(); + + // Open X11 display if necessary. + if (!cimg::X11attr().display) { + static bool xinit_threads = false; + if (!xinit_threads) { XInitThreads(); xinit_threads = true; } + cimg::X11attr().nb_wins = 0; + cimg::X11attr().display = XOpenDisplay((cimg_std::getenv("DISPLAY")?cimg_std::getenv("DISPLAY"):":0.0")); + if (!cimg::X11attr().display) + throw CImgDisplayException("CImgDisplay::_create_window() : Can't open X11 display"); + cimg::X11attr().nb_bits = DefaultDepth(cimg::X11attr().display, DefaultScreen(cimg::X11attr().display)); + if (cimg::X11attr().nb_bits!=8 && cimg::X11attr().nb_bits!=16 && cimg::X11attr().nb_bits!=24 && cimg::X11attr().nb_bits!=32) + throw CImgDisplayException("CImgDisplay::_create_window() : %u bits mode is not supported " + "(only 8, 16, 24 and 32 bits modes are supported)",cimg::X11attr().nb_bits); + cimg::X11attr().gc = new GC; + *cimg::X11attr().gc = DefaultGC(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)); + Visual *visual = DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)); + XVisualInfo vtemplate; + vtemplate.visualid = XVisualIDFromVisual(visual); + int nb_visuals; + XVisualInfo *vinfo = XGetVisualInfo(cimg::X11attr().display,VisualIDMask,&vtemplate,&nb_visuals); + if (vinfo && vinfo->red_maskblue_mask) cimg::X11attr().blue_first = true; + cimg::X11attr().byte_order = ImageByteOrder(cimg::X11attr().display); + XFree(vinfo); + XLockDisplay(cimg::X11attr().display); + cimg::X11attr().event_thread = new pthread_t; + pthread_create(cimg::X11attr().event_thread,0,_events_thread,0); + } else XLockDisplay(cimg::X11attr().display); + + // Set display variables + width = cimg::min(dimw,(unsigned int)screen_dimx()); + height = cimg::min(dimh,(unsigned int)screen_dimy()); + normalization = normalization_type<4?normalization_type:3; + is_fullscreen = fullscreen_flag; + window_x = window_y = 0; + is_closed = closed_flag; + title = tmp_title; + flush(); + + // Create X11 window and palette (if 8bits display) + if (is_fullscreen) { + if (!is_closed) _init_fullscreen(); + const unsigned int sx = screen_dimx(), sy = screen_dimy(); + XSetWindowAttributes winattr; + winattr.override_redirect = True; + window = XCreateWindow(cimg::X11attr().display, + RootWindow(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)), + (sx-width)/2,(sy-height)/2, + width,height,0,0,InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); + } else + window = XCreateSimpleWindow(cimg::X11attr().display, + RootWindow(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)), + 0,0,width,height,2,0,0x0L); + XStoreName(cimg::X11attr().display,window,title?title:" "); + if (cimg::X11attr().nb_bits==8) { + colormap = XCreateColormap(cimg::X11attr().display,window,DefaultVisual(cimg::X11attr().display, + DefaultScreen(cimg::X11attr().display)),AllocAll); + _set_colormap(colormap,3); + XSetWindowColormap(cimg::X11attr().display,window,colormap); + } + window_width = width; + window_height = height; + + // Create XImage + const unsigned int bufsize = width*height*(cimg::X11attr().nb_bits==8?1:(cimg::X11attr().nb_bits==16?2:4)); +#ifdef cimg_use_xshm + shminfo = 0; + if (XShmQueryExtension(cimg::X11attr().display)) { + shminfo = new XShmSegmentInfo; + image = XShmCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)), + cimg::X11attr().nb_bits,ZPixmap,0,shminfo,width,height); + if (!image) { + delete shminfo; + shminfo = 0; + } else { + shminfo->shmid = shmget(IPC_PRIVATE, bufsize, IPC_CREAT | 0777); + if (shminfo->shmid==-1) { + XDestroyImage(image); + delete shminfo; + shminfo = 0; + } else { + shminfo->shmaddr = image->data = (char*)(data = shmat(shminfo->shmid,0,0)); + if (shminfo->shmaddr==(char*)-1) { + shmctl(shminfo->shmid,IPC_RMID,0); + XDestroyImage(image); + delete shminfo; + shminfo = 0; + } else { + shminfo->readOnly = False; + cimg::X11attr().shm_enabled = true; + XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm); + XShmAttach(cimg::X11attr().display, shminfo); + XSync(cimg::X11attr().display, False); + XSetErrorHandler(oldXErrorHandler); + if (!cimg::X11attr().shm_enabled) { + shmdt(shminfo->shmaddr); + shmctl(shminfo->shmid,IPC_RMID,0); + XDestroyImage(image); + delete shminfo; + shminfo = 0; + } + } + } + } + } + if (!shminfo) +#endif + { + data = cimg_std::malloc(bufsize); + image = XCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)), + cimg::X11attr().nb_bits,ZPixmap,0,(char*)data,width,height,8,0); + } + + wm_delete_window = XInternAtom(cimg::X11attr().display, "WM_DELETE_WINDOW", False); + wm_delete_protocol = XInternAtom(cimg::X11attr().display, "WM_PROTOCOLS", False); + XSetWMProtocols(cimg::X11attr().display, window, &wm_delete_window, 1); + XSelectInput(cimg::X11attr().display,window, + ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask | + EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask); + if (is_fullscreen) XGrabKeyboard(cimg::X11attr().display, window, True, GrabModeAsync, GrabModeAsync, CurrentTime); + cimg::X11attr().wins[cimg::X11attr().nb_wins++]=this; + if (!is_closed) _map_window(); else { window_x = window_y = cimg::type::min(); } + XUnlockDisplay(cimg::X11attr().display); + } + + CImgDisplay& assign() { + if (is_empty()) return *this; + XLockDisplay(cimg::X11attr().display); + + // Remove display window from event thread list. + unsigned int i; + for (i = 0; ishmaddr); + shmctl(shminfo->shmid,IPC_RMID,0); + delete shminfo; + shminfo = 0; + } else +#endif + XDestroyImage(image); + data = 0; image = 0; + if (cimg::X11attr().nb_bits==8) XFreeColormap(cimg::X11attr().display,colormap); + colormap = 0; + XSync(cimg::X11attr().display, False); + + // Reset display variables + if (title) delete[] title; + width = height = normalization = window_width = window_height = 0; + window_x = window_y = 0; + is_fullscreen = false; + is_closed = true; + min = max = 0; + title = 0; + flush(); + + // End event thread and close display if necessary + XUnlockDisplay(cimg::X11attr().display); + + /* The code below was used to close the X11 display when not used anymore, + unfortunately, since the latest Xorg versions, it randomely hangs, so + I prefer to remove it. A fix would be needed anyway. + + if (!cimg::X11attr().nb_wins) { + // Kill event thread + pthread_cancel(*cimg::X11attr().event_thread); + XUnlockDisplay(cimg::X11attr().display); + pthread_join(*cimg::X11attr().event_thread,0); + delete cimg::X11attr().event_thread; + cimg::X11attr().event_thread = 0; + XCloseDisplay(cimg::X11attr().display); + cimg::X11attr().display = 0; + delete cimg::X11attr().gc; + cimg::X11attr().gc = 0; + } else XUnlockDisplay(cimg::X11attr().display); + */ + return *this; + } + + CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!dimw || !dimh) return assign(); + _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag); + min = max = 0; + cimg_std::memset(data,0,(cimg::X11attr().nb_bits==8?sizeof(unsigned char): + (cimg::X11attr().nb_bits==16?sizeof(unsigned short):sizeof(unsigned int)))*width*height); + return paint(); + } + + template + CImgDisplay& assign(const CImg& img, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!img) return assign(); + CImg tmp; + const CImg& nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2)); + _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag); + if (normalization==2) min = (float)nimg.minmax(max); + return render(nimg).paint(); + } + + template + CImgDisplay& assign(const CImgList& list, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!list) return assign(); + CImg tmp; + const CImg img = list.get_append('x','p'), + &nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2)); + _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag); + if (normalization==2) min = (float)nimg.minmax(max); + return render(nimg).paint(); + } + + CImgDisplay& assign(const CImgDisplay& win) { + if (!win) return assign(); + _assign(win.width,win.height,win.title,win.normalization,win.is_fullscreen,win.is_closed); + cimg_std::memcpy(data,win.data,(cimg::X11attr().nb_bits==8?sizeof(unsigned char): + cimg::X11attr().nb_bits==16?sizeof(unsigned short): + sizeof(unsigned int))*width*height); + return paint(); + } + + CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) { + if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign(); + if (is_empty()) return assign(nwidth,nheight); + const unsigned int + tmpdimx = (nwidth>0)?nwidth:(-nwidth*width/100), + tmpdimy = (nheight>0)?nheight:(-nheight*height/100), + dimx = tmpdimx?tmpdimx:1, + dimy = tmpdimy?tmpdimy:1; + XLockDisplay(cimg::X11attr().display); + if (window_width!=dimx || window_height!=dimy) XResizeWindow(cimg::X11attr().display,window,dimx,dimy); + if (width!=dimx || height!=dimy) switch (cimg::X11attr().nb_bits) { + case 8 : { unsigned char foo = 0; _resize(foo,dimx,dimy,redraw); } break; + case 16 : { unsigned short foo = 0; _resize(foo,dimx,dimy,redraw); } break; + default : { unsigned int foo = 0; _resize(foo,dimx,dimy,redraw); } + } + window_width = width = dimx; window_height = height = dimy; + is_resized = false; + XUnlockDisplay(cimg::X11attr().display); + if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2); + if (redraw) return paint(); + return *this; + } + + CImgDisplay& toggle_fullscreen(const bool redraw=true) { + if (is_empty()) return *this; + if (redraw) { + const unsigned int bufsize = width*height*(cimg::X11attr().nb_bits==8?1:(cimg::X11attr().nb_bits==16?2:4)); + void *odata = cimg_std::malloc(bufsize); + cimg_std::memcpy(odata,data,bufsize); + assign(width,height,title,normalization,!is_fullscreen,false); + cimg_std::memcpy(data,odata,bufsize); + cimg_std::free(odata); + return paint(false); + } + return assign(width,height,title,normalization,!is_fullscreen,false); + } + + CImgDisplay& show() { + if (!is_empty() && is_closed) { + XLockDisplay(cimg::X11attr().display); + if (is_fullscreen) _init_fullscreen(); + _map_window(); + is_closed = false; + XUnlockDisplay(cimg::X11attr().display); + return paint(); + } + return *this; + } + + CImgDisplay& close() { + if (!is_empty() && !is_closed) { + XLockDisplay(cimg::X11attr().display); + if (is_fullscreen) _desinit_fullscreen(); + XUnmapWindow(cimg::X11attr().display,window); + window_x = window_y = -1; + is_closed = true; + XUnlockDisplay(cimg::X11attr().display); + } + return *this; + } + + CImgDisplay& move(const int posx, const int posy) { + if (is_empty()) return *this; + show(); + XLockDisplay(cimg::X11attr().display); + XMoveWindow(cimg::X11attr().display,window,posx,posy); + window_x = posx; window_y = posy; + is_moved = false; + XUnlockDisplay(cimg::X11attr().display); + return paint(); + } + + CImgDisplay& show_mouse() { + if (is_empty()) return *this; + XLockDisplay(cimg::X11attr().display); + XDefineCursor(cimg::X11attr().display,window,None); + XUnlockDisplay(cimg::X11attr().display); + return *this; + } + + CImgDisplay& hide_mouse() { + if (is_empty()) return *this; + XLockDisplay(cimg::X11attr().display); + const char pix_data[8] = { 0 }; + XColor col; + col.red = col.green = col.blue = 0; + Pixmap pix = XCreateBitmapFromData(cimg::X11attr().display,window,pix_data,8,8); + Cursor cur = XCreatePixmapCursor(cimg::X11attr().display,pix,pix,&col,&col,0,0); + XFreePixmap(cimg::X11attr().display,pix); + XDefineCursor(cimg::X11attr().display,window,cur); + XUnlockDisplay(cimg::X11attr().display); + return *this; + } + + CImgDisplay& set_mouse(const int posx, const int posy) { + if (is_empty() || is_closed) return *this; + XLockDisplay(cimg::X11attr().display); + XWarpPointer(cimg::X11attr().display,None,window,0,0,0,0,posx,posy); + mouse_x = posx; mouse_y = posy; + is_moved = false; + XSync(cimg::X11attr().display, False); + XUnlockDisplay(cimg::X11attr().display); + return *this; + } + + CImgDisplay& set_title(const char *format, ...) { + if (is_empty()) return *this; + char tmp[1024] = {0}; + va_list ap; + va_start(ap, format); + cimg_std::vsprintf(tmp,format,ap); + va_end(ap); + if (title) delete[] title; + const int s = cimg::strlen(tmp)+1; + title = new char[s]; + cimg_std::memcpy(title,tmp,s*sizeof(char)); + XLockDisplay(cimg::X11attr().display); + XStoreName(cimg::X11attr().display,window,tmp); + XUnlockDisplay(cimg::X11attr().display); + return *this; + } + + template + CImgDisplay& display(const CImg& img) { + if (img.is_empty()) + throw CImgArgumentException("CImgDisplay::display() : Cannot display empty image."); + if (is_empty()) assign(img.width,img.height); + return render(img).paint(false); + } + + CImgDisplay& paint(const bool wait_expose=true) { + if (is_empty()) return *this; + XLockDisplay(cimg::X11attr().display); + _paint(wait_expose); + XUnlockDisplay(cimg::X11attr().display); + return *this; + } + + template + CImgDisplay& render(const CImg& img, const bool flag8=false) { + if (is_empty()) return *this; + if (!img) + throw CImgArgumentException("CImgDisplay::_render_image() : Specified input image (%u,%u,%u,%u,%p) is empty.", + img.width,img.height,img.depth,img.dim,img.data); + if (img.depth!=1) return render(img.get_projections2d(img.width/2,img.height/2,img.depth/2)); + if (cimg::X11attr().nb_bits==8 && (img.width!=width || img.height!=height)) return render(img.get_resize(width,height,1,-100,1)); + if (cimg::X11attr().nb_bits==8 && !flag8 && img.dim==3) return render(img.get_RGBtoLUT(true),true); + + const T + *data1 = img.data, + *data2 = (img.dim>1)?img.ptr(0,0,0,1):data1, + *data3 = (img.dim>2)?img.ptr(0,0,0,2):data1; + + if (cimg::X11attr().blue_first) cimg::swap(data1,data3); + XLockDisplay(cimg::X11attr().display); + + if (!normalization || (normalization==3 && cimg::type::string()==cimg::type::string())) { + min = max = 0; + switch (cimg::X11attr().nb_bits) { + case 8 : { // 256 color palette, no normalization + _set_colormap(colormap,img.dim); + unsigned char *const ndata = (img.width==width && img.height==height)?(unsigned char*)data:new unsigned char[img.width*img.height]; + unsigned char *ptrd = (unsigned char*)ndata; + switch (img.dim) { + case 1 : for (unsigned int xy = img.width*img.height; xy>0; --xy) (*ptrd++) = (unsigned char)*(data1++); + break; + case 2 : for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char R = (unsigned char)*(data1++), G = (unsigned char)*(data2++); + (*ptrd++) = (R&0xf0) | (G>>4); + } break; + default : for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char R = (unsigned char)*(data1++), G = (unsigned char)*(data2++), B = (unsigned char)*(data3++); + (*ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6); + } + } + if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned char*)data,width,height); delete[] ndata; } + } break; + case 16 : { // 16 bits colors, no normalization + unsigned short *const ndata = (img.width==width && img.height==height)?(unsigned short*)data:new unsigned short[img.width*img.height]; + unsigned char *ptrd = (unsigned char*)ndata; + const unsigned int M = 248; + switch (img.dim) { + case 1 : + if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++), G = val>>2; + *(ptrd++) = (val&M) | (G>>3); + *(ptrd++) = (G<<5) | (G>>1); + } else for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++), G = val>>2; + *(ptrd++) = (G<<5) | (G>>1); + *(ptrd++) = (val&M) | (G>>3); + } + break; + case 2 : + if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3); + *(ptrd++) = (G<<5); + } else for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + *(ptrd++) = (G<<5); + *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3); + } + break; + default : + if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3); + *(ptrd++) = (G<<5) | ((unsigned char)*(data3++)>>3); + } else for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char G = (unsigned char)*(data2++)>>2; + *(ptrd++) = (G<<5) | ((unsigned char)*(data3++)>>3); + *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3); + } + } + if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned short*)data,width,height); delete[] ndata; } + } break; + default : { // 24 bits colors, no normalization + unsigned int *const ndata = (img.width==width && img.height==height)?(unsigned int*)data:new unsigned int[img.width*img.height]; + if (sizeof(int)==4) { // 32 bits int uses optimized version + unsigned int *ptrd = ndata; + switch (img.dim) { + case 1 : + if (cimg::X11attr().byte_order==cimg::endianness()) + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + else + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++)<<8; + *(ptrd++) = (val<<16) | (val<<8) | val; + } + break; + case 2 : + if (cimg::X11attr().byte_order==cimg::endianness()) + for (unsigned int xy = img.width*img.height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8); + else + for (unsigned int xy = img.width*img.height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8); + break; + default : + if (cimg::X11attr().byte_order==cimg::endianness()) + for (unsigned int xy = img.width*img.height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++); + else + for (unsigned int xy = img.width*img.height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data3++)<<24) | ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8); + } + } else { + unsigned char *ptrd = (unsigned char*)ndata; + switch (img.dim) { + case 1 : + if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) { + *(ptrd++) = 0; + *(ptrd++) = (unsigned char)*(data1++); + *(ptrd++) = 0; + *(ptrd++) = 0; + } else for (unsigned int xy = img.width*img.height; xy>0; --xy) { + *(ptrd++) = 0; + *(ptrd++) = 0; + *(ptrd++) = (unsigned char)*(data1++); + *(ptrd++) = 0; + } + break; + case 2 : + if (cimg::X11attr().byte_order) cimg::swap(data1,data2); + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + *(ptrd++) = 0; + *(ptrd++) = (unsigned char)*(data2++); + *(ptrd++) = (unsigned char)*(data1++); + *(ptrd++) = 0; + } + break; + default : + if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) { + *(ptrd++) = 0; + *(ptrd++) = (unsigned char)*(data1++); + *(ptrd++) = (unsigned char)*(data2++); + *(ptrd++) = (unsigned char)*(data3++); + } else for (unsigned int xy = img.width*img.height; xy>0; --xy) { + *(ptrd++) = (unsigned char)*(data3++); + *(ptrd++) = (unsigned char)*(data2++); + *(ptrd++) = (unsigned char)*(data1++); + *(ptrd++) = 0; + } + } + } + if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned int*)data,width,height); delete[] ndata; } + } + }; + } else { + if (normalization==3) { + if (cimg::type::is_float()) min = (float)img.minmax(max); + else { min = (float)cimg::type::min(); max = (float)cimg::type::max(); } + } else if ((min>max) || normalization==1) min = (float)img.minmax(max); + const float delta = max-min, mm = delta?delta:1.0f; + switch (cimg::X11attr().nb_bits) { + case 8 : { // 256 color palette, with normalization + _set_colormap(colormap,img.dim); + unsigned char *const ndata = (img.width==width && img.height==height)?(unsigned char*)data:new unsigned char[img.width*img.height]; + unsigned char *ptrd = (unsigned char*)ndata; + switch (img.dim) { + case 1 : for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char R = (unsigned char)(255*(*(data1++)-min)/mm); + *(ptrd++) = R; + } break; + case 2 : for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char + R = (unsigned char)(255*(*(data1++)-min)/mm), + G = (unsigned char)(255*(*(data2++)-min)/mm); + (*ptrd++) = (R&0xf0) | (G>>4); + } break; + default : + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char + R = (unsigned char)(255*(*(data1++)-min)/mm), + G = (unsigned char)(255*(*(data2++)-min)/mm), + B = (unsigned char)(255*(*(data3++)-min)/mm); + *(ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6); + } + } + if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned char*)data,width,height); delete[] ndata; } + } break; + case 16 : { // 16 bits colors, with normalization + unsigned short *const ndata = (img.width==width && img.height==height)?(unsigned short*)data:new unsigned short[img.width*img.height]; + unsigned char *ptrd = (unsigned char*)ndata; + const unsigned int M = 248; + switch (img.dim) { + case 1 : + if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm), G = val>>2; + *(ptrd++) = (val&M) | (G>>3); + *(ptrd++) = (G<<5) | (val>>3); + } else for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm), G = val>>2; + *(ptrd++) = (G<<5) | (val>>3); + *(ptrd++) = (val&M) | (G>>3); + } + break; + case 2 : + if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2; + *(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3); + *(ptrd++) = (G<<5); + } else for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2; + *(ptrd++) = (G<<5); + *(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3); + } + break; + default : + if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2; + *(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3); + *(ptrd++) = (G<<5) | ((unsigned char)(255*(*(data3++)-min)/mm)>>3); + } else for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2; + *(ptrd++) = (G<<5) | ((unsigned char)(255*(*(data3++)-min)/mm)>>3); + *(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3); + } + } + if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned short*)data,width,height); delete[] ndata; } + } break; + default : { // 24 bits colors, with normalization + unsigned int *const ndata = (img.width==width && img.height==height)?(unsigned int*)data:new unsigned int[img.width*img.height]; + if (sizeof(int)==4) { // 32 bits int uses optimized version + unsigned int *ptrd = ndata; + switch (img.dim) { + case 1 : + if (cimg::X11attr().byte_order==cimg::endianness()) + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm); + *(ptrd++) = (val<<16) | (val<<8) | val; + } + else + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm); + *(ptrd++) = (val<<24) | (val<<16) | (val<<8); + } + break; + case 2 : + if (cimg::X11attr().byte_order==cimg::endianness()) + for (unsigned int xy = img.width*img.height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)(255*(*(data1++)-min)/mm)<<16) | + ((unsigned char)(255*(*(data2++)-min)/mm)<<8); + else + for (unsigned int xy = img.width*img.height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)(255*(*(data2++)-min)/mm)<<16) | + ((unsigned char)(255*(*(data1++)-min)/mm)<<8); + break; + default : + if (cimg::X11attr().byte_order==cimg::endianness()) + for (unsigned int xy = img.width*img.height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)(255*(*(data1++)-min)/mm)<<16) | + ((unsigned char)(255*(*(data2++)-min)/mm)<<8) | + (unsigned char)(255*(*(data3++)-min)/mm); + else + for (unsigned int xy = img.width*img.height; xy>0; --xy) + *(ptrd++) = + ((unsigned char)(255*(*(data3++)-min)/mm)<<24) | + ((unsigned char)(255*(*(data2++)-min)/mm)<<16) | + ((unsigned char)(255*(*(data1++)-min)/mm)<<8); + } + } else { + unsigned char *ptrd = (unsigned char*)ndata; + switch (img.dim) { + case 1 : + if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm); + (*ptrd++) = 0; + (*ptrd++) = val; + (*ptrd++) = val; + (*ptrd++) = val; + } else for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm); + (*ptrd++) = val; + (*ptrd++) = val; + (*ptrd++) = val; + (*ptrd++) = 0; + } + break; + case 2 : + if (cimg::X11attr().byte_order) cimg::swap(data1,data2); + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + (*ptrd++) = 0; + (*ptrd++) = (unsigned char)(255*(*(data2++)-min)/mm); + (*ptrd++) = (unsigned char)(255*(*(data1++)-min)/mm); + (*ptrd++) = 0; + } + break; + default : + if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) { + (*ptrd++) = 0; + (*ptrd++) = (unsigned char)(255*(*(data1++)-min)/mm); + (*ptrd++) = (unsigned char)(255*(*(data2++)-min)/mm); + (*ptrd++) = (unsigned char)(255*(*(data3++)-min)/mm); + } else for (unsigned int xy = img.width*img.height; xy>0; --xy) { + (*ptrd++) = (unsigned char)(255*(*(data3++)-min)/mm); + (*ptrd++) = (unsigned char)(255*(*(data2++)-min)/mm); + (*ptrd++) = (unsigned char)(255*(*(data1++)-min)/mm); + (*ptrd++) = 0; + } + } + } + if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned int*)data,width,height); delete[] ndata; } + } + } + } + XUnlockDisplay(cimg::X11attr().display); + return *this; + } + + template + const CImgDisplay& snapshot(CImg& img) const { + if (is_empty()) img.assign(); + else { + img.assign(width,height,1,3); + T + *data1 = img.ptr(0,0,0,0), + *data2 = img.ptr(0,0,0,1), + *data3 = img.ptr(0,0,0,2); + if (cimg::X11attr().blue_first) cimg::swap(data1,data3); + switch (cimg::X11attr().nb_bits) { + case 8 : { + unsigned char *ptrs = (unsigned char*)data; + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = *(ptrs++); + *(data1++) = val&0xe0; + *(data2++) = (val&0x1c)<<3; + *(data3++) = val<<6; + } + } break; + case 16 : { + unsigned char *ptrs = (unsigned char*)data; + if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val0 = *(ptrs++), val1 = *(ptrs++); + *(data1++) = val0&0xf8; + *(data2++) = (val0<<5) | ((val1&0xe0)>>5); + *(data3++) = val1<<3; + } else for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned short val0 = *(ptrs++), val1 = *(ptrs++); + *(data1++) = val1&0xf8; + *(data2++) = (val1<<5) | ((val0&0xe0)>>5); + *(data3++) = val0<<3; + } + } break; + default : { + unsigned char *ptrs = (unsigned char*)data; + if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) { + ++ptrs; + *(data1++) = *(ptrs++); + *(data2++) = *(ptrs++); + *(data3++) = *(ptrs++); + } else for (unsigned int xy = img.width*img.height; xy>0; --xy) { + *(data3++) = *(ptrs++); + *(data2++) = *(ptrs++); + *(data1++) = *(ptrs++); + ++ptrs; + } + } + } + } + return *this; + } + + // Windows-based display + //----------------------- +#elif cimg_display==2 + CLIENTCREATESTRUCT ccs; + BITMAPINFO bmi; + unsigned int *data; + DEVMODE curr_mode; + HWND window; + HWND background_window; + HDC hdc; + HANDLE thread; + HANDLE created; + HANDLE mutex; + bool mouse_tracking; + bool visible_cursor; + + static int screen_dimx() { + DEVMODE mode; + mode.dmSize = sizeof(DEVMODE); + mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode); + return mode.dmPelsWidth; + } + + static int screen_dimy() { + DEVMODE mode; + mode.dmSize = sizeof(DEVMODE); + mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode); + return mode.dmPelsHeight; + } + + static void wait_all() { + WaitForSingleObject(cimg::Win32attr().wait_event,INFINITE); + } + + static LRESULT APIENTRY _handle_events(HWND window,UINT msg,WPARAM wParam,LPARAM lParam) { +#ifdef _WIN64 + CImgDisplay* disp = (CImgDisplay*)GetWindowLongPtr(window,GWLP_USERDATA); +#else + CImgDisplay* disp = (CImgDisplay*)GetWindowLong(window,GWL_USERDATA); +#endif + MSG st_msg; + + switch (msg) { + case WM_CLOSE : + disp->mouse_x = disp->mouse_y = -1; + disp->window_x = disp->window_y = 0; + if (disp->button) { + cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1); + disp->button = 0; + } + if (disp->key) { + cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); + disp->key = 0; + } + if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; } + disp->is_closed = true; + ReleaseMutex(disp->mutex); + ShowWindow(disp->window,SW_HIDE); + disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + return 0; + case WM_SIZE : { + while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {} + WaitForSingleObject(disp->mutex,INFINITE); + const unsigned int nw = LOWORD(lParam),nh = HIWORD(lParam); + if (nw && nh && (nw!=disp->width || nh!=disp->height)) { + disp->window_width = nw; + disp->window_height = nh; + disp->mouse_x = disp->mouse_y = -1; + disp->is_resized = disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + } + ReleaseMutex(disp->mutex); + } break; + case WM_MOVE : { + while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {} + WaitForSingleObject(disp->mutex,INFINITE); + const int nx = (int)(short)(LOWORD(lParam)), ny = (int)(short)(HIWORD(lParam)); + if (nx!=disp->window_x || ny!=disp->window_y) { + disp->window_x = nx; + disp->window_y = ny; + disp->is_moved = disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + } + ReleaseMutex(disp->mutex); + } break; + case WM_PAINT : + disp->paint(); + break; + case WM_KEYDOWN : + disp->update_iskey((unsigned int)wParam,true); + if (disp->key) cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); + disp->key = (unsigned int)wParam; + if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; } + disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + break; + case WM_MOUSEMOVE : { + while (PeekMessage(&st_msg,window,WM_MOUSEMOVE,WM_MOUSEMOVE,PM_REMOVE)) {} + disp->mouse_x = LOWORD(lParam); + disp->mouse_y = HIWORD(lParam); +#if (_WIN32_WINNT>=0x0400) && !defined(NOTRACKMOUSEEVENT) + if (!disp->mouse_tracking) { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = disp->window; + if (TrackMouseEvent(&tme)) disp->mouse_tracking = true; + } +#endif + if (disp->mouse_x<0 || disp->mouse_y<0 || disp->mouse_x>=disp->dimx() || disp->mouse_y>=disp->dimy()) + disp->mouse_x = disp->mouse_y = -1; + disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + } break; + case WM_MOUSELEAVE : { + disp->mouse_x = disp->mouse_y = -1; + disp->mouse_tracking = false; + } break; + case WM_LBUTTONDOWN : + cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1); + disp->button|=1U; + disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + break; + case WM_RBUTTONDOWN : + cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1); + disp->button|=2U; + disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + break; + case WM_MBUTTONDOWN : + cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1); + disp->button|=4U; + disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + break; + case 0x020A : // WM_MOUSEWHEEL: + disp->wheel+=(int)((short)HIWORD(wParam))/120; + disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + case WM_KEYUP : + disp->update_iskey((unsigned int)wParam,false); + if (disp->key) { cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); disp->key = 0; } + if (disp->released_key) cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); + disp->released_key = (unsigned int)wParam; + disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + break; + case WM_LBUTTONUP : + cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1); + disp->button&=~1U; + disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + break; + case WM_RBUTTONUP : + cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1); + disp->button&=~2U; + disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + break; + case WM_MBUTTONUP : + cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1); + disp->button&=~4U; + disp->is_event = true; + SetEvent(cimg::Win32attr().wait_event); + break; + case WM_SETCURSOR : + if (disp->visible_cursor) ShowCursor(TRUE); + else ShowCursor(FALSE); + break; + } + return DefWindowProc(window,msg,wParam,lParam); + } + + static DWORD WINAPI _events_thread(void* arg) { + CImgDisplay *disp = (CImgDisplay*)(((void**)arg)[0]); + const char *title = (const char*)(((void**)arg)[1]); + MSG msg; + delete[] (void**)arg; + disp->bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + disp->bmi.bmiHeader.biWidth = disp->width; + disp->bmi.bmiHeader.biHeight = -(int)disp->height; + disp->bmi.bmiHeader.biPlanes = 1; + disp->bmi.bmiHeader.biBitCount = 32; + disp->bmi.bmiHeader.biCompression = BI_RGB; + disp->bmi.bmiHeader.biSizeImage = 0; + disp->bmi.bmiHeader.biXPelsPerMeter = 1; + disp->bmi.bmiHeader.biYPelsPerMeter = 1; + disp->bmi.bmiHeader.biClrUsed = 0; + disp->bmi.bmiHeader.biClrImportant = 0; + disp->data = new unsigned int[disp->width*disp->height]; + if (!disp->is_fullscreen) { // Normal window + RECT rect; + rect.left = rect.top = 0; rect.right = disp->width-1; rect.bottom = disp->height-1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int border1 = (rect.right-rect.left+1-disp->width)/2, border2 = rect.bottom-rect.top+1-disp->height-border1; + disp->window = CreateWindowA("MDICLIENT",title?title:" ", + WS_OVERLAPPEDWINDOW | (disp->is_closed?0:WS_VISIBLE), CW_USEDEFAULT,CW_USEDEFAULT, + disp->width + 2*border1, disp->height + border1 + border2, + 0,0,0,&(disp->ccs)); + if (!disp->is_closed) { + GetWindowRect(disp->window,&rect); + disp->window_x = rect.left + border1; + disp->window_y = rect.top + border2; + } else disp->window_x = disp->window_y = 0; + } else { // Fullscreen window + const unsigned int sx = screen_dimx(), sy = screen_dimy(); + disp->window = CreateWindowA("MDICLIENT",title?title:" ", + WS_POPUP | (disp->is_closed?0:WS_VISIBLE), (sx-disp->width)/2, (sy-disp->height)/2, + disp->width,disp->height,0,0,0,&(disp->ccs)); + disp->window_x = disp->window_y = 0; + } + SetForegroundWindow(disp->window); + disp->hdc = GetDC(disp->window); + disp->window_width = disp->width; + disp->window_height = disp->height; + disp->flush(); +#ifdef _WIN64 + SetWindowLongPtr(disp->window,GWLP_USERDATA,(LONG_PTR)disp); + SetWindowLongPtr(disp->window,GWLP_WNDPROC,(LONG_PTR)_handle_events); +#else + SetWindowLong(disp->window,GWL_USERDATA,(LONG)disp); + SetWindowLong(disp->window,GWL_WNDPROC,(LONG)_handle_events); +#endif + SetEvent(disp->created); + while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg); + return 0; + } + + CImgDisplay& _update_window_pos() { + if (!is_closed) { + RECT rect; + rect.left = rect.top = 0; rect.right = width-1; rect.bottom = height-1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int border1 = (rect.right-rect.left+1-width)/2, border2 = rect.bottom-rect.top+1-height-border1; + GetWindowRect(window,&rect); + window_x = rect.left + border1; + window_y = rect.top + border2; + } else window_x = window_y = -1; + return *this; + } + + void _init_fullscreen() { + background_window = 0; + if (is_fullscreen && !is_closed) { + DEVMODE mode; + unsigned int imode = 0, ibest = 0, bestbpp = 0, bw = ~0U, bh = ~0U; + for (mode.dmSize = sizeof(DEVMODE), mode.dmDriverExtra = 0; EnumDisplaySettings(0,imode,&mode); ++imode) { + const unsigned int nw = mode.dmPelsWidth, nh = mode.dmPelsHeight; + if (nw>=width && nh>=height && mode.dmBitsPerPel>=bestbpp && nw<=bw && nh<=bh) { + bestbpp = mode.dmBitsPerPel; + ibest = imode; + bw = nw; bh = nh; + } + } + if (bestbpp) { + curr_mode.dmSize = sizeof(DEVMODE); curr_mode.dmDriverExtra = 0; + EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&curr_mode); + EnumDisplaySettings(0,ibest,&mode); + ChangeDisplaySettings(&mode,0); + } else curr_mode.dmSize = 0; + + const unsigned int sx = screen_dimx(), sy = screen_dimy(); + if (sx!=width || sy!=height) { + CLIENTCREATESTRUCT background_ccs; + background_window = CreateWindowA("MDICLIENT","",WS_POPUP | WS_VISIBLE, 0,0,sx,sy,0,0,0,&background_ccs); + SetForegroundWindow(background_window); + } + } else curr_mode.dmSize = 0; + } + + void _desinit_fullscreen() { + if (is_fullscreen) { + if (background_window) DestroyWindow(background_window); + background_window = 0; + if (curr_mode.dmSize) ChangeDisplaySettings(&curr_mode,0); + is_fullscreen = false; + } + } + + CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *ptitle=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + + // Allocate space for window title + const int s = cimg::strlen(ptitle)+1; + char *tmp_title = s?new char[s]:0; + if (s) cimg_std::memcpy(tmp_title,ptitle,s*sizeof(char)); + + // Destroy previous window if existing + if (!is_empty()) assign(); + + // Set display variables + width = cimg::min(dimw,(unsigned int)screen_dimx()); + height = cimg::min(dimh,(unsigned int)screen_dimy()); + normalization = normalization_type<4?normalization_type:3; + is_fullscreen = fullscreen_flag; + window_x = window_y = 0; + is_closed = closed_flag; + visible_cursor = true; + mouse_tracking = false; + title = tmp_title; + flush(); + if (is_fullscreen) _init_fullscreen(); + + // Create event thread + void *arg = (void*)(new void*[2]); + ((void**)arg)[0]=(void*)this; + ((void**)arg)[1]=(void*)title; + unsigned long ThreadID = 0; + mutex = CreateMutex(0,FALSE,0); + created = CreateEvent(0,FALSE,FALSE,0); + thread = CreateThread(0,0,_events_thread,arg,0,&ThreadID); + WaitForSingleObject(created,INFINITE); + return *this; + } + + CImgDisplay& assign() { + if (is_empty()) return *this; + DestroyWindow(window); + TerminateThread(thread,0); + if (data) delete[] data; + if (title) delete[] title; + if (is_fullscreen) _desinit_fullscreen(); + width = height = normalization = window_width = window_height = 0; + window_x = window_y = 0; + is_fullscreen = false; + is_closed = true; + min = max = 0; + title = 0; + flush(); + return *this; + } + + CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!dimw || !dimh) return assign(); + _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag); + min = max = 0; + cimg_std::memset(data,0,sizeof(unsigned int)*width*height); + return paint(); + } + + template + CImgDisplay& assign(const CImg& img, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!img) return assign(); + CImg tmp; + const CImg& nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2)); + _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag); + if (normalization==2) min = (float)nimg.minmax(max); + return display(nimg); + } + + template + CImgDisplay& assign(const CImgList& list, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!list) return assign(); + CImg tmp; + const CImg img = list.get_append('x','p'), + &nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2)); + _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag); + if (normalization==2) min = (float)nimg.minmax(max); + return display(nimg); + } + + CImgDisplay& assign(const CImgDisplay& win) { + if (!win) return assign(); + _assign(win.width,win.height,win.title,win.normalization,win.is_fullscreen,win.is_closed); + cimg_std::memcpy(data,win.data,sizeof(unsigned int)*width*height); + return paint(); + } + + CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) { + if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign(); + if (is_empty()) return assign(nwidth,nheight); + const unsigned int + tmpdimx=(nwidth>0)?nwidth:(-nwidth*width/100), + tmpdimy=(nheight>0)?nheight:(-nheight*height/100), + dimx = tmpdimx?tmpdimx:1, + dimy = tmpdimy?tmpdimy:1; + if (window_width!=dimx || window_height!=dimy) { + RECT rect; rect.left = rect.top = 0; rect.right = dimx-1; rect.bottom = dimy-1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int cwidth = rect.right-rect.left+1, cheight = rect.bottom-rect.top+1; + SetWindowPos(window,0,0,0,cwidth,cheight,SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS); + } + if (width!=dimx || height!=dimy) { + unsigned int *ndata = new unsigned int[dimx*dimy]; + if (redraw) _render_resize(data,width,height,ndata,dimx,dimy); + else cimg_std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy); + delete[] data; + data = ndata; + bmi.bmiHeader.biWidth = dimx; + bmi.bmiHeader.biHeight = -(int)dimy; + width = dimx; + height = dimy; + } + window_width = dimx; window_height = dimy; + is_resized = false; + if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2); + if (redraw) return paint(); + return *this; + } + + CImgDisplay& toggle_fullscreen(const bool redraw=true) { + if (is_empty()) return *this; + if (redraw) { + const unsigned int bufsize = width*height*4; + void *odata = cimg_std::malloc(bufsize); + cimg_std::memcpy(odata,data,bufsize); + assign(width,height,title,normalization,!is_fullscreen,false); + cimg_std::memcpy(data,odata,bufsize); + cimg_std::free(odata); + return paint(); + } + return assign(width,height,title,normalization,!is_fullscreen,false); + } + + CImgDisplay& show() { + if (is_empty()) return *this; + if (is_closed) { + is_closed = false; + if (is_fullscreen) _init_fullscreen(); + ShowWindow(window,SW_SHOW); + _update_window_pos(); + } + return paint(); + } + + CImgDisplay& close() { + if (is_empty()) return *this; + if (!is_closed && !is_fullscreen) { + if (is_fullscreen) _desinit_fullscreen(); + ShowWindow(window,SW_HIDE); + is_closed = true; + window_x = window_y = 0; + } + return *this; + } + + CImgDisplay& move(const int posx, const int posy) { + if (is_empty()) return *this; + if (!is_fullscreen) { + RECT rect; rect.left = rect.top = 0; rect.right=window_width-1; rect.bottom=window_height-1; + AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); + const int border1 = (rect.right-rect.left+1-width)/2, border2 = rect.bottom-rect.top+1-height-border1; + SetWindowPos(window,0,posx-border1,posy-border2,0,0,SWP_NOSIZE | SWP_NOZORDER); + } else SetWindowPos(window,0,posx,posy,0,0,SWP_NOSIZE | SWP_NOZORDER); + window_x = posx; + window_y = posy; + is_moved = false; + return show(); + } + + CImgDisplay& show_mouse() { + if (is_empty()) return *this; + visible_cursor = true; + ShowCursor(TRUE); + SendMessage(window,WM_SETCURSOR,0,0); + return *this; + } + + CImgDisplay& hide_mouse() { + if (is_empty()) return *this; + visible_cursor = false; + ShowCursor(FALSE); + SendMessage(window,WM_SETCURSOR,0,0); + return *this; + } + + CImgDisplay& set_mouse(const int posx, const int posy) { + if (!is_closed && posx>=0 && posy>=0) { + _update_window_pos(); + const int res = (int)SetCursorPos(window_x+posx,window_y+posy); + if (res) { mouse_x = posx; mouse_y = posy; } + } + return *this; + } + + CImgDisplay& set_title(const char *format, ...) { + if (is_empty()) return *this; + char tmp[1024] = {0}; + va_list ap; + va_start(ap, format); + cimg_std::vsprintf(tmp,format,ap); + va_end(ap); + if (title) delete[] title; + const int s = cimg::strlen(tmp)+1; + title = new char[s]; + cimg_std::memcpy(title,tmp,s*sizeof(char)); + SetWindowTextA(window, tmp); + return *this; + } + + template + CImgDisplay& display(const CImg& img) { + if (img.is_empty()) + throw CImgArgumentException("CImgDisplay::display() : Cannot display empty image."); + if (is_empty()) assign(img.width,img.height); + return render(img).paint(); + } + + CImgDisplay& paint() { + if (!is_closed) { + WaitForSingleObject(mutex,INFINITE); + SetDIBitsToDevice(hdc,0,0,width,height,0,0,0,height,data,&bmi,DIB_RGB_COLORS); + ReleaseMutex(mutex); + } + return *this; + } + + template + CImgDisplay& render(const CImg& img) { + if (is_empty()) return *this; + if (!img) + throw CImgArgumentException("CImgDisplay::_render_image() : Specified input image (%u,%u,%u,%u,%p) is empty.", + img.width,img.height,img.depth,img.dim,img.data); + if (img.depth!=1) return render(img.get_projections2d(img.width/2,img.height/2,img.depth/2)); + + const T + *data1 = img.data, + *data2 = (img.dim>=2)?img.ptr(0,0,0,1):data1, + *data3 = (img.dim>=3)?img.ptr(0,0,0,2):data1; + + WaitForSingleObject(mutex,INFINITE); + unsigned int + *const ndata = (img.width==width && img.height==height)?data:new unsigned int[img.width*img.height], + *ptrd = ndata; + + if (!normalization || (normalization==3 && cimg::type::string()==cimg::type::string())) { + min = max = 0; + switch (img.dim) { + case 1 : { + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = (unsigned char)*(data1++); + *(ptrd++) = (val<<16) | (val<<8) | val; + }} break; + case 2 : { + for (unsigned int xy = img.width*img.height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8); + } break; + default : { + for (unsigned int xy = img.width*img.height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++); + } + } + } else { + if (normalization==3) { + if (cimg::type::is_float()) min = (float)img.minmax(max); + else { min = (float)cimg::type::min(); max = (float)cimg::type::max(); } + } else if ((min>max) || normalization==1) min = (float)img.minmax(max); + const float delta = max-min, mm = delta?delta:1.0f; + switch (img.dim) { + case 1 : { + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm); + *(ptrd++) = (val<<16) | (val<<8) | val; + }} break; + case 2 : { + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char + R = (unsigned char)(255*(*(data1++)-min)/mm), + G = (unsigned char)(255*(*(data2++)-min)/mm); + *(ptrd++) = (R<<16) | (G<<8); + }} break; + default : { + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char + R = (unsigned char)(255*(*(data1++)-min)/mm), + G = (unsigned char)(255*(*(data2++)-min)/mm), + B = (unsigned char)(255*(*(data3++)-min)/mm); + *(ptrd++) = (R<<16) | (G<<8) | B; + }} + } + } + if (ndata!=data) { _render_resize(ndata,img.width,img.height,data,width,height); delete[] ndata; } + ReleaseMutex(mutex); + return *this; + } + + template + const CImgDisplay& snapshot(CImg& img) const { + if (is_empty()) img.assign(); + else { + img.assign(width,height,1,3); + T + *data1 = img.ptr(0,0,0,0), + *data2 = img.ptr(0,0,0,1), + *data3 = img.ptr(0,0,0,2); + unsigned int *ptrs = data; + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned int val = *(ptrs++); + *(data1++) = (unsigned char)(val>>16); + *(data2++) = (unsigned char)((val>>8)&0xFF); + *(data3++) = (unsigned char)(val&0xFF); + } + } + return *this; + } + + // MacOSX - Carbon-based display + //------------------------------- + // (Code by Adrien Reboisson && Romain Blei, supervised by Jean-Marie Favreau) + // +#elif cimg_display==3 + unsigned int *data; // The bits of the picture + WindowRef carbonWindow; // The opaque carbon window struct associated with the display + MPCriticalRegionID paintCriticalRegion; // Critical section used when drawing + CGColorSpaceRef csr; // Needed for painting + CGDataProviderRef dataProvider; // Needed for painting + CGImageRef imageRef; // The image + UInt32 lastKeyModifiers; // Buffer storing modifiers state + + // Define the kind of the queries which can be serialized using the event thread. + typedef enum { + COM_CREATEWINDOW = 0, // Create window query + COM_RELEASEWINDOW, // Release window query + COM_SHOWWINDOW, // Show window query + COM_HIDEWINDOW, // Hide window query + COM_SHOWMOUSE, // Show mouse query + COM_HIDEMOUSE, // Hide mouse query + COM_RESIZEWINDOW, // Resize window query + COM_MOVEWINDOW, // Move window query + COM_SETTITLE, // Set window title query + COM_SETMOUSEPOS // Set cursor position query + } CImgCarbonQueryKind; + + // The query destructor send to the event thread. + struct CbSerializedQuery { + CImgDisplay* sender; // Query's sender + CImgCarbonQueryKind kind; // The kind of the query sent to the background thread + short x, y; // X:Y values for move/resize operations + char *c; // Char values for window title + bool createFullScreenWindow; // Boolean value used for full-screen window creation + bool createClosedWindow; // Boolean value used for closed-window creation + bool update; // Boolean value used for resize + bool success; // Succes or failure of the message, used as return value + CbSerializedQuery(CImgDisplay *s, CImgCarbonQueryKind k):sender(s),kind(k),success(false) {}; + + inline static CbSerializedQuery BuildReleaseWindowQuery(CImgDisplay* sender) { + return CbSerializedQuery(sender, COM_RELEASEWINDOW); + } + inline static CbSerializedQuery BuildCreateWindowQuery(CImgDisplay* sender, const bool fullscreen, const bool closed) { + CbSerializedQuery q(sender, COM_CREATEWINDOW); + q.createFullScreenWindow = fullscreen; + q.createClosedWindow = closed; + return q; + } + inline static CbSerializedQuery BuildShowWindowQuery(CImgDisplay* sender) { + return CbSerializedQuery(sender, COM_SHOWWINDOW); + } + inline static CbSerializedQuery BuildHideWindowQuery(CImgDisplay* sender) { + return CbSerializedQuery(sender, COM_HIDEWINDOW); + } + inline static CbSerializedQuery BuildShowMouseQuery(CImgDisplay* sender) { + return CbSerializedQuery(sender, COM_SHOWMOUSE); + } + inline static CbSerializedQuery BuildHideMouseQuery(CImgDisplay* sender) { + return CbSerializedQuery(sender, COM_HIDEMOUSE); + } + inline static CbSerializedQuery BuildResizeWindowQuery(CImgDisplay* sender, const int x, const int y, bool update) { + CbSerializedQuery q(sender, COM_RESIZEWINDOW); + q.x = x, q.y = y; + q.update = update; + return q; + } + inline static CbSerializedQuery BuildMoveWindowQuery(CImgDisplay* sender, const int x, const int y) { + CbSerializedQuery q(sender, COM_MOVEWINDOW); + q.x = x, q.y = y; + return q; + } + inline static CbSerializedQuery BuildSetWindowTitleQuery(CImgDisplay* sender, char* c) { + CbSerializedQuery q(sender, COM_SETTITLE); + q.c = c; + return q; + } + inline static CbSerializedQuery BuildSetWindowPosQuery(CImgDisplay* sender, const int x, const int y) { + CbSerializedQuery q(sender, COM_SETMOUSEPOS); + q.x = x, q.y = y; + return q; + } + }; + + // Send a serialized query in a synchroneous way. + // @param c Application Carbon global settings. + // @param m The query to send. + // @result Success/failure of the operation returned by the event thread. + bool _CbSendMsg(cimg::CarbonInfo& c, CbSerializedQuery m) { + MPNotifyQueue(c.com_queue,&m,0,0); // Send the given message + MPWaitOnSemaphore(c.sync_event,kDurationForever); // Wait end of processing notification + return m.success; + } + + // Free the window attached to the current display. + // @param c Application Carbon global settings. + // @result Success/failure of the operation. + bool _CbFreeAttachedWindow(cimg::CarbonInfo& c) { + if (!_CbSendMsg(c, CbSerializedQuery::BuildReleaseWindowQuery(this))) // Ask the main thread to free the given window + throw CImgDisplayException("Cannot release window associated with the current display."); + // If a window existed, ask to release it + MPEnterCriticalRegion(c.windowListCR,kDurationForever); // Lock the list of the windows + --c.windowCount; //Decrement the window count + MPExitCriticalRegion(c.windowListCR); // Unlock the list + return c.windowCount == 0; + } + + // Create the window attached to the current display. + // @param c Application Carbon global settings. + // @param title The window title, if any. + // @param fullscreen Shoud we start in fullscreen mode ? + // @param create_closed If true, the window is created but not displayed. + // @result Success/failure of the operation. + void _CbCreateAttachedWindow(cimg::CarbonInfo& c, const char* title, const bool fullscreen, const bool create_closed) { + if (!_CbSendMsg(c,CbSerializedQuery::BuildCreateWindowQuery(this,fullscreen,create_closed))) // Ask the main thread to create the window + throw CImgDisplayException("Cannot create the window associated with the current display."); + if (title) set_title(title); // Set the title, if any + // Now we can register the window + MPEnterCriticalRegion(c.windowListCR,kDurationForever); // Lock the list of the windows + ++c.windowCount; //Increment the window count + MPExitCriticalRegion(c.windowListCR); // Unlock the list + } + + // Destroy graphic objects previously allocated. We free the image, the data provider, then the colorspace. + void _CbFinalizeGraphics() { + CGImageRelease (imageRef); // Release the picture + CGDataProviderRelease(dataProvider); // Release the DP + CGColorSpaceRelease(csr); // Free the cs + } + + // Create graphic objects associated to a display. We have to create a colormap, a data provider, and the image. + void _CbInitializeGraphics() { + csr = CGColorSpaceCreateDeviceRGB(); // Create the color space first + if (!csr) + throw CImgDisplayException("CGColorSpaceCreateDeviceRGB() failed."); + // Create the DP + dataProvider = CGDataProviderCreateWithData(0,data,height*width*sizeof(unsigned int),0); + if (!dataProvider) + throw CImgDisplayException("CGDataProviderCreateWithData() failed."); + // ... and finally the image. + if (cimg::endianness()) + imageRef = CGImageCreate(width,height,8,32,width*sizeof(unsigned int),csr, + kCGImageAlphaNoneSkipFirst,dataProvider,0,false,kCGRenderingIntentDefault); + else + imageRef = CGImageCreate(width,height,8,32,width*sizeof(unsigned int),csr, + kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host,dataProvider,0,false,kCGRenderingIntentDefault); + if (!imageRef) + throw CImgDisplayException("CGImageCreate() failed."); + } + + // Reinit graphic objects. Free them, then reallocate all. + // This is used when image bounds are changed or when data source get invalid. + void _CbReinitGraphics() { + MPEnterCriticalRegion(paintCriticalRegion, kDurationForever); + _CbFinalizeGraphics(); + _CbInitializeGraphics(); + MPExitCriticalRegion(paintCriticalRegion); + } + + // Convert a point having global coordonates into the window coordonates. + // We use this function to replace the deprecated GlobalToLocal QuickDraw API. + // @param mouseEvent The mouse event which triggered the event handler. + // @param window The window where the event occured. + // @param point The modified point struct. + // @result True if the point struct has been converted successfully. + static bool _CbToLocalPointFromMouseEvent(EventRef mouseEvent, WindowRef window, HIPoint* point) { + Rect bounds; + if (GetWindowBounds(window,kWindowStructureRgn,&bounds)==noErr) { + point->x -= bounds.left; + point->y -= bounds.top; + HIViewRef view = NULL; + if (HIViewGetViewForMouseEvent(HIViewGetRoot(window),mouseEvent,&view)==noErr) + return HIViewConvertPoint(point, NULL, view) == noErr; + } + return false; + } + + static int screen_dimx() { + return CGDisplayPixelsWide(kCGDirectMainDisplay); + } + + static int screen_dimy() { + return CGDisplayPixelsHigh(kCGDirectMainDisplay); + } + + CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!dimw || !dimh) return assign(); + _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag); + min = max = 0; + cimg_std::memset(data,0,sizeof(unsigned int)*width*height); + return paint(); + } + + template + CImgDisplay& assign(const CImg& img, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!img) return assign(); + CImg tmp; + const CImg& nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2)); + _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag); + if (normalization==2) min = (float)nimg.minmax(max); + return display(nimg); + } + + template + CImgDisplay& assign(const CImgList& list, const char *title=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + if (!list) return assign(); + CImg tmp; + const CImg img = list.get_append('x','p'), + &nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2)); + _assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag); + if (normalization==2) min = (float)nimg.minmax(max); + return display(nimg); + } + + CImgDisplay& assign(const CImgDisplay &win) { + if (!win) return assign(); + _assign(win.width,win.height,win.title,win.normalization,win.is_fullscreen,win.is_closed); + cimg_std::memcpy(data,win.data,sizeof(unsigned int)*width*height); + return paint(); + } + + template + CImgDisplay& display(const CImg& img) { + if (is_empty()) assign(img.width,img.height); + return render(img).paint(); + } + + CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) { + if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign(); + if (is_empty()) return assign(nwidth,nheight); + const unsigned int + tmpdimx = (nwidth>0)?nwidth:(-nwidth*width/100), + tmpdimy = (nheight>0)?nheight:(-nheight*height/100), + dimx = tmpdimx?tmpdimx:1, + dimy = tmpdimy?tmpdimy:1; + cimg::CarbonInfo& c = cimg::CarbonAttr(); + + if ((window_width!=dimx || window_height!=dimy) && + !_CbSendMsg(c,CbSerializedQuery::BuildResizeWindowQuery(this,dimx,dimy,redraw))) + throw CImgDisplayException("CImgDisplay::resize() : Cannot resize the window associated to the current display."); + + if (width!=dimx || height!=dimy) { + unsigned int *ndata = new unsigned int[dimx*dimy]; + if (redraw) _render_resize(data,width,height,ndata,dimx,dimy); + else cimg_std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy); + unsigned int const* old_data = data; + data = ndata; + delete[] old_data; + _CbReinitGraphics(); + } + window_width = width = dimx; window_height = height = dimy; + is_resized = false; + if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2); + if (redraw) return paint(); + return *this; + } + + CImgDisplay& move(const int posx, const int posy) { + if (is_empty()) return *this; + if (!is_fullscreen) { + // If the operation succeeds, window_x and window_y are updated by the event thread + cimg::CarbonInfo& c = cimg::CarbonAttr(); + // Send the query + if (!_CbSendMsg(c,CbSerializedQuery::BuildMoveWindowQuery(this,posx,posy))) + throw CImgDisplayException("CImgDisplay::move() : Cannot move the window associated to the current display."); + } + return show(); + } + + CImgDisplay& set_mouse(const int posx, const int posy) { + if (!is_closed && posx>=0 && posy>=0) { + // If the operation succeeds, mouse_x and mouse_y are updated by the event thread + cimg::CarbonInfo& c = cimg::CarbonAttr(); + // Send the query + if (!_CbSendMsg(c,CbSerializedQuery::BuildSetWindowPosQuery(this,posx,posy))) + throw CImgDisplayException("CImgDisplay::set_mouse() : Cannot set the mouse position to the current display."); + } + return *this; + } + + CImgDisplay& hide_mouse() { + if (is_empty()) return *this; + cimg::CarbonInfo& c = cimg::CarbonAttr(); + // Send the query + if (!_CbSendMsg(c,CbSerializedQuery::BuildHideMouseQuery(this))) + throw CImgDisplayException("CImgDisplay::hide_mouse() : Cannot hide the mouse associated to the current display."); + return *this; + } + + CImgDisplay& show_mouse() { + if (is_empty()) return *this; + cimg::CarbonInfo& c = cimg::CarbonAttr(); + // Send the query + if (!_CbSendMsg(c,CbSerializedQuery::BuildShowMouseQuery(this))) + throw CImgDisplayException("CImgDisplay::show_mouse() : Cannot show the mouse associated to the current display."); + return *this; + } + + static void wait_all() { + cimg::CarbonInfo& c = cimg::CarbonAttr(); + MPWaitOnSemaphore(c.wait_event,kDurationForever); + } + + CImgDisplay& show() { + if (is_empty()) return *this; + if (is_closed) { + cimg::CarbonInfo& c = cimg::CarbonAttr(); + if (!_CbSendMsg(c,CbSerializedQuery::BuildShowWindowQuery(this))) + throw CImgDisplayException("CImgDisplay::show() : Cannot show the window associated to the current display."); + } + return paint(); + } + + CImgDisplay& close() { + if (is_empty()) return *this; + if (!is_closed && !is_fullscreen) { + cimg::CarbonInfo& c = cimg::CarbonAttr(); + // If the operation succeeds, window_x and window_y are updated on the event thread + if (!_CbSendMsg(c,CbSerializedQuery::BuildHideWindowQuery(this))) + throw CImgDisplayException("CImgDisplay::close() : Cannot hide the window associated to the current display."); + } + return *this; + } + + CImgDisplay& set_title(const char *format, ...) { + if (is_empty()) return *this; + char tmp[1024] = {0}; + va_list ap; + va_start(ap, format); + cimg_std::vsprintf(tmp,format,ap); + va_end(ap); + if (title) delete[] title; + const int s = cimg::strlen(tmp)+1; + title = new char[s]; + cimg_std::memcpy(title,tmp,s*sizeof(char)); + cimg::CarbonInfo& c = cimg::CarbonAttr(); + if (!_CbSendMsg(c,CbSerializedQuery::BuildSetWindowTitleQuery(this,tmp))) + throw CImgDisplayException("CImgDisplay::set_title() : Cannot set the window title associated to the current display."); + return *this; + } + + CImgDisplay& paint() { + if (!is_closed) { + MPEnterCriticalRegion(paintCriticalRegion,kDurationForever); + CGrafPtr portPtr = GetWindowPort(carbonWindow); + CGContextRef currentContext = 0; + TQDBeginCGContext(portPtr,¤tContext); + CGContextSetRGBFillColor(currentContext,255,255,255,255); + CGContextFillRect(currentContext,CGRectMake(0,0,window_width,window_height)); + CGContextDrawImage(currentContext,CGRectMake(0,int(window_height-height)<0?0:window_height-height,width,height),imageRef); + CGContextFlush(currentContext); + TQDEndCGContext(portPtr, ¤tContext); + MPExitCriticalRegion(paintCriticalRegion); + } + return *this; + } + + template + CImgDisplay& render(const CImg& img) { + if (is_empty()) return *this; + if (!img) + throw CImgArgumentException("CImgDisplay::_render_image() : Specified input image (%u,%u,%u,%u,%p) is empty.", + img.width,img.height,img.depth,img.dim,img.data); + if (img.depth!=1) return render(img.get_projections2d(img.width/2,img.height/2,img.depth/2)); + const T + *data1 = img.data, + *data2 = (img.dim>=2)?img.ptr(0,0,0,1):data1, + *data3 = (img.dim>=3)?img.ptr(0,0,0,2):data1; + MPEnterCriticalRegion(paintCriticalRegion, kDurationForever); + unsigned int + *const ndata = (img.width==width && img.height==height)?data:new unsigned int[img.width*img.height], + *ptrd = ndata; + if (!normalization || (normalization==3 && cimg::type::string()==cimg::type::string())) { + min = max = 0; + for (unsigned int xy = img.width*img.height; xy>0; --xy) + *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++); + } else { + if (normalization==3) { + if (cimg::type::is_float()) min = (float)img.minmax(max); + else { + min = (float)cimg::type::min(); + max = (float)cimg::type::max(); + } + } else if ((min>max) || normalization==1) min = (float)img.minmax(max); + const float delta = max-min, mm = delta?delta:1.0f; + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned char + R = (unsigned char)(255*(*(data1++)-min)/mm), + G = (unsigned char)(255*(*(data2++)-min)/mm), + B = (unsigned char)(255*(*(data3++)-min)/mm); + *(ptrd++) = (R<<16) | (G<<8) | (B); + } + } + if (ndata!=data) { + _render_resize(ndata,img.width,img.height,data,width,height); + delete[] ndata; + } + MPExitCriticalRegion(paintCriticalRegion); + return *this; + } + + template + const CImgDisplay& snapshot(CImg& img) const { + if (is_empty()) img.assign(); + else { + img.assign(width,height,1,3); + T + *data1 = img.ptr(0,0,0,0), + *data2 = img.ptr(0,0,0,1), + *data3 = img.ptr(0,0,0,2); + unsigned int *ptrs = data; + for (unsigned int xy = img.width*img.height; xy>0; --xy) { + const unsigned int val = *(ptrs++); + *(data1++) = (unsigned char)(val>>16); + *(data2++) = (unsigned char)((val>>8)&0xFF); + *(data3++) = (unsigned char)(val&0xFF); + } + } + return *this; + } + + CImgDisplay& toggle_fullscreen(const bool redraw=true) { + if (is_empty()) return *this; + if (redraw) { + const unsigned int bufsize = width*height*4; + void *odata = cimg_std::malloc(bufsize); + cimg_std::memcpy(odata,data,bufsize); + assign(width,height,title,normalization,!is_fullscreen,false); + cimg_std::memcpy(data,odata,bufsize); + cimg_std::free(odata); + return paint(); + } + return assign(width,height,title,normalization,!is_fullscreen,false); + } + + static OSStatus CarbonEventHandler(EventHandlerCallRef myHandler, EventRef theEvent, void* userData) { + OSStatus result = eventNotHandledErr; + CImgDisplay* disp = (CImgDisplay*) userData; + (void)myHandler; // Avoid "unused parameter" + cimg::CarbonInfo& c = cimg::CarbonAttr(); + // Gets the associated display + if (disp) { + // Window events are always handled + if (GetEventClass(theEvent)==kEventClassWindow) switch (GetEventKind (theEvent)) { + case kEventWindowClose : + disp->mouse_x = disp->mouse_y = -1; + disp->window_x = disp->window_y = 0; + if (disp->button) { + cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1); + disp->button = 0; + } + if (disp->key) { + cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); + disp->key = 0; + } + if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; } + disp->is_closed = true; + HideWindow(disp->carbonWindow); + disp->is_event = true; + MPSignalSemaphore(c.wait_event); + result = noErr; + break; + // There is a lot of case where we have to redraw our window + case kEventWindowBoundsChanging : + case kEventWindowResizeStarted : + case kEventWindowCollapsed : //Not sure it's really needed :-) + break; + case kEventWindowZoomed : + case kEventWindowExpanded : + case kEventWindowResizeCompleted : { + MPEnterCriticalRegion(disp->paintCriticalRegion, kDurationForever); + // Now we retrieve the new size of the window + Rect newContentRect; + GetWindowBounds(disp->carbonWindow,kWindowContentRgn,&newContentRect); + const unsigned int + nw = (unsigned int)(newContentRect.right - newContentRect.left), + nh = (unsigned int)(newContentRect.bottom - newContentRect.top); + + // Then we update CImg internal settings + if (nw && nh && (nw!=disp->width || nh!=disp->height)) { + disp->window_width = nw; + disp->window_height = nh; + disp->mouse_x = disp->mouse_y = -1; + disp->is_resized = true; + } + disp->is_event = true; + MPExitCriticalRegion(disp->paintCriticalRegion); + disp->paint(); // Coords changed, must update the screen + MPSignalSemaphore(c.wait_event); + result = noErr; + } break; + case kEventWindowDragStarted : + case kEventWindowDragCompleted : { + MPEnterCriticalRegion(disp->paintCriticalRegion, kDurationForever); + // Now we retrieve the new size of the window + Rect newContentRect ; + GetWindowBounds(disp->carbonWindow,kWindowStructureRgn,&newContentRect); + const int nx = (int)(newContentRect.left), ny = (int)(newContentRect.top); + // Then we update CImg internal settings + if (nx!=disp->window_x || ny!=disp->window_y) { + disp->window_x = nx; + disp->window_y = ny; + disp->is_moved = true; + } + disp->is_event = true; + MPExitCriticalRegion(disp->paintCriticalRegion); + disp->paint(); // Coords changed, must update the screen + MPSignalSemaphore(c.wait_event); + result = noErr; + } break; + case kEventWindowPaint : + disp->paint(); + break; + } + + switch (GetEventClass(theEvent)) { + case kEventClassKeyboard : { + if (GetEventKind(theEvent)==kEventRawKeyModifiersChanged) { + // Apple has special keys named "notifiers", we have to convert this (exotic ?) key handling into the regular CImg processing. + UInt32 newModifiers; + if (GetEventParameter(theEvent,kEventParamKeyModifiers,typeUInt32,0,sizeof(UInt32),0,&newModifiers)==noErr) { + int newKeyCode = -1; + UInt32 changed = disp->lastKeyModifiers^newModifiers; + // Find what changed here + if ((changed & rightShiftKey)!=0) newKeyCode = cimg::keySHIFTRIGHT; + if ((changed & shiftKey)!=0) newKeyCode = cimg::keySHIFTLEFT; + + // On the Mac, the "option" key = the ALT key + if ((changed & (optionKey | rightOptionKey))!=0) newKeyCode = cimg::keyALTGR; + if ((changed & controlKey)!=0) newKeyCode = cimg::keyCTRLLEFT; + if ((changed & rightControlKey)!=0) newKeyCode = cimg::keyCTRLRIGHT; + if ((changed & cmdKey)!=0) newKeyCode = cimg::keyAPPLEFT; + if ((changed & alphaLock)!=0) newKeyCode = cimg::keyCAPSLOCK; + if (newKeyCode != -1) { // Simulate keystroke + if (disp->key) cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); + disp->key = (int)newKeyCode; + } + disp->lastKeyModifiers = newModifiers; // Save current state + } + disp->is_event = true; + MPSignalSemaphore(c.wait_event); + } + if (GetEventKind(theEvent)==kEventRawKeyDown || GetEventKind(theEvent)==kEventRawKeyRepeat) { + char keyCode; + if (GetEventParameter(theEvent,kEventParamKeyMacCharCodes,typeChar,0,sizeof(keyCode),0,&keyCode)==noErr) { + disp->update_iskey((unsigned int)keyCode,true); + if (disp->key) cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); + disp->key = (unsigned int)keyCode; + if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; } + } + disp->is_event = true; + MPSignalSemaphore(c.wait_event); + } + } break; + + case kEventClassMouse : + switch (GetEventKind(theEvent)) { + case kEventMouseDragged : + // When you push the main button on the Apple mouse while moving it, you got NO kEventMouseMoved msg, + // but a kEventMouseDragged one. So we merge them here. + case kEventMouseMoved : + HIPoint point; + if (GetEventParameter(theEvent,kEventParamMouseLocation,typeHIPoint,0,sizeof(point),0,&point)==noErr) { + if (_CbToLocalPointFromMouseEvent(theEvent,disp->carbonWindow,&point)) { + disp->mouse_x = (int)point.x; + disp->mouse_y = (int)point.y; + if (disp->mouse_x<0 || disp->mouse_y<0 || disp->mouse_x>=disp->dimx() || disp->mouse_y>=disp->dimy()) + disp->mouse_x = disp->mouse_y = -1; + } else disp->mouse_x = disp->mouse_y = -1; + } + disp->is_event = true; + MPSignalSemaphore(c.wait_event); + break; + case kEventMouseDown : + UInt16 btn; + if (GetEventParameter(theEvent,kEventParamMouseButton,typeMouseButton,0,sizeof(btn),0,&btn)==noErr) { + cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1); + if (btn==kEventMouseButtonPrimary) disp->button|=1U; + // For those who don't have a multi-mouse button (as me), I think it's better to allow the user + // to emulate a right click by using the Control key + if ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0) + cimg::warn("CImgDisplay::CarbonEventHandler() : Will emulate right click now [Down]"); + if (btn==kEventMouseButtonSecondary || ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)) disp->button|=2U; + if (btn==kEventMouseButtonTertiary) disp->button|=4U; + } + disp->is_event = true; + MPSignalSemaphore(c.wait_event); + break; + case kEventMouseWheelMoved : + EventMouseWheelAxis wheelax; + SInt32 delta; + if (GetEventParameter(theEvent,kEventParamMouseWheelAxis,typeMouseWheelAxis,0,sizeof(wheelax),0,&wheelax)==noErr) + if (wheelax==kEventMouseWheelAxisY) { + if (GetEventParameter(theEvent,kEventParamMouseWheelDelta,typeLongInteger,0,sizeof(delta),0,&delta)==noErr) + if (delta>0) disp->wheel+=delta/120; //FIXME: why 120 ? + disp->is_event = true; + MPSignalSemaphore(c.wait_event); + } + break; + } + } + + switch (GetEventClass(theEvent)) { + case kEventClassKeyboard : + if (GetEventKind(theEvent)==kEventRawKeyUp) { + UInt32 keyCode; + if (GetEventParameter(theEvent,kEventParamKeyCode,typeUInt32,0,sizeof(keyCode),0,&keyCode)==noErr) { + disp->update_iskey((unsigned int)keyCode,false); + if (disp->key) { cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); disp->key = 0; } + if (disp->released_key) cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); + disp->released_key = (int)keyCode; + } + disp->is_event = true; + MPSignalSemaphore(c.wait_event); + } + break; + + case kEventClassMouse : + switch (GetEventKind(theEvent)) { + case kEventMouseUp : + UInt16 btn; + if (GetEventParameter(theEvent,kEventParamMouseButton,typeMouseButton,0,sizeof(btn),0,&btn)==noErr) { + cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1); + if (btn==kEventMouseButtonPrimary) disp->button&=~1U; + // See note in kEventMouseDown handler. + if ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0) + cimg::warn("CImgDisplay::CarbonEventHandler() : Will emulate right click now [Up]"); + if (btn==kEventMouseButtonSecondary || ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)) disp->button&=~2U; + if (btn==kEventMouseButtonTertiary) disp->button&=~2U; + } + disp->is_event = true; + MPSignalSemaphore(c.wait_event); + break; + } + } + } + return (result); + } + + static void* _events_thread(void* args) { + (void)args; // Make the compiler happy + cimg::CarbonInfo& c = cimg::CarbonAttr(); + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0); + MPSignalSemaphore(c.sync_event); // Notify the caller that all goes fine + EventRef theEvent; + EventTargetRef theTarget; + OSStatus err; + CbSerializedQuery* query; + theTarget = GetEventDispatcherTarget(); + + // Enter in the main loop + while (true) { + pthread_testcancel(); /* Check if cancelation happens */ + err = ReceiveNextEvent(0,0,kDurationImmediate,true,&theEvent); // Fetch new events + if (err==noErr) { // Received a carbon event, so process it ! + SendEventToEventTarget (theEvent, theTarget); + ReleaseEvent(theEvent); + } else if (err == eventLoopTimedOutErr) { // There is no event to process, so check if there is new messages to process + OSStatus r =MPWaitOnQueue(c.com_queue,(void**)&query,0,0,10*kDurationMillisecond); + if (r!=noErr) continue; //nothing in the queue or an error.., bye + // If we're here, we've something to do now. + if (query) { + switch (query->kind) { + case COM_SETMOUSEPOS : { // change the cursor position + query->success = CGDisplayMoveCursorToPoint(kCGDirectMainDisplay,CGPointMake(query->sender->window_x+query->x,query->sender->window_y+query->y)) + == kCGErrorSuccess; + if (query->success) { + query->sender->mouse_x = query->x; + query->sender->mouse_y = query->y; + } else cimg::warn("CImgDisplay::_events_thread() : CGDisplayMoveCursorToPoint failed."); + } break; + case COM_SETTITLE : { // change the title bar caption + CFStringRef windowTitle = CFStringCreateWithCString(0,query->c,kCFStringEncodingMacRoman); + query->success = SetWindowTitleWithCFString(query->sender->carbonWindow,windowTitle)==noErr; + if (!query->success) + cimg::warn("CImgDisplay::_events_thread() : SetWindowTitleWithCFString failed."); + CFRelease(windowTitle); + } break; + case COM_RESIZEWINDOW : { // Resize a window + SizeWindow(query->sender->carbonWindow,query->x,query->y,query->update); + // If the window has been resized successfully, update display informations + query->sender->window_width = query->x; + query->sender->window_height = query->y; + query->success = true; + } break; + case COM_MOVEWINDOW : { // Move a window + MoveWindow(query->sender->carbonWindow,query->x,query->y,false); + query->sender->window_x = query->x; + query->sender->window_y = query->y; + query->sender->is_moved = false; + query->success = true; + } break; + case COM_SHOWMOUSE : { // Show the mouse + query->success = CGDisplayShowCursor(kCGDirectMainDisplay)==noErr; + if (!query->success) + cimg::warn("CImgDisplay::_events_thread() : CGDisplayShowCursor failed."); + } break; + case COM_HIDEMOUSE : { // Hide the mouse + query->success = CGDisplayHideCursor(kCGDirectMainDisplay)==noErr; + if (!query->success) + cimg::warn("CImgDisplay::_events_thread() : CGDisplayHideCursor failed."); + } break; + case COM_SHOWWINDOW : { // We've to show a window + ShowWindow(query->sender->carbonWindow); + query->success = true; + query->sender->is_closed = false; + } break; + case COM_HIDEWINDOW : { // We've to show a window + HideWindow(query->sender->carbonWindow); + query->sender->is_closed = true; + query->sender->window_x = query->sender->window_y = 0; + query->success = true; + } break; + case COM_RELEASEWINDOW : { // We have to release a given window handle + query->success = true; + CFRelease(query->sender->carbonWindow); + } break; + case COM_CREATEWINDOW : { // We have to create a window + query->success = true; + WindowAttributes windowAttrs; + Rect contentRect; + if (query->createFullScreenWindow) { + // To simulate a "true" full screen, we remove menus and close boxes + windowAttrs = (1L << 9); //Why ? kWindowNoTitleBarAttribute seems to be not defined on 10.3 + // Define a full screen bound rect + SetRect(&contentRect,0,0,CGDisplayPixelsWide(kCGDirectMainDisplay),CGDisplayPixelsHigh(kCGDirectMainDisplay)); + } else { // Set the window size + SetRect(&contentRect,0,0,query->sender->width,query->sender->height); // Window will be centered with RepositionWindow. + // Use default attributes + windowAttrs = kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute | kWindowInWindowMenuAttribute | kWindowLiveResizeAttribute; + } + // Update window position + if (query->createClosedWindow) query->sender->window_x = query->sender->window_y = 0; + else { + query->sender->window_x = contentRect.left; + query->sender->window_y = contentRect.top; + } + // Update window flags + query->sender->window_width = query->sender->width; + query->sender->window_height = query->sender->height; + query->sender->flush(); + // Create the window + if (CreateNewWindow(kDocumentWindowClass,windowAttrs,&contentRect,&query->sender->carbonWindow)!=noErr) { + query->success = false; + cimg::warn("CImgDisplay::_events_thread() : CreateNewWindow() failed."); + } + // Send it to the foreground + if (RepositionWindow(query->sender->carbonWindow,0,kWindowCenterOnMainScreen)!=noErr) query->success = false; + // Show it, if needed + if (!query->createClosedWindow) ShowWindow(query->sender->carbonWindow); + + // Associate a valid event handler + EventTypeSpec eventList[] = { + { kEventClassWindow, kEventWindowClose }, + { kEventClassWindow, kEventWindowResizeStarted }, + { kEventClassWindow, kEventWindowResizeCompleted }, + { kEventClassWindow, kEventWindowDragStarted}, + { kEventClassWindow, kEventWindowDragCompleted }, + { kEventClassWindow, kEventWindowPaint }, + { kEventClassWindow, kEventWindowBoundsChanging }, + { kEventClassWindow, kEventWindowCollapsed }, + { kEventClassWindow, kEventWindowExpanded }, + { kEventClassWindow, kEventWindowZoomed }, + { kEventClassKeyboard, kEventRawKeyDown }, + { kEventClassKeyboard, kEventRawKeyUp }, + { kEventClassKeyboard, kEventRawKeyRepeat }, + { kEventClassKeyboard, kEventRawKeyModifiersChanged }, + { kEventClassMouse, kEventMouseMoved }, + { kEventClassMouse, kEventMouseDown }, + { kEventClassMouse, kEventMouseUp }, + { kEventClassMouse, kEventMouseDragged } + }; + + // Set up the handler + if (InstallWindowEventHandler(query->sender->carbonWindow,NewEventHandlerUPP(CarbonEventHandler),GetEventTypeCount(eventList), + eventList,(void*)query->sender,0)!=noErr) { + query->success = false; + cimg::warn("CImgDisplay::_events_thread() : InstallWindowEventHandler failed."); + } + + // Paint + query->sender->paint(); + } break; + default : + cimg::warn("CImgDisplay::_events_thread() : Received unknow code %d.",query->kind); + } + // Signal that the message has been processed + MPSignalSemaphore(c.sync_event); + } + } + } + // If we are here, the application is now finished + pthread_exit(0); + } + + CImgDisplay& assign() { + if (is_empty()) return *this; + cimg::CarbonInfo& c = cimg::CarbonAttr(); + // Destroy the window associated to the display + _CbFreeAttachedWindow(c); + // Don't destroy the background thread here. + // If you check whether _CbFreeAttachedWindow() returned true, + // - saying that there were no window left on screen - and + // you destroy the background thread here, ReceiveNextEvent won't + // work anymore if you create a new window after. So the + // background thread must be killed (pthread_cancel() + pthread_join()) + // only on the application shutdown. + + // Finalize graphics + _CbFinalizeGraphics(); + + // Do some cleanup + if (data) delete[] data; + if (title) delete[] title; + width = height = normalization = window_width = window_height = 0; + window_x = window_y = 0; + is_fullscreen = false; + is_closed = true; + min = max = 0; + title = 0; + flush(); + if (MPDeleteCriticalRegion(paintCriticalRegion)!=noErr) + throw CImgDisplayException("CImgDisplay()::assign() : MPDeleteCriticalRegion failed."); + return *this; + } + + CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *ptitle=0, + const unsigned int normalization_type=3, + const bool fullscreen_flag=false, const bool closed_flag=false) { + cimg::CarbonInfo& c = cimg::CarbonAttr(); + + // Allocate space for window title + const int s = cimg::strlen(ptitle)+1; + char *tmp_title = s?new char[s]:0; + if (s) cimg_std::memcpy(tmp_title,ptitle,s*sizeof(char)); + + // Destroy previous window if existing + if (!is_empty()) assign(); + + // Set display variables + width = cimg::min(dimw,(unsigned int)screen_dimx()); + height = cimg::min(dimh,(unsigned int)screen_dimy()); + normalization = normalization_type<4?normalization_type:3; + is_fullscreen = fullscreen_flag; + is_closed = closed_flag; + lastKeyModifiers = 0; + title = tmp_title; + flush(); + + // Create the paint CR + if (MPCreateCriticalRegion(&paintCriticalRegion) != noErr) + throw CImgDisplayException("CImgDisplay::_assign() : MPCreateCriticalRegion() failed."); + + // Create the thread if it's not already created + if (c.event_thread==0) { + // Background thread does not exists, so create it ! + if (pthread_create(&c.event_thread,0,_events_thread,0)!=0) + throw CImgDisplayException("CImgDisplay::_assign() : pthread_create() failed."); + // Wait for thread initialization + MPWaitOnSemaphore(c.sync_event, kDurationForever); + } + + // Init disp. graphics + data = new unsigned int[width*height]; + _CbInitializeGraphics(); + + // Now ask the thread to create the window + _CbCreateAttachedWindow(c,ptitle,fullscreen_flag,closed_flag); + return *this; + } + +#endif + + }; + + /* + #-------------------------------------- + # + # + # + # Definition of the CImg structure + # + # + # + #-------------------------------------- + */ + + //! Class representing an image (up to 4 dimensions wide), each pixel being of type \c T. + /** + This is the main class of the %CImg Library. It declares and constructs + an image, allows access to its pixel values, and is able to perform various image operations. + + \par Image representation + + A %CImg image is defined as an instance of the container \ref CImg<\c T>, which contains a regular grid of pixels, + each pixel value being of type \c T. The image grid can have up to 4 dimensions : width, height, depth + and number of channels. + Usually, the three first dimensions are used to describe spatial coordinates (x,y,z), while the number of channels + is rather used as a vector-valued dimension (it may describe the R,G,B color channels for instance). + If you need a fifth dimension, you can use image lists \ref CImgList<\c T> rather than simple images \ref CImg<\c T>. + + Thus, the \ref CImg<\c T> class is able to represent volumetric images of vector-valued pixels, + as well as images with less dimensions (1D scalar signal, 2D color images, ...). + Most member functions of the class CImg<\c T> are designed to handle this maximum case of (3+1) dimensions. + + Concerning the pixel value type \c T : + fully supported template types are the basic C++ types : unsigned char, char, short, unsigned int, int, + unsigned long, long, float, double, ... . + Typically, fast image display can be done using CImg images, + while complex image processing algorithms may be rather coded using CImg or CImg + images that have floating-point pixel values. The default value for the template T is \c float. + Using your own template types may be possible. However, you will certainly have to define the complete set + of arithmetic and logical operators for your class. + + \par Image structure + + The \ref CImg<\c T> structure contains \a six fields : + - \ref width defines the number of \a columns of the image (size along the X-axis). + - \ref height defines the number of \a rows of the image (size along the Y-axis). + - \ref depth defines the number of \a slices of the image (size along the Z-axis). + - \ref dim defines the number of \a channels of the image (size along the V-axis). + - \ref data defines a \a pointer to the \a pixel \a data (of type \c T). + - \ref is_shared is a boolean that tells if the memory buffer \ref data is shared with + another image. + + You can access these fields publicly although it is recommended to use the dedicated functions + dimx(), dimy(), dimz(), dimv() and ptr() to do so. + Image dimensions are not limited to a specific range (as long as you got enough available memory). + A value of \e 1 usually means that the corresponding dimension is \a flat. + If one of the dimensions is \e 0, or if the data pointer is null, the image is considered as \e empty. + Empty images should not contain any pixel data and thus, will not be processed by CImg member functions + (a CImgInstanceException will be thrown instead). + Pixel data are stored in memory, in a non interlaced mode (See \ref cimg_storage). + + \par Image declaration and construction + + Declaring an image can be done by using one of the several available constructors. + Here is a list of the most used : + + - Construct images from arbitrary dimensions : + - CImg img; declares an empty image. + - CImg img(128,128); declares a 128x128 greyscale image with + \c unsigned \c char pixel values. + - CImg img(3,3); declares a 3x3 matrix with \c double coefficients. + - CImg img(256,256,1,3); declares a 256x256x1x3 (color) image + (colors are stored as an image with three channels). + - CImg img(128,128,128); declares a 128x128x128 volumetric and greyscale image + (with \c double pixel values). + - CImg<> img(128,128,128,3); declares a 128x128x128 volumetric color image + (with \c float pixels, which is the default value of the template parameter \c T). + - \b Note : images pixels are not automatically initialized to 0. You may use the function \ref fill() to + do it, or use the specific constructor taking 5 parameters like this : + CImg<> img(128,128,128,3,0); declares a 128x128x128 volumetric color image with all pixel values to 0. + + - Construct images from filenames : + - CImg img("image.jpg"); reads a JPEG color image from the file "image.jpg". + - CImg img("analyze.hdr"); reads a volumetric image (ANALYZE7.5 format) from the file "analyze.hdr". + - \b Note : You need to install ImageMagick + to be able to read common compressed image formats (JPG,PNG, ...) (See \ref cimg_files_io). + + - Construct images from C-style arrays : + - CImg img(data_buffer,256,256); constructs a 256x256 greyscale image from a \c int* buffer + \c data_buffer (of size 256x256=65536). + - CImg img(data_buffer,256,256,1,3,false); constructs a 256x256 color image + from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels follow each others). + - CImg img(data_buffer,256,256,1,3,true); constructs a 256x256 color image + from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels are multiplexed). + + The complete list of constructors can be found here. + + \par Most useful functions + + The \ref CImg<\c T> class contains a lot of functions that operates on images. + Some of the most useful are : + + - operator()(), operator[]() : allows to access or write pixel values. + - display() : displays the image in a new window. + **/ + template + struct CImg { + + //! Variable representing the width of the instance image (i.e. dimensions along the X-axis). + /** + \remark + - Prefer using the function CImg::dimx() to get information about the width of an image. + - Use function CImg::resize() to set a new width for an image. Setting directly the variable \c width would probably + result in a library crash. + - Empty images have \c width defined to \c 0. + **/ + unsigned int width; + + //! Variable representing the height of the instance image (i.e. dimensions along the Y-axis). + /** + \remark + - Prefer using the function CImg::dimy() to get information about the height of an image. + - Use function CImg::resize() to set a new height for an image. Setting directly the variable \c height would probably + result in a library crash. + - 1D signals have \c height defined to \c 1. + - Empty images have \c height defined to \c 0. + **/ + unsigned int height; + + //! Variable representing the depth of the instance image (i.e. dimensions along the Z-axis). + /** + \remark + - Prefer using the function CImg::dimz() to get information about the depth of an image. + - Use function CImg::resize() to set a new depth for an image. Setting directly the variable \c depth would probably + result in a library crash. + - Classical 2D images have \c depth defined to \c 1. + - Empty images have \c depth defined to \c 0. + **/ + unsigned int depth; + + //! Variable representing the number of channels of the instance image (i.e. dimensions along the V-axis). + /** + \remark + - Prefer using the function CImg::dimv() to get information about the depth of an image. + - Use function CImg::resize() to set a new vector dimension for an image. Setting directly the variable \c dim would probably + result in a library crash. + - Scalar-valued images (one value per pixel) have \c dim defined to \c 1. + - Empty images have \c depth defined to \c 0. + **/ + unsigned int dim; + + //! Variable telling if pixel buffer of the instance image is shared with another one. + bool is_shared; + + //! Pointer to the first pixel of the pixel buffer. + T *data; + + //! Iterator type for CImg. + /** + \remark + - An \p iterator is a T* pointer (address of a pixel value in the pixel buffer). + - Iterators are not directly used in %CImg functions, they have been introduced for compatibility with the STL. + **/ + typedef T* iterator; + + //! Const iterator type for CImg. + /** + \remark + - A \p const_iterator is a const T* pointer (address of a pixel value in the pixel buffer). + - Iterators are not directly used in %CImg functions, they have been introduced for compatibility with the STL. + **/ + typedef const T* const_iterator; + + //! Get value type + typedef T value_type; + + // Define common T-dependant types. + typedef typename cimg::superset::type Tbool; + typedef typename cimg::superset::type Tuchar; + typedef typename cimg::superset::type Tchar; + typedef typename cimg::superset::type Tushort; + typedef typename cimg::superset::type Tshort; + typedef typename cimg::superset::type Tuint; + typedef typename cimg::superset::type Tint; + typedef typename cimg::superset::type Tulong; + typedef typename cimg::superset::type Tlong; + typedef typename cimg::superset::type Tfloat; + typedef typename cimg::superset::type Tdouble; + typedef typename cimg::last::type boolT; + typedef typename cimg::last::type ucharT; + typedef typename cimg::last::type charT; + typedef typename cimg::last::type ushortT; + typedef typename cimg::last::type shortT; + typedef typename cimg::last::type uintT; + typedef typename cimg::last::type intT; + typedef typename cimg::last::type ulongT; + typedef typename cimg::last::type longT; + typedef typename cimg::last::type floatT; + typedef typename cimg::last::type doubleT; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- +#ifdef cimg_plugin +#include cimg_plugin +#endif +#ifdef cimg_plugin1 +#include cimg_plugin1 +#endif +#ifdef cimg_plugin2 +#include cimg_plugin2 +#endif +#ifdef cimg_plugin3 +#include cimg_plugin3 +#endif +#ifdef cimg_plugin4 +#include cimg_plugin4 +#endif +#ifdef cimg_plugin5 +#include cimg_plugin5 +#endif +#ifdef cimg_plugin6 +#include cimg_plugin6 +#endif +#ifdef cimg_plugin7 +#include cimg_plugin7 +#endif +#ifdef cimg_plugin8 +#include cimg_plugin8 +#endif +#ifndef cimg_plugin_greycstoration +#define cimg_plugin_greycstoration_count +#endif +#ifndef cimg_plugin_greycstoration_lock +#define cimg_plugin_greycstoration_lock +#endif +#ifndef cimg_plugin_greycstoration_unlock +#define cimg_plugin_greycstoration_unlock +#endif + + //@} + + //-------------------------------------- + // + //! \name Constructors-Destructor-Copy + //@{ + //-------------------------------------- + + //! Destructor. + /** + The destructor destroys the instance image. + \remark + - Destructing an empty or shared image does nothing. + - Otherwise, all memory used to store the pixel data of the instance image is freed. + - When destroying a non-shared image, be sure that every shared instances of the same image are + also destroyed to avoid further access to desallocated memory buffers. + **/ + ~CImg() { + if (data && !is_shared) delete[] data; + } + + //! Default constructor. + /** + The default constructor creates an empty instance image. + \remark + - An empty image does not contain any data and has all of its dimensions \ref width, \ref height, \ref depth, \ref dim + set to 0 as well as its pointer to the pixel buffer \ref data. + - An empty image is non-shared. + **/ + CImg(): + width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {} + + //! Constructs a new image with given size (\p dx,\p dy,\p dz,\p dv). + /** + This constructors create an instance image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T. + \param dx Desired size along the X-axis, i.e. the \ref width of the image. + \param dy Desired size along the Y-axis, i.e. the \ref height of the image. + \param dz Desired size along the Z-axis, i.e. the \ref depth of the image. + \param dv Desired size along the V-axis, i.e. the number of image channels \ref dim. + \remark + - If one of the input dimension \p dx,\p dy,\p dz or \p dv is set to 0, the created image is empty + and all has its dimensions set to 0. No memory for pixel data is then allocated. + - This constructor creates only non-shared images. + - Image pixels allocated by this constructor are \b not \b initialized. + Use the constructor CImg(const unsigned int,const unsigned int,const unsigned int,const unsigned int,const T) + to get an image of desired size with pixels set to a particular value. + **/ + explicit CImg(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1, const unsigned int dv=1): + is_shared(false) { + const unsigned long siz = dx*dy*dz*dv; + if (siz) { width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; } + else { width = height = depth = dim = 0; data = 0; } + } + + //! Construct an image with given size (\p dx,\p dy,\p dz,\p dv) and with pixel having a default value \p val. + /** + This constructor creates an instance image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T and sets all pixel + values of the created instance image to \p val. + \param dx Desired size along the X-axis, i.e. the \ref width of the image. + \param dy Desired size along the Y-axis, i.e. the \ref height of the image. + \param dz Desired size along the Z-axis, i.e. the \ref depth of the image. + \param dv Desired size along the V-axis, i.e. the number of image channels \p dim. + \param val Default value for image pixels. + \remark + - This constructor has the same properties as CImg(const unsigned int,const unsigned int,const unsigned int,const unsigned int). + **/ + CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, const T val): + is_shared(false) { + const unsigned long siz = dx*dy*dz*dv; + if (siz) { width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; fill(val); } + else { width = height = depth = dim = 0; data = 0; } + } + + //! Construct an image with given size (\p dx,\p dy,\p dz,\p dv) and with specified pixel values (int version). + CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, + const int val0, const int val1, ...):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) { +#define _CImg_stdarg(img,a0,a1,N,t) { \ + unsigned int _siz = (unsigned int)N; \ + if (_siz--) { \ + va_list ap; \ + va_start(ap,a1); \ + T *ptrd = (img).data; \ + *(ptrd++) = (T)a0; \ + if (_siz--) { \ + *(ptrd++) = (T)a1; \ + for (; _siz; --_siz) *(ptrd++) = (T)va_arg(ap,t); \ + } \ + va_end(ap); \ + }} + assign(dx,dy,dz,dv); + _CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,int); + } + + //! Construct an image with given size (\p dx,\p dy,\p dz,\p dv) and with specified pixel values (double version). + CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, + const double val0, const double val1, ...):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) { + assign(dx,dy,dz,dv); + _CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,double); + } + + //! Construct an image with given size and with specified values given in a string. + CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, + const char *const values, const bool repeat_pattern):is_shared(false) { + const unsigned long siz = dx*dy*dz*dv; + if (siz) { width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; fill(values,repeat_pattern); } + else { width = height = depth = dim = 0; data = 0; } + } + + //! Construct an image from a raw memory buffer. + /** + This constructor creates an instance image of size (\p dx,\p dy,\p dz,\p dv) and fill its pixel buffer by + copying data values from the input raw pixel buffer \p data_buffer. + **/ + template + CImg(const t *const data_buffer, const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dv=1, const bool shared=false):is_shared(false) { + if (shared) + throw CImgArgumentException("CImg<%s>::CImg() : Cannot construct a shared instance image from a (%s*) buffer " + "(different pixel types).", + pixel_type(),CImg::pixel_type()); + const unsigned long siz = dx*dy*dz*dv; + if (data_buffer && siz) { + width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; + const t *ptrs = data_buffer + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs); + } else { width = height = depth = dim = 0; data = 0; } + } + +#ifndef cimg_use_visualcpp6 + CImg(const T *const data_buffer, const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dv=1, const bool shared=false) +#else + CImg(const T *const data_buffer, const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dv, const bool shared) +#endif + { + const unsigned long siz = dx*dy*dz*dv; + if (data_buffer && siz) { + width = dx; height = dy; depth = dz; dim = dv; is_shared = shared; + if (is_shared) data = const_cast(data_buffer); + else { data = new T[siz]; cimg_std::memcpy(data,data_buffer,siz*sizeof(T)); } + } else { width = height = depth = dim = 0; is_shared = false; data = 0; } + } + + //! Default copy constructor. + /** + The default copy constructor creates a new instance image having same dimensions + (\ref width, \ref height, \ref depth, \ref dim) and same pixel values as the input image \p img. + \param img The input image to copy. + \remark + - If the input image \p img is non-shared or have a different template type \p t != \p T, + the default copy constructor allocates a new pixel buffer and copy the pixel data + of \p img into it. In this case, the pointers \ref data to the pixel buffers of the two images are different + and the resulting instance image is non-shared. + - If the input image \p img is shared and has the same template type \p t == \p T, + the default copy constructor does not allocate a new pixel buffer and the resulting instance image + shares its pixel buffer with the input image \p img, which means that modifying pixels of \p img also modifies + the created instance image. + - Copying an image having a different template type \p t != \p T performs a crude static cast conversion of each pixel value from + type \p t to type \p T. + - Copying an image having the same template type \p t == \p T is significantly faster. + **/ + template + CImg(const CImg& img):is_shared(false) { + const unsigned int siz = img.size(); + if (img.data && siz) { + width = img.width; height = img.height; depth = img.depth; dim = img.dim; data = new T[siz]; + const t *ptrs = img.data + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs); + } else { width = height = depth = dim = 0; data = 0; } + } + + CImg(const CImg& img) { + const unsigned int siz = img.size(); + if (img.data && siz) { + width = img.width; height = img.height; depth = img.depth; dim = img.dim; is_shared = img.is_shared; + if (is_shared) data = const_cast(img.data); + else { data = new T[siz]; cimg_std::memcpy(data,img.data,siz*sizeof(T)); } + } else { width = height = depth = dim = 0; is_shared = false; data = 0; } + } + + //! Advanced copy constructor. + /** + The advanced copy constructor - as the default constructor CImg(const CImg< t >&) - creates a new instance image having same dimensions + \ref width, \ref height, \ref depth, \ref dim and same pixel values as the input image \p img. + But it also decides if the created instance image shares its memory with the input image \p img (if the input parameter + \p shared is set to \p true) or not (if the input parameter \p shared is set to \p false). + \param img The input image to copy. + \param shared Boolean flag that decides if the copy is shared on non-shared. + \remark + - It is not possible to create a shared copy if the input image \p img is empty or has a different pixel type \p t != \p T. + - If a non-shared copy of the input image \p img is created, a new memory buffer is allocated for pixel data. + - If a shared copy of the input image \p img is created, no extra memory is allocated and the pixel buffer of the instance + image is the same as the one used by the input image \p img. + **/ + template + CImg(const CImg& img, const bool shared):is_shared(false) { + if (shared) + throw CImgArgumentException("CImg<%s>::CImg() : Cannot construct a shared instance image from a CImg<%s> instance " + "(different pixel types).", + pixel_type(),CImg::pixel_type()); + const unsigned int siz = img.size(); + if (img.data && siz) { + width = img.width; height = img.height; depth = img.depth; dim = img.dim; data = new T[siz]; + const t *ptrs = img.data + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs); + } else { width = height = depth = dim = 0; data = 0; } + } + + CImg(const CImg& img, const bool shared) { + const unsigned int siz = img.size(); + if (img.data && siz) { + width = img.width; height = img.height; depth = img.depth; dim = img.dim; is_shared = shared; + if (is_shared) data = const_cast(img.data); + else { data = new T[siz]; cimg_std::memcpy(data,img.data,siz*sizeof(T)); } + } else { width = height = depth = dim = 0; is_shared = false; data = 0; } + } + + //! Construct an image using dimensions of another image + template + CImg(const CImg& img, const char *const dimensions):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) { + assign(img,dimensions); + } + + //! Construct an image using dimensions of another image, and fill it with a default value + template + CImg(const CImg& img, const char *const dimensions, const T val): + width(0),height(0),depth(0),dim(0),is_shared(false),data(0) { + assign(img,dimensions).fill(val); + } + + //! Construct an image from an image file. + /** + This constructor creates an instance image by reading it from a file. + \param filename Filename of the image file. + \remark + - The image format is deduced from the filename only by looking for the filename extension i.e. without + analyzing the file itself. + - Recognized image formats depend on the tools installed on your system or the external libraries you use to link your code with. + More informations on this topic can be found in cimg_files_io. + - If the filename is not found, a CImgIOException is thrown by this constructor. + **/ + CImg(const char *const filename):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) { + assign(filename); + } + + //! Construct an image from the content of a CImgDisplay instance. + CImg(const CImgDisplay &disp):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) { + disp.snapshot(*this); + } + + //! In-place version of the default constructor/destructor. + /** + This function replaces the instance image by an empty image. + \remark + - Memory used by the previous content of the instance image is freed if necessary. + - If the instance image was initially shared, it is replaced by a (non-shared) empty image. + - This function is useful to free memory used by an image that is not of use, but which + has been created in the current code scope (i.e. not destroyed yet). + **/ + CImg& assign() { + if (data && !is_shared) delete[] data; + width = height = depth = dim = 0; is_shared = false; data = 0; + return *this; + } + + //! In-place version of the default constructor. + /** + This function is strictly equivalent to \ref assign() and has been + introduced for having a STL-compliant function name. + **/ + CImg& clear() { + return assign(); + } + + //! In-place version of the previous constructor. + /** + This function replaces the instance image by a new image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T. + \param dx Desired size along the X-axis, i.e. the \ref width of the image. + \param dy Desired size along the Y-axis, i.e. the \ref height of the image. + \param dz Desired size along the Z-axis, i.e. the \ref depth of the image. + \param dv Desired size along the V-axis, i.e. the number of image channels \p dim. + - If one of the input dimension \p dx,\p dy,\p dz or \p dv is set to 0, the instance image becomes empty + and all has its dimensions set to 0. No memory for pixel data is then allocated. + - Memory buffer used to store previous pixel values is freed if necessary. + - If the instance image is shared, this constructor actually does nothing more than verifying + that new and old image dimensions fit. + - Image pixels allocated by this function are \b not \b initialized. + Use the function assign(const unsigned int,const unsigned int,const unsigned int,const unsigned int,const T) + to assign an image of desired size with pixels set to a particular value. + **/ + CImg& assign(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1, const unsigned int dv=1) { + const unsigned long siz = dx*dy*dz*dv; + if (!siz) return assign(); + const unsigned long curr_siz = size(); + if (siz!=curr_siz) { + if (is_shared) + throw CImgArgumentException("CImg<%s>::assign() : Cannot assign image (%u,%u,%u,%u) to shared instance image (%u,%u,%u,%u,%p).", + pixel_type(),dx,dy,dz,dv,width,height,depth,dim,data); + else { if (data) delete[] data; data = new T[siz]; } + } + width = dx; height = dy; depth = dz; dim = dv; + return *this; + } + + //! In-place version of the previous constructor. + /** + This function replaces the instance image by a new image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T + and sets all pixel values of the instance image to \p val. + \param dx Desired size along the X-axis, i.e. the \ref width of the image. + \param dy Desired size along the Y-axis, i.e. the \ref height of the image. + \param dz Desired size along the Z-axis, i.e. the \ref depth of the image. + \param dv Desired size along the V-axis, i.e. the number of image channels \p dim. + \param val Default value for image pixels. + \remark + - This function has the same properties as assign(const unsigned int,const unsigned int,const unsigned int,const unsigned int). + **/ + CImg& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, const T val) { + return assign(dx,dy,dz,dv).fill(val); + } + + //! In-place version of the previous constructor. + CImg& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, + const int val0, const int val1, ...) { + assign(dx,dy,dz,dv); + _CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,int); + return *this; + } + + //! In-place version of the previous constructor. + CImg& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, + const double val0, const double val1, ...) { + assign(dx,dy,dz,dv); + _CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,double); + return *this; + } + + //! In-place version of the previous constructor. + template + CImg& assign(const t *const data_buffer, const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dv=1) { + const unsigned long siz = dx*dy*dz*dv; + if (!data_buffer || !siz) return assign(); + assign(dx,dy,dz,dv); + const t *ptrs = data_buffer + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs); + return *this; + } + +#ifndef cimg_use_visualcpp6 + CImg& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dv=1) +#else + CImg& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dv) +#endif + { + const unsigned long siz = dx*dy*dz*dv; + if (!data_buffer || !siz) return assign(); + const unsigned long curr_siz = size(); + if (data_buffer==data && siz==curr_siz) return assign(dx,dy,dz,dv); + if (is_shared || data_buffer+siz=data+size()) { + assign(dx,dy,dz,dv); + if (is_shared) cimg_std::memmove(data,data_buffer,siz*sizeof(T)); + else cimg_std::memcpy(data,data_buffer,siz*sizeof(T)); + } else { + T *new_data = new T[siz]; + cimg_std::memcpy(new_data,data_buffer,siz*sizeof(T)); + delete[] data; data = new_data; width = dx; height = dy; depth = dz; dim = dv; + } + return *this; + } + + //! In-place version of the previous constructor, allowing to force the shared state of the instance image. + template + CImg& assign(const t *const data_buffer, const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dv, const bool shared) { + if (shared) + throw CImgArgumentException("CImg<%s>::assign() : Cannot assign buffer (%s*) to shared instance image (%u,%u,%u,%u,%p)" + "(different pixel types).", + pixel_type(),CImg::pixel_type(),width,height,depth,dim,data); + return assign(data_buffer,dx,dy,dz,dv); + } + + CImg& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dv, const bool shared) { + const unsigned long siz = dx*dy*dz*dv; + if (!data_buffer || !siz) return assign(); + if (!shared) { if (is_shared) assign(); assign(data_buffer,dx,dy,dz,dv); } + else { + if (!is_shared) { + if (data_buffer+siz=data+size()) assign(); + else cimg::warn("CImg<%s>::assign() : Shared instance image has overlapping memory !", + pixel_type()); + } + width = dx; height = dy; depth = dz; dim = dv; is_shared = true; + data = const_cast(data_buffer); + } + return *this; + } + + //! In-place version of the default copy constructor. + /** + This function assigns a copy of the input image \p img to the current instance image. + \param img The input image to copy. + \remark + - If the instance image is not shared, the content of the input image \p img is copied into a new buffer + becoming the new pixel buffer of the instance image, while the old pixel buffer is freed if necessary. + - If the instance image is shared, the content of the input image \p img is copied into the current (shared) pixel buffer + of the instance image, modifying then the image referenced by the shared instance image. The instance image still remains shared. + **/ + template + CImg& assign(const CImg& img) { + return assign(img.data,img.width,img.height,img.depth,img.dim); + } + + //! In-place version of the advanced constructor. + /** + This function - as the simpler function assign(const CImg< t >&) - assigns a copy of the input image \p img to the + current instance image. But it also decides if the copy is shared (if the input parameter \p shared is set to \c true) + or non-shared (if the input parameter \p shared is set to \c false). + \param img The input image to copy. + \param shared Boolean flag that decides if the copy is shared or non-shared. + \remark + - It is not possible to assign a shared copy if the input image \p img is empty or has a different pixel type \p t != \p T. + - If a non-shared copy of the input image \p img is assigned, a new memory buffer is allocated for pixel data. + - If a shared copy of the input image \p img is assigned, no extra memory is allocated and the pixel buffer of the instance + image is the same as the one used by the input image \p img. + **/ + template + CImg& assign(const CImg& img, const bool shared) { + return assign(img.data,img.width,img.height,img.depth,img.dim,shared); + } + + //! In-place version of the previous constructor. + template + CImg& assign(const CImg& img, const char *const dimensions) { + if (dimensions) { + unsigned int siz[4] = { 0,1,1,1 }; + const char *s = dimensions; + char tmp[256] = { 0 }, c = 0; + int val = 0; + for (unsigned int k=0; k<4; ++k) { + const int err = cimg_std::sscanf(s,"%[-0-9]%c",tmp,&c); + if (err>=1) { + const int err = cimg_std::sscanf(s,"%d",&val); + if (err==1) { + int val2 = val<0?-val:(c=='%'?val:-1); + if (val2>=0) { + val = (int)((k==0?img.width:(k==1?img.height:(k==2?img.depth:img.dim)))*val2/100); + if (c!='%' && !val) val = 1; + } + siz[k] = val; + } + s+=cimg::strlen(tmp); + if (c=='%') ++s; + } + if (!err) { + if (!cimg::strncasecmp(s,"x",1)) { ++s; siz[k] = img.width; } + else if (!cimg::strncasecmp(s,"y",1)) { ++s; siz[k] = img.height; } + else if (!cimg::strncasecmp(s,"z",1)) { ++s; siz[k] = img.depth; } + else if (!cimg::strncasecmp(s,"v",1)) { ++s; siz[k] = img.dim; } + else if (!cimg::strncasecmp(s,"dx",2)) { s+=2; siz[k] = img.width; } + else if (!cimg::strncasecmp(s,"dy",2)) { s+=2; siz[k] = img.height; } + else if (!cimg::strncasecmp(s,"dz",2)) { s+=2; siz[k] = img.depth; } + else if (!cimg::strncasecmp(s,"dv",2)) { s+=2; siz[k] = img.dim; } + else if (!cimg::strncasecmp(s,"dimx",4)) { s+=4; siz[k] = img.width; } + else if (!cimg::strncasecmp(s,"dimy",4)) { s+=4; siz[k] = img.height; } + else if (!cimg::strncasecmp(s,"dimz",4)) { s+=4; siz[k] = img.depth; } + else if (!cimg::strncasecmp(s,"dimv",4)) { s+=4; siz[k] = img.dim; } + else if (!cimg::strncasecmp(s,"width",5)) { s+=5; siz[k] = img.width; } + else if (!cimg::strncasecmp(s,"height",6)) { s+=6; siz[k] = img.height; } + else if (!cimg::strncasecmp(s,"depth",5)) { s+=5; siz[k] = img.depth; } + else if (!cimg::strncasecmp(s,"dim",3)) { s+=3; siz[k] = img.dim; } + else { ++s; --k; } + } + } + return assign(siz[0],siz[1],siz[2],siz[3]); + } + return assign(); + } + + //! In-place version of the previous constructor. + template + CImg& assign(const CImg& img, const char *const dimensions, const T val) { + return assign(img,dimensions).fill(val); + } + + //! In-place version of the previous constructor. + /** + This function replaces the instance image by the one that have been read from the given file. + \param filename Filename of the image file. + - The image format is deduced from the filename only by looking for the filename extension i.e. without + analyzing the file itself. + - Recognized image formats depend on the tools installed on your system or the external libraries you use to link your code with. + More informations on this topic can be found in cimg_files_io. + - If the filename is not found, a CImgIOException is thrown by this constructor. + **/ + CImg& assign(const char *const filename) { + return load(filename); + } + + //! In-place version of the previous constructor. + CImg& assign(const CImgDisplay &disp) { + disp.snapshot(*this); + return *this; + } + + //! Transfer the content of the instance image into another one in a way that memory copies are avoided if possible. + /** + The instance image is always empty after a call to this function. + **/ + template + CImg& transfer_to(CImg& img) { + img.assign(*this); + assign(); + return img; + } + + CImg& transfer_to(CImg& img) { + if (is_shared || img.is_shared) { img.assign(*this); assign(); } else { img.assign(); swap(img); } + return img; + } + + //! Swap all fields of two images. Use with care ! + CImg& swap(CImg& img) { + cimg::swap(width,img.width); + cimg::swap(height,img.height); + cimg::swap(depth,img.depth); + cimg::swap(dim,img.dim); + cimg::swap(data,img.data); + cimg::swap(is_shared,img.is_shared); + return img; + } + + //@} + //------------------------------------- + // + //! \name Image Informations + //@{ + //------------------------------------- + + //! Return the type of the pixel values. + /** + \return a string describing the type of the image pixels (template parameter \p T). + - The string returned may contains spaces ("unsigned char"). + - If the template parameter T does not correspond to a registered type, the string "unknown" is returned. + **/ + static const char* pixel_type() { + return cimg::type::string(); + } + + //! Return the total number of pixel values in an image. + /** + - Equivalent to : dimx() * dimy() * dimz() * dimv(). + + \par example: + \code + CImg<> img(100,100,1,3); + if (img.size()==100*100*3) std::fprintf(stderr,"This statement is true"); + \endcode + **/ + unsigned long size() const { + return width*height*depth*dim; + } + + //! Return the number of columns of the instance image (size along the X-axis, i.e image width). + int dimx() const { + return (int)width; + } + + //! Return the number of rows of the instance image (size along the Y-axis, i.e image height). + int dimy() const { + return (int)height; + } + + //! Return the number of slices of the instance image (size along the Z-axis). + int dimz() const { + return (int)depth; + } + + //! Return the number of vector channels of the instance image (size along the V-axis). + int dimv() const { + return (int)dim; + } + + //! Return \c true if image (*this) has the specified width. + bool is_sameX(const unsigned int dx) const { + return (width==dx); + } + + //! Return \c true if images \c (*this) and \c img have same width. + template + bool is_sameX(const CImg& img) const { + return is_sameX(img.width); + } + + //! Return \c true if images \c (*this) and the display \c disp have same width. + bool is_sameX(const CImgDisplay& disp) const { + return is_sameX(disp.width); + } + + //! Return \c true if image (*this) has the specified height. + bool is_sameY(const unsigned int dy) const { + return (height==dy); + } + + //! Return \c true if images \c (*this) and \c img have same height. + template + bool is_sameY(const CImg& img) const { + return is_sameY(img.height); + } + + //! Return \c true if images \c (*this) and the display \c disp have same height. + bool is_sameY(const CImgDisplay& disp) const { + return is_sameY(disp.height); + } + + //! Return \c true if image (*this) has the specified depth. + bool is_sameZ(const unsigned int dz) const { + return (depth==dz); + } + + //! Return \c true if images \c (*this) and \c img have same depth. + template + bool is_sameZ(const CImg& img) const { + return is_sameZ(img.depth); + } + + //! Return \c true if image (*this) has the specified number of channels. + bool is_sameV(const unsigned int dv) const { + return (dim==dv); + } + + //! Return \c true if images \c (*this) and \c img have same dim. + template + bool is_sameV(const CImg& img) const { + return is_sameV(img.dim); + } + + //! Return \c true if image (*this) has the specified width and height. + bool is_sameXY(const unsigned int dx, const unsigned int dy) const { + return (is_sameX(dx) && is_sameY(dy)); + } + + //! Return \c true if images have same width and same height. + template + bool is_sameXY(const CImg& img) const { + return (is_sameX(img) && is_sameY(img)); + } + + //! Return \c true if image \c (*this) and the display \c disp have same width and same height. + bool is_sameXY(const CImgDisplay& disp) const { + return (is_sameX(disp) && is_sameY(disp)); + } + + //! Return \c true if image (*this) has the specified width and depth. + bool is_sameXZ(const unsigned int dx, const unsigned int dz) const { + return (is_sameX(dx) && is_sameZ(dz)); + } + + //! Return \c true if images have same width and same depth. + template + bool is_sameXZ(const CImg& img) const { + return (is_sameX(img) && is_sameZ(img)); + } + + //! Return \c true if image (*this) has the specified width and number of channels. + bool is_sameXV(const unsigned int dx, const unsigned int dv) const { + return (is_sameX(dx) && is_sameV(dv)); + } + + //! Return \c true if images have same width and same number of channels. + template + bool is_sameXV(const CImg& img) const { + return (is_sameX(img) && is_sameV(img)); + } + + //! Return \c true if image (*this) has the specified height and depth. + bool is_sameYZ(const unsigned int dy, const unsigned int dz) const { + return (is_sameY(dy) && is_sameZ(dz)); + } + + //! Return \c true if images have same height and same depth. + template + bool is_sameYZ(const CImg& img) const { + return (is_sameY(img) && is_sameZ(img)); + } + + //! Return \c true if image (*this) has the specified height and number of channels. + bool is_sameYV(const unsigned int dy, const unsigned int dv) const { + return (is_sameY(dy) && is_sameV(dv)); + } + + //! Return \c true if images have same height and same number of channels. + template + bool is_sameYV(const CImg& img) const { + return (is_sameY(img) && is_sameV(img)); + } + + //! Return \c true if image (*this) has the specified depth and number of channels. + bool is_sameZV(const unsigned int dz, const unsigned int dv) const { + return (is_sameZ(dz) && is_sameV(dv)); + } + + //! Return \c true if images have same depth and same number of channels. + template + bool is_sameZV(const CImg& img) const { + return (is_sameZ(img) && is_sameV(img)); + } + + //! Return \c true if image (*this) has the specified width, height and depth. + bool is_sameXYZ(const unsigned int dx, const unsigned int dy, const unsigned int dz) const { + return (is_sameXY(dx,dy) && is_sameZ(dz)); + } + + //! Return \c true if images have same width, same height and same depth. + template + bool is_sameXYZ(const CImg& img) const { + return (is_sameXY(img) && is_sameZ(img)); + } + + //! Return \c true if image (*this) has the specified width, height and depth. + bool is_sameXYV(const unsigned int dx, const unsigned int dy, const unsigned int dv) const { + return (is_sameXY(dx,dy) && is_sameV(dv)); + } + + //! Return \c true if images have same width, same height and same number of channels. + template + bool is_sameXYV(const CImg& img) const { + return (is_sameXY(img) && is_sameV(img)); + } + + //! Return \c true if image (*this) has the specified width, height and number of channels. + bool is_sameXZV(const unsigned int dx, const unsigned int dz, const unsigned int dv) const { + return (is_sameXZ(dx,dz) && is_sameV(dv)); + } + + //! Return \c true if images have same width, same depth and same number of channels. + template + bool is_sameXZV(const CImg& img) const { + return (is_sameXZ(img) && is_sameV(img)); + } + + //! Return \c true if image (*this) has the specified height, depth and number of channels. + bool is_sameYZV(const unsigned int dy, const unsigned int dz, const unsigned int dv) const { + return (is_sameYZ(dy,dz) && is_sameV(dv)); + } + + //! Return \c true if images have same height, same depth and same number of channels. + template + bool is_sameYZV(const CImg& img) const { + return (is_sameYZ(img) && is_sameV(img)); + } + + //! Return \c true if image (*this) has the specified width, height, depth and number of channels. + bool is_sameXYZV(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv) const { + return (is_sameXYZ(dx,dy,dz) && is_sameV(dv)); + } + + //! Return \c true if images \c (*this) and \c img have same width, same height, same depth and same number of channels. + template + bool is_sameXYZV(const CImg& img) const { + return (is_sameXYZ(img) && is_sameV(img)); + } + + //! Return \c true if current image is empty. + bool is_empty() const { + return !(data && width && height && depth && dim); + } + + //! Return \p true if image is not empty. + operator bool() const { + return !is_empty(); + } + + //! Return an iterator to the first image pixel + iterator begin() { + return data; + } + + const_iterator begin() const { + return data; + } + + //! Return reference to the first image pixel + const T& first() const { + return *data; + } + + T& first() { + return *data; + } + + //! Return an iterator pointing after the last image pixel + iterator end() { + return data + size(); + } + + const_iterator end() const { + return data + size(); + } + + //! Return a reference to the last image pixel + const T& last() const { + return data[size() - 1]; + } + + T& last() { + return data[size() - 1]; + } + + //! Return a pointer to the pixel buffer. + T* ptr() { + return data; + } + + const T* ptr() const { + return data; + } + + //! Return a pointer to the pixel value located at (\p x,\p y,\p z,\p v). + /** + \param x X-coordinate of the pixel. + \param y Y-coordinate of the pixel. + \param z Z-coordinate of the pixel. + \param v V-coordinate of the pixel. + + - When called without parameters, ptr() returns a pointer to the begining of the pixel buffer. + - If the macro \c 'cimg_debug'>=3, boundary checking is performed and warning messages may appear if + given coordinates are outside the image range (but function performances decrease). + + \par example: + \code + CImg img(100,100,1,1,0); // Define a 100x100 greyscale image with float-valued pixels. + float *ptr = ptr(10,10); // Get a pointer to the pixel located at (10,10). + float val = *ptr; // Get the pixel value. + \endcode + **/ +#if cimg_debug>=3 + T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) { + const long off = offset(x,y,z,v); + if (off<0 || off>=(long)size()) { + cimg::warn("CImg<%s>::ptr() : Asked for a pointer at coordinates (%u,%u,%u,%u) (offset=%ld), " + "outside image range (%u,%u,%u,%u) (size=%lu)", + pixel_type(),x,y,z,v,off,width,height,depth,dim,size()); + return data; + } + return data + off; + } + + const T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const { + return const_cast*>(this)->ptr(x,y,z,v); + } +#else + T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) { + return data + (long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth; + } + + const T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const { + return data + (long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth; + } +#endif + + //! Return \c true if the memory buffers of the two images overlaps. + /** + May happen when using shared images. + **/ + template + bool is_overlapped(const CImg& img) const { + const unsigned long csiz = size(), isiz = img.size(); + return !((void*)(data+csiz)<=(void*)img.data || (void*)data>=(void*)(img.data+isiz)); + } + + //! Return the offset of the pixel coordinates (\p x,\p y,\p z,\p v) with respect to the data pointer \c data. + /** + \param x X-coordinate of the pixel. + \param y Y-coordinate of the pixel. + \param z Z-coordinate of the pixel. + \param v V-coordinate of the pixel. + + - No checking is done on the validity of the given coordinates. + + \par Example: + \code + CImg img(100,100,1,3,0); // Define a 100x100 color image with float-valued black pixels. + long off = img.offset(10,10,0,2); // Get the offset of the blue value of the pixel located at (10,10). + float val = img[off]; // Get the blue value of the pixel. + \endcode + **/ + long offset(const int x, const int y=0, const int z=0, const int v=0) const { + return (long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth; + } + + //! Fast access to pixel value for reading or writing. + /** + \param x X-coordinate of the pixel. + \param y Y-coordinate of the pixel. + \param z Z-coordinate of the pixel. + \param v V-coordinate of the pixel. + + - If one image dimension is equal to 1, it can be omitted in the coordinate list (see example below). + - If the macro \c 'cimg_debug'>=3, boundary checking is performed and warning messages may appear + (but function performances decrease). + + \par example: + \code + CImg img(100,100,1,3,0); // Define a 100x100 color image with float-valued black pixels. + const float valR = img(10,10,0,0); // Read the red component at coordinates (10,10). + const float valG = img(10,10,0,1); // Read the green component at coordinates (10,10) + const float valB = img(10,10,2); // Read the blue component at coordinates (10,10) (Z-coordinate omitted here). + const float avg = (valR + valG + valB)/3; // Compute average pixel value. + img(10,10,0) = img(10,10,1) = img(10,10,2) = avg; // Replace the pixel (10,10) by the average grey value. + \endcode + **/ +#if cimg_debug>=3 + T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) { + const long off = offset(x,y,z,v); + if (!data || off>=(long)size()) { + cimg::warn("CImg<%s>::operator() : Pixel access requested at (%u,%u,%u,%u) (offset=%ld) " + "outside the image range (%u,%u,%u,%u) (size=%lu)", + pixel_type(),x,y,z,v,off,width,height,depth,dim,size()); + return *data; + } + else return data[off]; + } + + const T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const { + return const_cast*>(this)->operator()(x,y,z,v); + } +#else + T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) { + return data[(long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth]; + } + + const T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const { + return data[(long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth]; + } +#endif + + //! Fast access to pixel value for reading or writing, using an offset to the image pixel. + /** + \param off Offset of the pixel according to the begining of the pixel buffer, given by ptr(). + + - If the macro \c 'cimg_debug'>=3, boundary checking is performed and warning messages may appear + (but function performances decrease). + - As pixel values are aligned in memory, this operator can sometime useful to access values easier than + with operator()() (see example below). + + \par example: + \code + CImg vec(1,10); // Define a vector of float values (10 lines, 1 row). + const float val1 = vec(0,4); // Get the fifth element using operator()(). + const float val2 = vec[4]; // Get the fifth element using operator[]. Here, val2==val1. + \endcode + **/ +#if cimg_debug>=3 + T& operator[](const unsigned long off) { + if (!data || off>=size()) { + cimg::warn("CImg<%s>::operator[] : Pixel access requested at offset=%lu " + "outside the image range (%u,%u,%u,%u) (size=%lu)", + pixel_type(),off,width,height,depth,dim,size()); + return *data; + } + else return data[off]; + } + + const T& operator[](const unsigned long off) const { + return const_cast*>(this)->operator[](off); + } +#else + T& operator[](const unsigned long off) { + return data[off]; + } + + const T& operator[](const unsigned long off) const { + return data[off]; + } +#endif + + //! Return a reference to the last image value + T& back() { + return operator()(size()-1); + } + + const T& back() const { + return operator()(size()-1); + } + + //! Return a reference to the first image value + T& front() { + return *data; + } + + const T& front() const { + return *data; + } + + //! Return \c true if pixel (x,y,z,v) is inside image boundaries. + bool containsXYZV(const int x, const int y=0, const int z=0, const int v=0) const { + return !is_empty() && x>=0 && x=0 && y=0 && z=0 && v + bool contains(const T& pixel, t& x, t& y, t& z, t& v) const { + const unsigned long wh = width*height, whz = wh*depth, siz = whz*dim; + const T *const ppixel = &pixel; + if (is_empty() || ppixel=data+siz) return false; + unsigned long off = (unsigned long)(ppixel - data); + const unsigned long nv = off/whz; + off%=whz; + const unsigned long nz = off/wh; + off%=wh; + const unsigned long ny = off/width, nx = off%width; + x = (t)nx; y = (t)ny; z = (t)nz; v = (t)nv; + return true; + } + + //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y,z). + template + bool contains(const T& pixel, t& x, t& y, t& z) const { + const unsigned long wh = width*height, whz = wh*depth, siz = whz*dim; + const T *const ppixel = &pixel; + if (is_empty() || ppixel=data+siz) return false; + unsigned long off = ((unsigned long)(ppixel - data))%whz; + const unsigned long nz = off/wh; + off%=wh; + const unsigned long ny = off/width, nx = off%width; + x = (t)nx; y = (t)ny; z = (t)nz; + return true; + } + + //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y). + template + bool contains(const T& pixel, t& x, t& y) const { + const unsigned long wh = width*height, siz = wh*depth*dim; + const T *const ppixel = &pixel; + if (is_empty() || ppixel=data+siz) return false; + unsigned long off = ((unsigned long)(ppixel - data))%wh; + const unsigned long ny = off/width, nx = off%width; + x = (t)nx; y = (t)ny; + return true; + } + + //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x). + template + bool contains(const T& pixel, t& x) const { + const T *const ppixel = &pixel; + if (is_empty() || ppixel=data+size()) return false; + x = (t)(((unsigned long)(ppixel - data))%width); + return true; + } + + //! Return \c true if specified referenced value is inside the image boundaries. + bool contains(const T& pixel) const { + const T *const ppixel = &pixel; + return !is_empty() && ppixel>=data && ppixel=(int)size())?(cimg::temporary(out_val)=out_val):(*this)[off]; + } + + T at(const int off, const T out_val) const { + return (off<0 || off>=(int)size())?out_val:(*this)[off]; + } + + //! Read a pixel value with Neumann boundary conditions. + T& at(const int off) { + if (!size()) + throw CImgInstanceException("CImg<%s>::at() : Instance image is empty.", + pixel_type()); + return _at(off); + } + + T at(const int off) const { + if (!size()) + throw CImgInstanceException("CImg<%s>::at() : Instance image is empty.", + pixel_type()); + return _at(off); + } + + T& _at(const int off) { + const unsigned int siz = (unsigned int)size(); + return (*this)[off<0?0:(unsigned int)off>=siz?siz-1:off]; + } + + T _at(const int off) const { + const unsigned int siz = (unsigned int)size(); + return (*this)[off<0?0:(unsigned int)off>=siz?siz-1:off]; + } + + //! Read a pixel value with Dirichlet boundary conditions. + T& atXYZV(const int x, const int y, const int z, const int v, const T out_val) { + return (x<0 || y<0 || z<0 || v<0 || x>=dimx() || y>=dimy() || z>=dimz() || v>=dimv())? + (cimg::temporary(out_val)=out_val):(*this)(x,y,z,v); + } + + T atXYZV(const int x, const int y, const int z, const int v, const T out_val) const { + return (x<0 || y<0 || z<0 || v<0 || x>=dimx() || y>=dimy() || z>=dimz() || v>=dimv())?out_val:(*this)(x,y,z,v); + } + + //! Read a pixel value with Neumann boundary conditions. + T& atXYZV(const int x, const int y, const int z, const int v) { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::atXYZV() : Instance image is empty.", + pixel_type()); + return _atXYZV(x,y,z,v); + } + + T atXYZV(const int x, const int y, const int z, const int v) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::atXYZV() : Instance image is empty.", + pixel_type()); + return _atXYZV(x,y,z,v); + } + + T& _atXYZV(const int x, const int y, const int z, const int v) { + return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y), + z<0?0:(z>=dimz()?dimz()-1:z), v<0?0:(v>=dimv()?dimv()-1:v)); + } + + T _atXYZV(const int x, const int y, const int z, const int v) const { + return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y), + z<0?0:(z>=dimz()?dimz()-1:z), v<0?0:(v>=dimv()?dimv()-1:v)); + } + + //! Read a pixel value with Dirichlet boundary conditions for the three first coordinates (\c x,\c y,\c z). + T& atXYZ(const int x, const int y, const int z, const int v, const T out_val) { + return (x<0 || y<0 || z<0 || x>=dimx() || y>=dimy() || z>=dimz())? + (cimg::temporary(out_val)=out_val):(*this)(x,y,z,v); + } + + T atXYZ(const int x, const int y, const int z, const int v, const T out_val) const { + return (x<0 || y<0 || z<0 || x>=dimx() || y>=dimy() || z>=dimz())?out_val:(*this)(x,y,z,v); + } + + //! Read a pixel value with Neumann boundary conditions for the three first coordinates (\c x,\c y,\c z). + T& atXYZ(const int x, const int y, const int z, const int v=0) { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::atXYZ() : Instance image is empty.", + pixel_type()); + return _atXYZ(x,y,z,v); + } + + T atXYZ(const int x, const int y, const int z, const int v=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::atXYZ() : Instance image is empty.", + pixel_type()); + return _atXYZ(x,y,z,v); + } + + T& _atXYZ(const int x, const int y, const int z, const int v=0) { + return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y<0?0:(y>=dimy()?dimy()-1:y), + z<0?0:(z>=dimz()?dimz()-1:z),v); + } + + T _atXYZ(const int x, const int y, const int z, const int v=0) const { + return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y<0?0:(y>=dimy()?dimy()-1:y), + z<0?0:(z>=dimz()?dimz()-1:z),v); + } + + //! Read a pixel value with Dirichlet boundary conditions for the two first coordinates (\c x,\c y). + T& atXY(const int x, const int y, const int z, const int v, const T out_val) { + return (x<0 || y<0 || x>=dimx() || y>=dimy())?(cimg::temporary(out_val)=out_val):(*this)(x,y,z,v); + } + + T atXY(const int x, const int y, const int z, const int v, const T out_val) const { + return (x<0 || y<0 || x>=dimx() || y>=dimy())?out_val:(*this)(x,y,z,v); + } + + //! Read a pixel value with Neumann boundary conditions for the two first coordinates (\c x,\c y). + T& atXY(const int x, const int y, const int z=0, const int v=0) { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::atXY() : Instance image is empty.", + pixel_type()); + return _atXY(x,y,z,v); + } + + T atXY(const int x, const int y, const int z=0, const int v=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::atXY() : Instance image is empty.", + pixel_type()); + return _atXY(x,y,z,v); + } + + T& _atXY(const int x, const int y, const int z=0, const int v=0) { + return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),z,v); + } + + T _atXY(const int x, const int y, const int z=0, const int v=0) const { + return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),z,v); + } + + //! Read a pixel value with Dirichlet boundary conditions for the first coordinates (\c x). + T& atX(const int x, const int y, const int z, const int v, const T out_val) { + return (x<0 || x>=dimx())?(cimg::temporary(out_val)=out_val):(*this)(x,y,z,v); + } + + T atX(const int x, const int y, const int z, const int v, const T out_val) const { + return (x<0 || x>=dimx())?out_val:(*this)(x,y,z,v); + } + + //! Read a pixel value with Neumann boundary conditions for the first coordinates (\c x). + T& atX(const int x, const int y=0, const int z=0, const int v=0) { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::atX() : Instance image is empty.", + pixel_type()); + return _atX(x,y,z,v); + } + + T atX(const int x, const int y=0, const int z=0, const int v=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::atX() : Instance image is empty.", + pixel_type()); + return _atX(x,y,z,v); + } + + T& _atX(const int x, const int y=0, const int z=0, const int v=0) { + return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y,z,v); + } + + T _atX(const int x, const int y=0, const int z=0, const int v=0) const { + return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y,z,v); + } + + //! Read a pixel value using linear interpolation and Dirichlet boundary conditions. + Tfloat linear_atXYZV(const float fx, const float fy, const float fz, const float fv, const T out_val) const { + const int + x = (int)fx-(fx>=0?0:1), nx = x+1, + y = (int)fy-(fy>=0?0:1), ny = y+1, + z = (int)fz-(fz>=0?0:1), nz = z+1, + v = (int)fv-(fv>=0?0:1), nv = v+1; + const float + dx = fx-x, + dy = fy-y, + dz = fz-z, + dv = fv-v; + const Tfloat + Icccc = (Tfloat)atXYZV(x,y,z,v,out_val), Inccc = (Tfloat)atXYZV(nx,y,z,v,out_val), + Icncc = (Tfloat)atXYZV(x,ny,z,v,out_val), Inncc = (Tfloat)atXYZV(nx,ny,z,v,out_val), + Iccnc = (Tfloat)atXYZV(x,y,nz,v,out_val), Incnc = (Tfloat)atXYZV(nx,y,nz,v,out_val), + Icnnc = (Tfloat)atXYZV(x,ny,nz,v,out_val), Innnc = (Tfloat)atXYZV(nx,ny,nz,v,out_val), + Icccn = (Tfloat)atXYZV(x,y,z,nv,out_val), Inccn = (Tfloat)atXYZV(nx,y,z,nv,out_val), + Icncn = (Tfloat)atXYZV(x,ny,z,nv,out_val), Inncn = (Tfloat)atXYZV(nx,ny,z,nv,out_val), + Iccnn = (Tfloat)atXYZV(x,y,nz,nv,out_val), Incnn = (Tfloat)atXYZV(nx,y,nz,nv,out_val), + Icnnn = (Tfloat)atXYZV(x,ny,nz,nv,out_val), Innnn = (Tfloat)atXYZV(nx,ny,nz,nv,out_val); + return Icccc + + dx*(Inccc-Icccc + + dy*(Icccc+Inncc-Icncc-Inccc + + dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc + + dv*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) + + dv*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) + + dz*(Icccc+Incnc-Iccnc-Inccc + + dv*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) + + dv*(Icccc+Inccn-Inccc-Icccn)) + + dy*(Icncc-Icccc + + dz*(Icccc+Icnnc-Iccnc-Icncc + + dv*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) + + dv*(Icccc+Icncn-Icncc-Icccn)) + + dz*(Iccnc-Icccc + + dv*(Icccc+Iccnn-Iccnc-Icccn)) + + dv*(Icccn-Icccc); + } + + //! Read a pixel value using linear interpolation and Neumann boundary conditions. + Tfloat linear_atXYZV(const float fx, const float fy=0, const float fz=0, const float fv=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::linear_atXYZV() : Instance image is empty.", + pixel_type()); + return _linear_atXYZV(fx,fy,fz,fv); + } + + Tfloat _linear_atXYZV(const float fx, const float fy=0, const float fz=0, const float fv=0) const { + const float + nfx = fx<0?0:(fx>width-1?width-1:fx), + nfy = fy<0?0:(fy>height-1?height-1:fy), + nfz = fz<0?0:(fz>depth-1?depth-1:fz), + nfv = fv<0?0:(fv>dim-1?dim-1:fv); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy, + z = (unsigned int)nfz, + v = (unsigned int)nfv; + const float + dx = nfx-x, + dy = nfy-y, + dz = nfz-z, + dv = nfv-v; + const unsigned int + nx = dx>0?x+1:x, + ny = dy>0?y+1:y, + nz = dz>0?z+1:z, + nv = dv>0?v+1:v; + const Tfloat + Icccc = (Tfloat)(*this)(x,y,z,v), Inccc = (Tfloat)(*this)(nx,y,z,v), + Icncc = (Tfloat)(*this)(x,ny,z,v), Inncc = (Tfloat)(*this)(nx,ny,z,v), + Iccnc = (Tfloat)(*this)(x,y,nz,v), Incnc = (Tfloat)(*this)(nx,y,nz,v), + Icnnc = (Tfloat)(*this)(x,ny,nz,v), Innnc = (Tfloat)(*this)(nx,ny,nz,v), + Icccn = (Tfloat)(*this)(x,y,z,nv), Inccn = (Tfloat)(*this)(nx,y,z,nv), + Icncn = (Tfloat)(*this)(x,ny,z,nv), Inncn = (Tfloat)(*this)(nx,ny,z,nv), + Iccnn = (Tfloat)(*this)(x,y,nz,nv), Incnn = (Tfloat)(*this)(nx,y,nz,nv), + Icnnn = (Tfloat)(*this)(x,ny,nz,nv), Innnn = (Tfloat)(*this)(nx,ny,nz,nv); + return Icccc + + dx*(Inccc-Icccc + + dy*(Icccc+Inncc-Icncc-Inccc + + dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc + + dv*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) + + dv*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) + + dz*(Icccc+Incnc-Iccnc-Inccc + + dv*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) + + dv*(Icccc+Inccn-Inccc-Icccn)) + + dy*(Icncc-Icccc + + dz*(Icccc+Icnnc-Iccnc-Icncc + + dv*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) + + dv*(Icccc+Icncn-Icncc-Icccn)) + + dz*(Iccnc-Icccc + + dv*(Icccc+Iccnn-Iccnc-Icccn)) + + dv*(Icccn-Icccc); + } + + //! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first three coordinates). + Tfloat linear_atXYZ(const float fx, const float fy, const float fz, const int v, const T out_val) const { + const int + x = (int)fx-(fx>=0?0:1), nx = x+1, + y = (int)fy-(fy>=0?0:1), ny = y+1, + z = (int)fz-(fz>=0?0:1), nz = z+1; + const float + dx = fx-x, + dy = fy-y, + dz = fz-z; + const Tfloat + Iccc = (Tfloat)atXYZ(x,y,z,v,out_val), Incc = (Tfloat)atXYZ(nx,y,z,v,out_val), + Icnc = (Tfloat)atXYZ(x,ny,z,v,out_val), Innc = (Tfloat)atXYZ(nx,ny,z,v,out_val), + Iccn = (Tfloat)atXYZ(x,y,nz,v,out_val), Incn = (Tfloat)atXYZ(nx,y,nz,v,out_val), + Icnn = (Tfloat)atXYZ(x,ny,nz,v,out_val), Innn = (Tfloat)atXYZ(nx,ny,nz,v,out_val); + return Iccc + + dx*(Incc-Iccc + + dy*(Iccc+Innc-Icnc-Incc + + dz*(Iccn+Innn+Icnc+Incc-Icnn-Incn-Iccc-Innc)) + + dz*(Iccc+Incn-Iccn-Incc)) + + dy*(Icnc-Iccc + + dz*(Iccc+Icnn-Iccn-Icnc)) + + dz*(Iccn-Iccc); + } + + //! Read a pixel value using linear interpolation and Neumann boundary conditions (first three coordinates). + Tfloat linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int v=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::linear_atXYZ() : Instance image is empty.", + pixel_type()); + return _linear_atXYZ(fx,fy,fz,v); + } + + Tfloat _linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int v=0) const { + const float + nfx = fx<0?0:(fx>width-1?width-1:fx), + nfy = fy<0?0:(fy>height-1?height-1:fy), + nfz = fz<0?0:(fz>depth-1?depth-1:fz); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy, + z = (unsigned int)nfz; + const float + dx = nfx-x, + dy = nfy-y, + dz = nfz-z; + const unsigned int + nx = dx>0?x+1:x, + ny = dy>0?y+1:y, + nz = dz>0?z+1:z; + const Tfloat + Iccc = (Tfloat)(*this)(x,y,z,v), Incc = (Tfloat)(*this)(nx,y,z,v), + Icnc = (Tfloat)(*this)(x,ny,z,v), Innc = (Tfloat)(*this)(nx,ny,z,v), + Iccn = (Tfloat)(*this)(x,y,nz,v), Incn = (Tfloat)(*this)(nx,y,nz,v), + Icnn = (Tfloat)(*this)(x,ny,nz,v), Innn = (Tfloat)(*this)(nx,ny,nz,v); + return Iccc + + dx*(Incc-Iccc + + dy*(Iccc+Innc-Icnc-Incc + + dz*(Iccn+Innn+Icnc+Incc-Icnn-Incn-Iccc-Innc)) + + dz*(Iccc+Incn-Iccn-Incc)) + + dy*(Icnc-Iccc + + dz*(Iccc+Icnn-Iccn-Icnc)) + + dz*(Iccn-Iccc); + } + + //! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first two coordinates). + Tfloat linear_atXY(const float fx, const float fy, const int z, const int v, const T out_val) const { + const int + x = (int)fx-(fx>=0?0:1), nx = x+1, + y = (int)fy-(fy>=0?0:1), ny = y+1; + const float + dx = fx-x, + dy = fy-y; + const Tfloat + Icc = (Tfloat)atXY(x,y,z,v,out_val), Inc = (Tfloat)atXY(nx,y,z,v,out_val), + Icn = (Tfloat)atXY(x,ny,z,v,out_val), Inn = (Tfloat)atXY(nx,ny,z,v,out_val); + return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc); + } + + //! Read a pixel value using linear interpolation and Neumann boundary conditions (first two coordinates). + Tfloat linear_atXY(const float fx, const float fy, const int z=0, const int v=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::linear_atXY() : Instance image is empty.", + pixel_type()); + return _linear_atXY(fx,fy,z,v); + } + + Tfloat _linear_atXY(const float fx, const float fy, const int z=0, const int v=0) const { + const float + nfx = fx<0?0:(fx>width-1?width-1:fx), + nfy = fy<0?0:(fy>height-1?height-1:fy); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy; + const float + dx = nfx-x, + dy = nfy-y; + const unsigned int + nx = dx>0?x+1:x, + ny = dy>0?y+1:y; + const Tfloat + Icc = (Tfloat)(*this)(x,y,z,v), Inc = (Tfloat)(*this)(nx,y,z,v), + Icn = (Tfloat)(*this)(x,ny,z,v), Inn = (Tfloat)(*this)(nx,ny,z,v); + return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc); + } + + //! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first coordinate). + Tfloat linear_atX(const float fx, const int y, const int z, const int v, const T out_val) const { + const int + x = (int)fx-(fx>=0?0:1), nx = x+1; + const float + dx = fx-x; + const Tfloat + Ic = (Tfloat)atX(x,y,z,v,out_val), In = (Tfloat)atXY(nx,y,z,v,out_val); + return Ic + dx*(In-Ic); + } + + //! Read a pixel value using linear interpolation and Neumann boundary conditions (first coordinate). + Tfloat linear_atX(const float fx, const int y=0, const int z=0, const int v=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::linear_atX() : Instance image is empty.", + pixel_type()); + return _linear_atX(fx,y,z,v); + } + + Tfloat _linear_atX(const float fx, const int y=0, const int z=0, const int v=0) const { + const float + nfx = fx<0?0:(fx>width-1?width-1:fx); + const unsigned int + x = (unsigned int)nfx; + const float + dx = nfx-x; + const unsigned int + nx = dx>0?x+1:x; + const Tfloat + Ic = (Tfloat)(*this)(x,y,z,v), In = (Tfloat)(*this)(nx,y,z,v); + return Ic + dx*(In-Ic); + } + + //! Read a pixel value using cubic interpolation and Dirichlet boundary conditions. + Tfloat cubic_atXY(const float fx, const float fy, const int z, const int v, const T out_val) const { + const int + x = (int)fx-(fx>=0?0:1), px = x-1, nx = x+1, ax = x+2, + y = (int)fy-(fy>=0?0:1), py = y-1, ny = y+1, ay = y+2; + const float + dx = fx-x, dx2 = dx*dx, dx3 = dx2*dx, + dy = fy-y; + const Tfloat + Ipp = (Tfloat)atXY(px,py,z,v,out_val), Icp = (Tfloat)atXY(x,py,z,v,out_val), + Inp = (Tfloat)atXY(nx,py,z,v,out_val), Iap = (Tfloat)atXY(ax,py,z,v,out_val), + Ipc = (Tfloat)atXY(px,y,z,v,out_val), Icc = (Tfloat)atXY(x,y,z,v,out_val), + Inc = (Tfloat)atXY(nx,y,z,v,out_val), Iac = (Tfloat)atXY(ax,y,z,v,out_val), + Ipn = (Tfloat)atXY(px,ny,z,v,out_val), Icn = (Tfloat)atXY(x,ny,z,v,out_val), + Inn = (Tfloat)atXY(nx,ny,z,v,out_val), Ian = (Tfloat)atXY(ax,ny,z,v,out_val), + Ipa = (Tfloat)atXY(px,ay,z,v,out_val), Ica = (Tfloat)atXY(x,ay,z,v,out_val), + Ina = (Tfloat)atXY(nx,ay,z,v,out_val), Iaa = (Tfloat)atXY(ax,ay,z,v,out_val), + valm = cimg::min(cimg::min(Ipp,Icp,Inp,Iap),cimg::min(Ipc,Icc,Inc,Iac),cimg::min(Ipn,Icn,Inn,Ian),cimg::min(Ipa,Ica,Ina,Iaa)), + valM = cimg::max(cimg::max(Ipp,Icp,Inp,Iap),cimg::max(Ipc,Icc,Inc,Iac),cimg::max(Ipn,Icn,Inn,Ian),cimg::max(Ipa,Ica,Ina,Iaa)), + u0p = Icp - Ipp, + u1p = Iap - Inp, + ap = 2*(Icp-Inp) + u0p + u1p, + bp = 3*(Inp-Icp) - 2*u0p - u1p, + u0c = Icc - Ipc, + u1c = Iac - Inc, + ac = 2*(Icc-Inc) + u0c + u1c, + bc = 3*(Inc-Icc) - 2*u0c - u1c, + u0n = Icn - Ipn, + u1n = Ian - Inn, + an = 2*(Icn-Inn) + u0n + u1n, + bn = 3*(Inn-Icn) - 2*u0n - u1n, + u0a = Ica - Ipa, + u1a = Iaa - Ina, + aa = 2*(Ica-Ina) + u0a + u1a, + ba = 3*(Ina-Ica) - 2*u0a - u1a, + valp = ap*dx3 + bp*dx2 + u0p*dx + Icp, + valc = ac*dx3 + bc*dx2 + u0c*dx + Icc, + valn = an*dx3 + bn*dx2 + u0n*dx + Icn, + vala = aa*dx3 + ba*dx2 + u0a*dx + Ica, + u0 = valc - valp, + u1 = vala - valn, + a = 2*(valc-valn) + u0 + u1, + b = 3*(valn-valc) - 2*u0 - u1, + val = a*dy*dy*dy + b*dy*dy + u0*dy + valc; + return valvalM?valM:val); + } + + //! Read a pixel value using cubic interpolation and Neumann boundary conditions. + Tfloat cubic_atXY(const float fx, const float fy, const int z=0, const int v=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::cubic_atXY() : Instance image is empty.", + pixel_type()); + return _cubic_atXY(fx,fy,z,v); + } + + Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int v=0) const { + const float + nfx = fx<0?0:(fx>width-1?width-1:fx), + nfy = fy<0?0:(fy>height-1?height-1:fy); + const int + x = (int)nfx, + y = (int)nfy; + const float + dx = nfx-x, dx2 = dx*dx, dx3 = dx2*dx, + dy = nfy-y; + const int + px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=dimx()?dimx()-1:x+2, + py = y-1<0?0:y-1, ny = dy>0?y+1:y, ay = y+2>=dimy()?dimy()-1:y+2; + const Tfloat + Ipp = (Tfloat)(*this)(px,py,z,v), Icp = (Tfloat)(*this)(x,py,z,v), + Inp = (Tfloat)(*this)(nx,py,z,v), Iap = (Tfloat)(*this)(ax,py,z,v), + Ipc = (Tfloat)(*this)(px,y,z,v), Icc = (Tfloat)(*this)(x,y,z,v), + Inc = (Tfloat)(*this)(nx,y,z,v), Iac = (Tfloat)(*this)(ax,y,z,v), + Ipn = (Tfloat)(*this)(px,ny,z,v), Icn = (Tfloat)(*this)(x,ny,z,v), + Inn = (Tfloat)(*this)(nx,ny,z,v), Ian = (Tfloat)(*this)(ax,ny,z,v), + Ipa = (Tfloat)(*this)(px,ay,z,v), Ica = (Tfloat)(*this)(x,ay,z,v), + Ina = (Tfloat)(*this)(nx,ay,z,v), Iaa = (Tfloat)(*this)(ax,ay,z,v), + valm = cimg::min(cimg::min(Ipp,Icp,Inp,Iap),cimg::min(Ipc,Icc,Inc,Iac),cimg::min(Ipn,Icn,Inn,Ian),cimg::min(Ipa,Ica,Ina,Iaa)), + valM = cimg::max(cimg::max(Ipp,Icp,Inp,Iap),cimg::max(Ipc,Icc,Inc,Iac),cimg::max(Ipn,Icn,Inn,Ian),cimg::max(Ipa,Ica,Ina,Iaa)), + u0p = Icp - Ipp, + u1p = Iap - Inp, + ap = 2*(Icp-Inp) + u0p + u1p, + bp = 3*(Inp-Icp) - 2*u0p - u1p, + u0c = Icc - Ipc, + u1c = Iac - Inc, + ac = 2*(Icc-Inc) + u0c + u1c, + bc = 3*(Inc-Icc) - 2*u0c - u1c, + u0n = Icn - Ipn, + u1n = Ian - Inn, + an = 2*(Icn-Inn) + u0n + u1n, + bn = 3*(Inn-Icn) - 2*u0n - u1n, + u0a = Ica - Ipa, + u1a = Iaa - Ina, + aa = 2*(Ica-Ina) + u0a + u1a, + ba = 3*(Ina-Ica) - 2*u0a - u1a, + valp = ap*dx3 + bp*dx2 + u0p*dx + Icp, + valc = ac*dx3 + bc*dx2 + u0c*dx + Icc, + valn = an*dx3 + bn*dx2 + u0n*dx + Icn, + vala = aa*dx3 + ba*dx2 + u0a*dx + Ica, + u0 = valc - valp, + u1 = vala - valn, + a = 2*(valc-valn) + u0 + u1, + b = 3*(valn-valc) - 2*u0 - u1, + val = a*dy*dy*dy + b*dy*dy + u0*dy + valc; + return valvalM?valM:val); + } + + //! Read a pixel value using cubic interpolation and Dirichlet boundary conditions (first coordinates). + Tfloat cubic_atX(const float fx, const int y, const int z, const int v, const T out_val) const { + const int + x = (int)fx-(fx>=0?0:1), px = x-1, nx = x+1, ax = x+2; + const float + dx = fx-x; + const Tfloat + Ip = (Tfloat)atX(px,y,z,v,out_val), Ic = (Tfloat)atX(x,y,z,v,out_val), + In = (Tfloat)atX(nx,y,z,v,out_val), Ia = (Tfloat)atX(ax,y,z,v,out_val), + valm = cimg::min(Ip,In,Ic,Ia), valM = cimg::max(Ip,In,Ic,Ia), + u0 = Ic - Ip, + u1 = Ia - In, + a = 2*(Ic-In) + u0 + u1, + b = 3*(In-Ic) - 2*u0 - u1, + val = a*dx*dx*dx + b*dx*dx + u0*dx + Ic; + return valvalM?valM:val); + } + + //! Read a pixel value using cubic interpolation and Neumann boundary conditions (first coordinates). + Tfloat cubic_atX(const float fx, const int y=0, const int z=0, const int v=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::cubic_atX() : Instance image is empty.", + pixel_type()); + return _cubic_atX(fx,y,z,v); + } + + Tfloat _cubic_atX(const float fx, const int y=0, const int z=0, const int v=0) const { + const float + nfx = fx<0?0:(fx>width-1?width-1:fx); + const int + x = (int)nfx; + const float + dx = nfx-x; + const int + px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=dimx()?dimx()-1:x+2; + const Tfloat + Ip = (Tfloat)(*this)(px,y,z,v), Ic = (Tfloat)(*this)(x,y,z,v), + In = (Tfloat)(*this)(nx,y,z,v), Ia = (Tfloat)(*this)(ax,y,z,v), + valm = cimg::min(Ip,In,Ic,Ia), valM = cimg::max(Ip,In,Ic,Ia), + u0 = Ic - Ip, + u1 = Ia - In, + a = 2*(Ic-In) + u0 + u1, + b = 3*(In-Ic) - 2*u0 - u1, + val = a*dx*dx*dx + b*dx*dx + u0*dx + Ic; + return valvalM?valM:val); + } + + //! Set a pixel value, with 3D float coordinates, using linear interpolation. + CImg& set_linear_atXYZ(const T& val, const float fx, const float fy=0, const float fz=0, const int v=0, + const bool add=false) { + const int + x = (int)fx-(fx>=0?0:1), nx = x+1, + y = (int)fy-(fy>=0?0:1), ny = y+1, + z = (int)fz-(fz>=0?0:1), nz = z+1; + const float + dx = fx-x, + dy = fy-y, + dz = fz-z; + if (v>=0 && v=0 && z=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx=0 && nz=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx=0?0:1), nx = x+1, + y = (int)fy-(fy>=0?0:1), ny = y+1; + const float + dx = fx-x, + dy = fy-y; + if (z>=0 && z=0 && v=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx::min() : Instance image is empty.", + pixel_type()); + const T *ptrmin = data; + T min_value = *ptrmin; + cimg_for(*this,ptr,T) if ((*ptr)::min() : Instance image is empty.", + pixel_type()); + T *ptrmin = data; + T min_value = *ptrmin; + cimg_for(*this,ptr,T) if ((*ptr)::max() : Instance image is empty.", + pixel_type()); + const T *ptrmax = data; + T max_value = *ptrmax; + cimg_for(*this,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr); + return *ptrmax; + } + + //! Return a reference to the maximum pixel value of the instance image + T& max() { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::max() : Instance image is empty.", + pixel_type()); + T *ptrmax = data; + T max_value = *ptrmax; + cimg_for(*this,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr); + return *ptrmax; + } + + //! Return a reference to the minimum pixel value and return also the maximum pixel value. + template + const T& minmax(t& max_val) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::minmax() : Instance image is empty.", + pixel_type()); + const T *ptrmin = data; + T min_value = *ptrmin, max_value = min_value; + cimg_for(*this,ptr,T) { + const T val = *ptr; + if (valmax_value) max_value = val; + } + max_val = (t)max_value; + return *ptrmin; + } + + //! Return a reference to the minimum pixel value and return also the maximum pixel value. + template + T& minmax(t& max_val) { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::minmax() : Instance image is empty.", + pixel_type()); + T *ptrmin = data; + T min_value = *ptrmin, max_value = min_value; + cimg_for(*this,ptr,T) { + const T val = *ptr; + if (valmax_value) max_value = val; + } + max_val = (t)max_value; + return *ptrmin; + } + + //! Return a reference to the maximum pixel value and return also the minimum pixel value. + template + const T& maxmin(t& min_val) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::maxmin() : Instance image is empty.", + pixel_type()); + const T *ptrmax = data; + T max_value = *ptrmax, min_value = max_value; + cimg_for(*this,ptr,T) { + const T val = *ptr; + if (val>max_value) { max_value = val; ptrmax = ptr; } + if (val + T& maxmin(t& min_val) { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::maxmin() : Instance image is empty.", + pixel_type()); + T *ptrmax = data; + T max_value = *ptrmax, min_value = max_value; + cimg_for(*this,ptr,T) { + const T val = *ptr; + if (val>max_value) { max_value = val; ptrmax = ptr; } + if (val::sum() : Instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),width,height,depth,dim,data); + Tfloat res = 0; + cimg_for(*this,ptr,T) res+=*ptr; + return res; + } + + //! Return the mean pixel value of the instance image. + Tfloat mean() const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::mean() : Instance image is empty.", + pixel_type()); + Tfloat val = 0; + cimg_for(*this,ptr,T) val+=*ptr; + return val/size(); + } + + //! Return the variance of the image. + /** + @param variance_method Determines how to calculate the variance + + + + + + + + + +
0Second moment: + @f$ v = 1/N \sum\limits_{k=1}^{N} (x_k - \bar x)^2 + = 1/N \left( \sum\limits_{k=1}^N x_k^2 - \left( \sum\limits_{k=1}^N x_k \right)^2 / N \right) @f$ + with @f$ \bar x = 1/N \sum\limits_{k=1}^N x_k \f$
1Best unbiased estimator: @f$ v = \frac{1}{N-1} \sum\limits_{k=1}^{N} (x_k - \bar x)^2 @f$
2Least median of squares
3Least trimmed of squares
+ */ + Tfloat variance(const unsigned int variance_method=1) const { + Tfloat foo; + return variancemean(variance_method,foo); + } + + //! Return the variance and the mean of the image. + template + Tfloat variancemean(const unsigned int variance_method, t& mean) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::variance() : Instance image is empty.", + pixel_type()); + Tfloat variance = 0, average = 0; + const unsigned int siz = size(); + switch (variance_method) { + case 3 : { // Least trimmed of Squares + CImg buf(*this); + const unsigned int siz2 = siz>>1; + { cimg_for(buf,ptrs,Tfloat) { const Tfloat val = *ptrs; (*ptrs)*=val; average+=val; }} + buf.sort(); + Tfloat a = 0; + const Tfloat *ptrs = buf.ptr(); + for (unsigned int j = 0; j buf(*this); + buf.sort(); + const unsigned int siz2 = siz>>1; + const Tfloat med_i = buf[siz2]; + cimg_for(buf,ptrs,Tfloat) { const Tfloat val = *ptrs; *ptrs = cimg::abs(val - med_i); average+=val; } + buf.sort(); + const Tfloat sig = (Tfloat)(1.4828*buf[siz2]); + variance = sig*sig; + } break; + case 1 : { // Least mean square (robust definition) + Tfloat S = 0, S2 = 0; + cimg_for(*this,ptr,T) { const Tfloat val = (Tfloat)*ptr; S+=val; S2+=val*val; } + variance = siz>1?(S2 - S*S/siz)/(siz - 1):0; + average = S; + } break; + case 0 :{ // Least mean square (standard definition) + Tfloat S = 0, S2 = 0; + cimg_for(*this,ptr,T) { const Tfloat val = (Tfloat)*ptr; S+=val; S2+=val*val; } + variance = (S2 - S*S/siz)/siz; + average = S; + } break; + default : + throw CImgArgumentException("CImg<%s>::variancemean() : Incorrect parameter 'variance_method = %d' (correct values are 0,1,2 or 3).", + pixel_type(),variance_method); + } + mean = (t)(average/siz); + return variance>0?variance:0; + } + + //! Return the kth smallest element of the image. + // (Adapted from the numerical recipies for CImg) + T kth_smallest(const unsigned int k) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::kth_smallest() : Instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),width,height,depth,dim,data); + CImg arr(*this); + unsigned long l = 0, ir = size()-1; + for (;;) { + if (ir<=l+1) { + if (ir==l+1 && arr[ir]>1; + cimg::swap(arr[mid],arr[l+1]); + if (arr[l]>arr[ir]) cimg::swap(arr[l],arr[ir]); + if (arr[l+1]>arr[ir]) cimg::swap(arr[l+1],arr[ir]); + if (arr[l]>arr[l+1]) cimg::swap(arr[l],arr[l+1]); + unsigned long i = l+1, j = ir; + const T pivot = arr[l+1]; + for (;;) { + do ++i; while (arr[i]pivot); + if (j=k) ir=j-1; + if (j<=k) l=i; + } + } + return 0; + } + + //! Compute a statistics vector (min,max,mean,variance,xmin,ymin,zmin,vmin,xmax,ymax,zmax,vmax). + CImg& stats(const unsigned int variance_method=1) { + return get_stats(variance_method).transfer_to(*this); + } + + CImg get_stats(const unsigned int variance_method=1) const { + if (is_empty()) return CImg(); + const unsigned long siz = size(); + const T *const odata = data; + const T *pm = odata, *pM = odata; + Tfloat S = 0, S2 = 0; + T m = *pm, M = m; + cimg_for(*this,ptr,T) { + const T val = *ptr; + const Tfloat fval = (Tfloat)val; + if (valM) { M = val; pM = ptr; } + S+=fval; + S2+=fval*fval; + } + const Tfloat + mean_value = S/siz, + _variance_value = variance_method==0?(S2 - S*S/siz)/siz: + (variance_method==1?(siz>1?(S2 - S*S/siz)/(siz - 1):0): + variance(variance_method)), + variance_value = _variance_value>0?_variance_value:0; + int + xm = 0, ym = 0, zm = 0, vm = 0, + xM = 0, yM = 0, zM = 0, vM = 0; + contains(*pm,xm,ym,zm,vm); + contains(*pM,xM,yM,zM,vM); + return CImg(1,12).fill((Tfloat)m,(Tfloat)M,mean_value,variance_value, + (Tfloat)xm,(Tfloat)ym,(Tfloat)zm,(Tfloat)vm, + (Tfloat)xM,(Tfloat)yM,(Tfloat)zM,(Tfloat)vM); + } + + //! Return the median value of the image. + T median() const { + const unsigned int s = size(); + const T res = kth_smallest(s>>1); + return (s%2)?res:((res+kth_smallest((s>>1)-1))/2); + } + + //! Compute the MSE (Mean-Squared Error) between two images. + template + Tfloat MSE(const CImg& img) const { + if (img.size()!=size()) + throw CImgArgumentException("CImg<%s>::MSE() : Instance image (%u,%u,%u,%u) and given image (%u,%u,%u,%u) have different dimensions.", + pixel_type(),width,height,depth,dim,img.width,img.height,img.depth,img.dim); + + Tfloat vMSE = 0; + const t* ptr2 = img.end(); + cimg_for(*this,ptr1,T) { + const Tfloat diff = (Tfloat)*ptr1 - (Tfloat)*(--ptr2); + vMSE += diff*diff; + } + vMSE/=img.size(); + return vMSE; + } + + //! Compute the PSNR between two images. + template + Tfloat PSNR(const CImg& img, const Tfloat valmax=(Tfloat)255) const { + const Tfloat vMSE = (Tfloat)cimg_std::sqrt(MSE(img)); + return (vMSE!=0)?(Tfloat)(20*cimg_std::log10(valmax/vMSE)):(Tfloat)(cimg::type::max()); + } + + //! Return the trace of the image, viewed as a matrix. + Tfloat trace() const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::trace() : Instance matrix (%u,%u,%u,%u,%p) is empty.", + pixel_type(),width,height,depth,dim,data); + Tfloat res = 0; + cimg_forX(*this,k) res+=(*this)(k,k); + return res; + } + + //! Return the dot product of the current vector/matrix with the vector/matrix \p img. + template + Tfloat dot(const CImg& img) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::dot() : Instance object (%u,%u,%u,%u,%p) is empty.", + pixel_type(),width,height,depth,dim,data); + if (!img) + throw CImgArgumentException("CImg<%s>::trace() : Specified argument (%u,%u,%u,%u,%p) is empty.", + pixel_type(),img.width,img.height,img.depth,img.dim,img.data); + const unsigned long nb = cimg::min(size(),img.size()); + Tfloat res = 0; + for (unsigned long off = 0; off::det() : Instance matrix (%u,%u,%u,%u,%p) is not square or is empty.", + pixel_type(),width,height,depth,dim,data); + switch (width) { + case 1 : return (Tfloat)((*this)(0,0)); + case 2 : return (Tfloat)((*this)(0,0))*(Tfloat)((*this)(1,1)) - (Tfloat)((*this)(0,1))*(Tfloat)((*this)(1,0)); + case 3 : { + const Tfloat + a = (Tfloat)data[0], d = (Tfloat)data[1], g = (Tfloat)data[2], + b = (Tfloat)data[3], e = (Tfloat)data[4], h = (Tfloat)data[5], + c = (Tfloat)data[6], f = (Tfloat)data[7], i = (Tfloat)data[8]; + return i*a*e - a*h*f - i*b*d + b*g*f + c*d*h - c*g*e; + } + default : { + CImg lu(*this); + CImg indx; + bool d; + lu._LU(indx,d); + Tfloat res = d?(Tfloat)1:(Tfloat)-1; + cimg_forX(lu,i) res*=lu(i,i); + return res; + } + } + return 0; + } + + //! Return the norm of the current vector/matrix. \p ntype = norm type (0=L2, 1=L1, -1=Linf). + Tfloat norm(const int norm_type=2) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::norm() : Instance object (%u,%u,%u,%u,%p) is empty.", + pixel_type(),width,height,depth,dim,data); + Tfloat res = 0; + switch (norm_type) { + case -1 : { + cimg_foroff(*this,off) { + const Tfloat tmp = cimg::abs((Tfloat)data[off]); + if (tmp>res) res = tmp; + } + return res; + } break; + case 1 : { + cimg_foroff(*this,off) res+=cimg::abs((Tfloat)data[off]); + return res; + } break; + case 2 : return (Tfloat)cimg_std::sqrt(dot(*this)); break; + default : + throw CImgArgumentException("CImg<%s>::norm() : Incorrect parameter 'norm_type=%d' (correct values are -1,1 or 2).", + pixel_type(),norm_type); + } + return 0; + } + + //! Return a C-string containing the values of the instance image. + CImg value_string(const char separator=',', const unsigned int max_size=0) const { + if (is_empty()) return CImg(1,1,1,1,0); + const unsigned int siz = (unsigned int)size(); + CImgList items; + char item[256] = { 0 }; + const T *ptrs = ptr(); + for (unsigned int off = 0; off::format(),cimg::type::format(*(ptrs++))); + const int l = cimg::strlen(item); + items.insert(CImg(item,l+1)); + items[items.size-1](l) = separator; + } + cimg_std::sprintf(item,cimg::type::format(),cimg::type::format(*ptrs)); + items.insert(CImg(item,cimg::strlen(item)+1)); + CImg res = items.get_append('x'); + if (max_size) { res.crop(0,max_size); res(max_size) = 0; } + return res; + } + + //! Display informations about the image on the standard error output. + /** + \param title Name for the considered image (optional). + \param display_stats Compute and display image statistics (optional). + **/ + const CImg& print(const char *title=0, const bool display_stats=true) const { + int xm = 0, ym = 0, zm = 0, vm = 0, xM = 0, yM = 0, zM = 0, vM = 0; + static CImg st; + if (!is_empty() && display_stats) { + st = get_stats(); + xm = (int)st[4]; ym = (int)st[5], zm = (int)st[6], vm = (int)st[7]; + xM = (int)st[8]; yM = (int)st[9], zM = (int)st[10], vM = (int)st[11]; + } + const unsigned long siz = size(), msiz = siz*sizeof(T), siz1 = siz-1; + const unsigned int mdisp = msiz<8*1024?0:(msiz<8*1024*1024?1:2), width1 = width-1; + char ntitle[64] = { 0 }; + if (!title) cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); + cimg_std::fprintf(cimg_stdout,"%s: this = %p, size = (%u,%u,%u,%u) [%lu %s], data = (%s*)%p (%s) = [ ", + title?title:ntitle,(void*)this,width,height,depth,dim, + mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)), + mdisp==0?"b":(mdisp==1?"Kb":"Mb"), + pixel_type(),(void*)data,is_shared?"shared":"not shared"); + if (!is_empty()) cimg_foroff(*this,off) { + cimg_std::fprintf(cimg_stdout,cimg::type::format(),cimg::type::format(data[off])); + if (off!=siz1) cimg_std::fprintf(cimg_stdout,"%s",off%width==width1?" ; ":" "); + if (off==7 && siz>16) { off = siz1-8; if (off!=7) cimg_std::fprintf(cimg_stdout,"... "); } + } + if (!is_empty() && display_stats) + cimg_std::fprintf(cimg_stdout," ], min = %g, max = %g, mean = %g, std = %g, coords(min) = (%u,%u,%u,%u), coords(max) = (%u,%u,%u,%u).\n", + st[0],st[1],st[2],cimg_std::sqrt(st[3]),xm,ym,zm,vm,xM,yM,zM,vM); + else cimg_std::fprintf(cimg_stdout,"%s].\n",is_empty()?"":" "); + return *this; + } + + //@} + //------------------------------------------ + // + //! \name Arithmetic and Boolean Operators + //@{ + //------------------------------------------ + + //! Assignment operator. + /** + This operator assigns a copy of the input image \p img to the current instance image. + \param img The input image to copy. + \remark + - This operator is strictly equivalent to the function assign(const CImg< t >&) and has exactly the same properties. + **/ + template + CImg& operator=(const CImg& img) { + return assign(img); + } + + CImg& operator=(const CImg& img) { + return assign(img); + } + + //! Assign values of a C-array to the instance image. + /** + \param buf Pointer to a C-style array having a size of (at least) this->size(). + + - Replace pixel values by the content of the array \c buf. + - Warning : the value types in the array and in the image must be the same. + + \par example: + \code + float tab[4*4] = { 1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16 }; // Define a 4x4 matrix in C-style. + CImg matrice(4,4); // Define a 4x4 greyscale image. + matrice = tab; // Fill the image by the values in tab. + \endcode + **/ + CImg& operator=(const T *buf) { + return assign(buf,width,height,depth,dim); + } + + //! Assign a value to each image pixel of the instance image. + CImg& operator=(const T val) { + return fill(val); + } + + //! Operator+ + /** + \remark + - This operator can be used to get a non-shared copy of an image. + **/ + CImg operator+() const { + return CImg(*this,false); + } + + //! Operator+=; +#ifdef cimg_use_visualcpp6 + CImg& operator+=(const T val) +#else + template + CImg& operator+=(const t val) +#endif + { + cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)+val); + return *this; + } + + //! Operator+= + template + CImg& operator+=(const CImg& img) { + if (is_overlapped(img)) return *this+=+img; + const unsigned int smin = cimg::min(size(),img.size()); + t *ptrs = img.data + smin; + for (T *ptrd = data + smin; ptrd>data; --ptrd, (*ptrd)=(T)((*ptrd)+(*(--ptrs)))) {} + return *this; + } + + //! Operator++ (prefix) + CImg& operator++() { + cimg_for(*this,ptr,T) ++(*ptr); + return *this; + } + + //! Operator++ (postfix) + CImg operator++(int) { + const CImg copy(*this,false); + ++*this; + return copy; + } + + //! Operator-. + CImg operator-() const { + return CImg(width,height,depth,dim,0)-=*this; + } + + //! Operator-=. +#ifdef cimg_use_visualcpp6 + CImg& operator-=(const T val) +#else + template + CImg& operator-=(const t val) +#endif + { + cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)-val); + return *this; + } + + //! Operator-=. + template + CImg& operator-=(const CImg& img) { + if (is_overlapped(img)) return *this-=+img; + const unsigned int smin = cimg::min(size(),img.size()); + t *ptrs = img.data+smin; + for (T *ptrd = data+smin; ptrd>data; --ptrd, (*ptrd) = (T)((*ptrd)-(*(--ptrs)))) {} + return *this; + } + + //! Operator-- (prefix). + CImg& operator--() { + cimg_for(*this,ptr,T) *ptr = *ptr-(T)1; + return *this; + } + + //! Operator-- (postfix). + CImg operator--(int) { + CImg copy(*this,false); + --*this; + return copy; + } + + //! Operator*=. +#ifdef cimg_use_visualcpp6 + CImg& operator*=(const double val) +#else + template + CImg& operator*=(const t val) +#endif + { + cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)*val); + return *this; + } + + //! Operator*=. + template + CImg& operator*=(const CImg& img) { + return ((*this)*img).transfer_to(*this); + } + + //! Operator/=. +#ifdef cimg_use_visualcpp6 + CImg& operator/=(const double val) +#else + template + CImg& operator/=(const t val) +#endif + { + cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)/val); + return *this; + } + + //! Operator/=. + template + CImg& operator/=(const CImg& img) { + return assign(*this*img.get_invert()); + } + + //! Modulo. + template + CImg::type> operator%(const CImg& img) const { + typedef typename cimg::superset::type Tt; + return CImg(*this,false)%=img; + } + + //! Modulo. + CImg operator%(const T val) const { + return (+*this)%=val; + } + + //! In-place modulo. + CImg& operator%=(const T val) { + cimg_for(*this,ptr,T) (*ptr) = (T)cimg::mod(*ptr,val); + return *this; + } + + //! In-place modulo. + template + CImg& operator%=(const CImg& img) { + if (is_overlapped(img)) return *this%=+img; + typedef typename cimg::superset::type Tt; + const unsigned int smin = cimg::min(size(),img.size()); + const t *ptrs = img.data + smin; + for (T *ptrd = data + smin; ptrd>data; ) { + T& val = *(--ptrd); + val = (T)cimg::mod((Tt)val,(Tt)*(--ptrs)); + } + return *this; + } + + //! Bitwise AND. + template + CImg::type> operator&(const CImg& img) const { + typedef typename cimg::superset::type Tt; + return CImg(*this,false)&=img; + } + + //! Bitwise AND. + CImg operator&(const T val) const { + return (+*this)&=val; + } + + //! In-place bitwise AND. + template + CImg& operator&=(const CImg& img) { + if (is_overlapped(img)) return *this&=+img; + const unsigned int smin = cimg::min(size(),img.size()); + const t *ptrs = img.data + smin; + for (T *ptrd = data + smin; ptrd>data; ) { + T& val = *(--ptrd); + val = (T)((unsigned long)val & (unsigned long)*(--ptrs)); + } + return *this; + } + + //! In-place bitwise AND. + CImg& operator&=(const T val) { + cimg_for(*this,ptr,T) *ptr = (T)((unsigned long)*ptr & (unsigned long)val); + return *this; + } + + //! Bitwise OR. + template + CImg::type> operator|(const CImg& img) const { + typedef typename cimg::superset::type Tt; + return CImg(*this,false)|=img; + } + + //! Bitwise OR. + CImg operator|(const T val) const { + return (+*this)|=val; + } + + //! In-place bitwise OR. + template + CImg& operator|=(const CImg& img) { + if (is_overlapped(img)) return *this|=+img; + const unsigned int smin = cimg::min(size(),img.size()); + const t *ptrs = img.data + smin; + for (T *ptrd = data + smin; ptrd>data; ) { + T& val = *(--ptrd); + val = (T)((unsigned long)val | (unsigned long)*(--ptrs)); + } + return *this; + } + + //! In-place bitwise OR. + CImg& operator|=(const T val) { + cimg_for(*this,ptr,T) *ptr = (T)((unsigned long)*ptr | (unsigned long)val); + return *this; + } + + //! Bitwise XOR. + template + CImg::type> operator^(const CImg& img) const { + typedef typename cimg::superset::type Tt; + return CImg(*this,false)^=img; + } + + //! Bitwise XOR. + CImg operator^(const T val) const { + return (+*this)^=val; + } + + //! In-place bitwise XOR. + template + CImg& operator^=(const CImg& img) { + if (is_overlapped(img)) return *this^=+img; + const unsigned int smin = cimg::min(size(),img.size()); + const t *ptrs = img.data + smin; + for (T *ptrd = data+smin; ptrd>data; ) { + T& val = *(--ptrd); + val =(T)((unsigned long)val ^ (unsigned long)*(--ptrs)); + } + return *this; + } + + //! In-place bitwise XOR. + CImg& operator^=(const T val) { + cimg_for(*this,ptr,T) *ptr = (T)((unsigned long)*ptr ^ (unsigned long)val); + return *this; + } + + //! Bitwise NOT. + CImg operator~() const { + CImg res(width,height,depth,dim); + const T *ptrs = end(); + cimg_for(res,ptrd,T) { const unsigned long val = (unsigned long)*(--ptrs); *ptrd = (T)~val; } + return res; + } + + //! Bitwise left shift. + CImg& operator<<=(const int n) { + cimg_for(*this,ptr,T) *ptr = (T)(((long)*ptr)< operator<<(const int n) const { + return (+*this)<<=n; + } + + //! Bitwise right shift. + CImg& operator>>=(const int n) { + cimg_for(*this,ptr,T) *ptr = (T)(((long)*ptr)>>n); + return *this; + } + + //! Bitwise right shift. + CImg operator>>(const int n) const { + return (+*this)>>=n; + } + + //! Boolean equality. + template + bool operator==(const CImg& img) const { + const unsigned int siz = size(); + bool vequal = true; + if (siz!=img.size()) return false; + t *ptrs = img.data + siz; + for (T *ptrd = data + siz; vequal && ptrd>data; vequal = vequal && ((*(--ptrd))==(*(--ptrs)))) {} + return vequal; + } + + //! Boolean difference. + template + bool operator!=(const CImg& img) const { + return !((*this)==img); + } + + //! Return a list of two images { *this, img }. + template + CImgList::type> operator<<(const CImg& img) const { + typedef typename cimg::superset::type Tt; + return CImgList(*this,img); + } + + //! Return a copy of \p list, where image *this has been inserted at first position. + template + CImgList::type> operator<<(const CImgList& list) const { + typedef typename cimg::superset::type Tt; + return CImgList(list).insert(*this,0); + } + + //! Return a list of two images { *this, img }. + template + CImgList::type> operator>>(const CImg& img) const { + return (*this)< + CImgList& operator>>(const CImgList& list) const { + return list.insert(*this,0); + } + + //! Display an image into a CImgDisplay. + const CImg& operator>>(CImgDisplay& disp) const { + return display(disp); + } + + //@} + //--------------------------------------- + // + //! \name Usual Mathematics Functions + //@{ + //--------------------------------------- + + //! Apply a R->R function on all pixel values. + template + CImg& apply(t& func) { + cimg_for(*this,ptr,T) *ptr = func(*ptr); + return *this; + } + + template + CImg get_apply(t& func) const { + return (+*this).apply(func); + } + + //! Pointwise multiplication between two images. + template + CImg& mul(const CImg& img) { + if (is_overlapped(img)) return mul(+img); + t *ptrs = img.data; + T *ptrf = data + cimg::min(size(),img.size()); + for (T* ptrd = data; ptrd + CImg::type> get_mul(const CImg& img) const { + typedef typename cimg::superset::type Tt; + return CImg(*this,false).mul(img); + } + + //! Pointwise division between two images. + template + CImg& div(const CImg& img) { + if (is_overlapped(img)) return div(+img); + t *ptrs = img.data; + T *ptrf = data + cimg::min(size(),img.size()); + for (T* ptrd = data; ptrd + CImg::type> get_div(const CImg& img) const { + typedef typename cimg::superset::type Tt; + return CImg(*this,false).div(img); + } + + //! Pointwise max operator between two images. + template + CImg& max(const CImg& img) { + if (is_overlapped(img)) return max(+img); + t *ptrs = img.data; + T *ptrf = data + cimg::min(size(),img.size()); + for (T* ptrd = data; ptrd + CImg::type> get_max(const CImg& img) const { + typedef typename cimg::superset::type Tt; + return CImg(*this,false).max(img); + } + + //! Pointwise max operator between an image and a value. + CImg& max(const T val) { + cimg_for(*this,ptr,T) (*ptr) = cimg::max(*ptr,val); + return *this; + } + + CImg get_max(const T val) const { + return (+*this).max(val); + } + + //! Pointwise min operator between two images. + template + CImg& min(const CImg& img) { + if (is_overlapped(img)) return min(+img); + t *ptrs = img.data; + T *ptrf = data + cimg::min(size(),img.size()); + for (T* ptrd = data; ptrd + CImg::type> get_min(const CImg& img) const { + typedef typename cimg::superset::type Tt; + return CImg(*this,false).min(img); + } + + //! Pointwise min operator between an image and a value. + CImg& min(const T val) { + cimg_for(*this,ptr,T) (*ptr) = cimg::min(*ptr,val); + return *this; + } + + CImg get_min(const T val) const { + return (+*this).min(val); + } + + //! Compute the square value of each pixel. + CImg& sqr() { + cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = (T)(val*val); }; + return *this; + } + + CImg get_sqr() const { + return CImg(*this,false).sqr(); + } + + //! Compute the square root of each pixel value. + CImg& sqrt() { + cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::sqrt((double)(*ptr)); + return *this; + } + + CImg get_sqrt() const { + return CImg(*this,false).sqrt(); + } + + //! Compute the exponential of each pixel value. + CImg& exp() { + cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::exp((double)(*ptr)); + return *this; + } + + CImg get_exp() const { + return CImg(*this,false).exp(); + } + + //! Compute the log of each each pixel value. + CImg& log() { + cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::log((double)(*ptr)); + return *this; + } + + CImg get_log() const { + return CImg(*this,false).log(); + } + + //! Compute the log10 of each each pixel value. + CImg& log10() { + cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::log10((double)(*ptr)); + return *this; + } + + CImg get_log10() const { + return CImg(*this,false).log10(); + } + + //! Compute the power by p of each pixel value. + CImg& pow(const double p) { + if (p==0) return fill(1); + if (p==0.5) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = (T)cimg_std::sqrt((double)val); } return *this; } + if (p==1) return *this; + if (p==2) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val; } return *this; } + if (p==3) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val*val; } return *this; } + if (p==4) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val*val*val; } return *this; } + cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::pow((double)(*ptr),p); + return *this; + } + + CImg get_pow(const double p) const { + return CImg(*this,false).pow(p); + } + + //! Compute the power of each pixel value. + template + CImg& pow(const CImg& img) { + if (is_overlapped(img)) return pow(+img); + t *ptrs = img.data; + T *ptrf = data + cimg::min(size(),img.size()); + for (T* ptrd = data; ptrd + CImg get_pow(const CImg& img) const { + return CImg(*this,false).pow(img); + } + + //! Compute the absolute value of each pixel value. + CImg& abs() { + cimg_for(*this,ptr,T) (*ptr) = cimg::abs(*ptr); + return *this; + } + + CImg get_abs() const { + return CImg(*this,false).abs(); + } + + //! Compute the cosinus of each pixel value. + CImg& cos() { + cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::cos((double)(*ptr)); + return *this; + } + + CImg get_cos() const { + return CImg(*this,false).cos(); + } + + //! Compute the sinus of each pixel value. + CImg& sin() { + cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::sin((double)(*ptr)); + return *this; + } + + CImg get_sin() const { + return CImg(*this,false).sin(); + } + + //! Compute the tangent of each pixel. + CImg& tan() { + cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::tan((double)(*ptr)); + return *this; + } + + CImg get_tan() const { + return CImg(*this,false).tan(); + } + + //! Compute the arc-cosine of each pixel value. + CImg& acos() { + cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::acos((double)(*ptr)); + return *this; + } + + CImg get_acos() const { + return CImg(*this,false).acos(); + } + + //! Compute the arc-sinus of each pixel value. + CImg& asin() { + cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::asin((double)(*ptr)); + return *this; + } + + CImg get_asin() const { + return CImg(*this,false).asin(); + } + + //! Compute the arc-tangent of each pixel. + CImg& atan() { + cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::atan((double)(*ptr)); + return *this; + } + + CImg get_atan() const { + return CImg(*this,false).atan(); + } + + //! Compute image with rounded pixel values. + /** + \param x Rounding precision. + \param rounding_type Roundin type, can be 0 (nearest), 1 (forward), -1(backward). + **/ + CImg& round(const float x, const int rounding_type=0) { + cimg_for(*this,ptr,T) (*ptr) = (T)cimg::round(*ptr,x,rounding_type); + return *this; + } + + CImg get_round(const float x, const unsigned int rounding_type=0) const { + return (+*this).round(x,rounding_type); + } + + //! Fill the instance image with random values between specified range. + CImg& rand(const T val_min, const T val_max) { + const float delta = (float)val_max - (float)val_min; + cimg_for(*this,ptr,T) *ptr = (T)(val_min + cimg::rand()*delta); + return *this; + } + + CImg get_rand(const T val_min, const T val_max) const { + return (+*this).rand(val_min,val_max); + } + + //@} + //----------------------------------- + // + //! \name Usual Image Transformations + //@{ + //----------------------------------- + + //! Fill an image by a value \p val. + /** + \param val = fill value + \note All pixel values of the instance image will be initialized by \p val. + **/ + CImg& fill(const T val) { + if (is_empty()) return *this; + if (val && sizeof(T)!=1) cimg_for(*this,ptr,T) *ptr = val; + else cimg_std::memset(data,(int)val,size()*sizeof(T)); + return *this; + } + + CImg get_fill(const T val) const { + return CImg(width,height,depth,dim).fill(val); + } + + //! Fill sequentially all pixel values with values \a val0 and \a val1 respectively. + CImg& fill(const T val0, const T val1) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-1; + for (ptr = data; ptr get_fill(const T val0, const T val1) const { + return CImg(width,height,depth,dim).fill(val0,val1); + } + + //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2. + CImg& fill(const T val0, const T val1, const T val2) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-2; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2); + } + + //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3. + CImg& fill(const T val0, const T val1, const T val2, const T val3) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-3; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3); + } + + //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3 and \a val4. + CImg& fill(const T val0, const T val1, const T val2, const T val3, const T val4) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-4; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3, const T val4) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3,val4); + } + + //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3 and \a val4 and \a val5. + CImg& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-5; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5); + } + + //! Fill sequentially pixel values. + CImg& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-6; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6); + } + + //! Fill sequentially pixel values. + CImg& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-7; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7); + } + + //! Fill sequentially pixel values. + CImg& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-8; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8); + } + + //! Fill sequentially pixel values. + CImg& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-9; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9); + } + + //! Fill sequentially pixel values. + CImg& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9, const T val10) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-10; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9, const T val10) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10); + } + + //! Fill sequentially pixel values. + CImg& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9, const T val10, const T val11) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-11; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9, const T val10, const T val11) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11); + } + + //! Fill sequentially pixel values. + CImg& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9, const T val10, const T val11, const T val12) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-12; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9, const T val10, const T val11, const T val12) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12); + } + + //! Fill sequentially pixel values. + CImg& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9, const T val10, const T val11, const T val12, + const T val13) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-13; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9, const T val10, const T val11, const T val12, + const T val13) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12, + val13); + } + + //! Fill sequentially pixel values. + CImg& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9, const T val10, const T val11, const T val12, + const T val13, const T val14) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-14; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9, const T val10, const T val11, const T val12, + const T val13, const T val14) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12, + val13,val14); + } + + //! Fill sequentially pixel values. + CImg& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9, const T val10, const T val11, const T val12, + const T val13, const T val14, const T val15) { + if (is_empty()) return *this; + T *ptr, *ptr_end = end()-15; + for (ptr = data; ptr get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6, + const T val7, const T val8, const T val9, const T val10, const T val11, const T val12, + const T val13, const T val14, const T val15) const { + return CImg(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12, + val13,val14,val15); + } + + //! Fill image values according to the values found in the specified string. + CImg& fill(const char *const values, const bool repeat_pattern) { + if (is_empty() || !values) return *this; + T *ptrd = data, *ptr_end = data + size(); + const char *nvalues = values; + const unsigned int siz = size(); + char cval[64] = { 0 }, sep = 0; + int err = 0; double val = 0; unsigned int nb = 0; + while ((err=cimg_std::sscanf(nvalues,"%63[ \n\t0-9e.+-]%c",cval,&sep))>0 && + cimg_std::sscanf(cval,"%lf",&val)>0 && nb get_fill(const char *const values, const bool repeat_pattern) const { + return repeat_pattern?CImg(width,height,depth,dim).fill(values,repeat_pattern):(+*this).fill(values,repeat_pattern); + } + + //! Fill image values according to the values found in the specified image. + template + CImg& fill(const CImg& values, const bool repeat_pattern=true) { + if (is_empty() || !values) return *this; + T *ptrd = data, *ptrd_end = ptrd + size(); + for (t *ptrs = values.data, *ptrs_end = ptrs + values.size(); ptrs + CImg get_fill(const CImg& values, const bool repeat_pattern=true) const { + return repeat_pattern?CImg(width,height,depth,dim).fill(values,repeat_pattern):(+*this).fill(values,repeat_pattern); + } + + //! Fill image values along the X-axis at the specified pixel position (y,z,v). + CImg& fillX(const unsigned int y, const unsigned int z, const unsigned int v, const int a0, ...) { +#define _cimg_fill1(x,y,z,v,off,siz,t) { \ + va_list ap; va_start(ap,a0); T *ptrd = ptr(x,y,z,v); *ptrd = (T)a0; \ + for (unsigned int k = 1; k& fillX(const unsigned int y, const unsigned int z, const unsigned int v, const double a0, ...) { + if (y& fillY(const unsigned int x, const unsigned int z, const unsigned int v, const int a0, ...) { + if (x& fillY(const unsigned int x, const unsigned int z, const unsigned int v, const double a0, ...) { + if (x& fillZ(const unsigned int x, const unsigned int y, const unsigned int v, const int a0, ...) { + const unsigned int wh = width*height; + if (x& fillZ(const unsigned int x, const unsigned int y, const unsigned int v, const double a0, ...) { + const unsigned int wh = width*height; + if (x& fillV(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) { + const unsigned int whz = width*height*depth; + if (x& fillV(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) { + const unsigned int whz = width*height*depth; + if (x& normalize(const T a, const T b) { + if (is_empty()) return *this; + const T na = a get_normalize(const T a, const T b) const { + return (+*this).normalize(a,b); + } + + //! Cut pixel values between \a a and \a b. + CImg& cut(const T a, const T b) { + if (is_empty()) return *this; + const T na = anb)?nb:*ptr); + return *this; + } + + CImg get_cut(const T a, const T b) const { + return (+*this).cut(a,b); + } + + //! Quantize pixel values into \n levels. + CImg& quantize(const unsigned int n, const bool keep_range=true) { + if (is_empty()) return *this; + if (!n) + throw CImgArgumentException("CImg<%s>::quantize() : Cannot quantize image to 0 values.", + pixel_type()); + Tfloat m, M = (Tfloat)maxmin(m), range = M - m; + if (range>0) { + if (keep_range) cimg_for(*this,ptr,T) { + const unsigned int val = (unsigned int)((*ptr-m)*n/range); + *ptr = (T)(m + cimg::min(val,n-1)*range/n); + } else cimg_for(*this,ptr,T) { + const unsigned int val = (unsigned int)((*ptr-m)*n/range); + *ptr = (T)cimg::min(val,n-1); + } + } + return *this; + } + + CImg get_quantize(const unsigned int n, const bool keep_range=true) const { + return (+*this).quantize(n,keep_range); + } + + //! Threshold the image. + /** + \param value Threshold value. + \param soft Enable soft thresholding. + \param strict Tells if the threshold is strict. + **/ + CImg& threshold(const T value, const bool soft=false, const bool strict=false) { + if (is_empty()) return *this; + if (strict) { + if (soft) cimg_for(*this,ptr,T) { const T v = *ptr; *ptr = v>value?(T)(v-value):v<-value?(T)(v+value):(T)0; } + else cimg_for(*this,ptr,T) *ptr = *ptr>value?(T)1:(T)0; + } else { + if (soft) cimg_for(*this,ptr,T) { const T v = *ptr; *ptr = v>=value?(T)(v-value):v<=-value?(T)(v+value):(T)0; } + else cimg_for(*this,ptr,T) *ptr = *ptr>=value?(T)1:(T)0; + } + return *this; + } + + CImg get_threshold(const T value, const bool soft=false, const bool strict=false) const { + return (+*this).threshold(value,soft,strict); + } + + //! Rotate an image. + /** + \param angle = rotation angle (in degrees). + \param cond = rotation type. can be : + - 0 = zero-value at borders + - 1 = nearest pixel. + - 2 = Fourier style. + \note Returned image will probably have a different size than the instance image *this. + **/ + CImg& rotate(const float angle, const unsigned int border_conditions=3, const unsigned int interpolation=1) { + return get_rotate(angle,border_conditions,interpolation).transfer_to(*this); + } + + CImg get_rotate(const float angle, const unsigned int border_conditions=3, const unsigned int interpolation=1) const { + if (is_empty()) return *this; + CImg dest; + const float nangle = cimg::mod(angle,360.0f); + if (border_conditions!=1 && cimg::mod(nangle,90.0f)==0) { // optimized version for orthogonal angles + const int wm1 = dimx()-1, hm1 = dimy()-1; + const int iangle = (int)nangle/90; + switch (iangle) { + case 1 : { + dest.assign(height,width,depth,dim); + cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(y,hm1-x,z,v); + } break; + case 2 : { + dest.assign(width,height,depth,dim); + cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(wm1-x,hm1-y,z,v); + } break; + case 3 : { + dest.assign(height,width,depth,dim); + cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(wm1-y,x,z,v); + } break; + default : + return *this; + } + } else { // generic version + const float + rad = (float)(nangle*cimg::valuePI/180.0), + ca = (float)cimg_std::cos(rad), + sa = (float)cimg_std::sin(rad), + ux = cimg::abs(width*ca), uy = cimg::abs(width*sa), + vx = cimg::abs(height*sa), vy = cimg::abs(height*ca), + w2 = 0.5f*width, h2 = 0.5f*height, + dw2 = 0.5f*(ux+vx), dh2 = 0.5f*(uy+vy); + dest.assign((int)(ux+vx), (int)(uy+vy),depth,dim); + switch (border_conditions) { + case 0 : { + switch (interpolation) { + case 2 : { + cimg_forXY(dest,x,y) cimg_forZV(*this,z,v) + dest(x,y,z,v) = (T)cubic_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v,0); + } break; + case 1 : { + cimg_forXY(dest,x,y) cimg_forZV(*this,z,v) + dest(x,y,z,v) = (T)linear_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v,0); + } break; + default : { + cimg_forXY(dest,x,y) cimg_forZV(*this,z,v) + dest(x,y,z,v) = atXY((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,v,0); + } + } + } break; + case 1 : { + switch (interpolation) { + case 2 : + cimg_forXY(dest,x,y) cimg_forZV(*this,z,v) + dest(x,y,z,v) = (T)cubic_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v); + break; + case 1 : + cimg_forXY(dest,x,y) cimg_forZV(*this,z,v) + dest(x,y,z,v) = (T)linear_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v); + break; + default : + cimg_forXY(dest,x,y) cimg_forZV(*this,z,v) + dest(x,y,z,v) = atXY((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,v); + } + } break; + case 2 : { + switch (interpolation) { + case 2 : + cimg_forXY(dest,x,y) cimg_forZV(*this,z,v) + dest(x,y,z,v) = (T)cubic_atXY(cimg::mod(w2 + (x-dw2)*ca + (y-dh2)*sa,(float)dimx()), + cimg::mod(h2 - (x-dw2)*sa + (y-dh2)*ca,(float)dimy()),z,v); + break; + case 1 : + cimg_forXY(dest,x,y) cimg_forZV(*this,z,v) + dest(x,y,z,v) = (T)linear_atXY(cimg::mod(w2 + (x-dw2)*ca + (y-dh2)*sa,(float)dimx()), + cimg::mod(h2 - (x-dw2)*sa + (y-dh2)*ca,(float)dimy()),z,v); + break; + default : + cimg_forXY(dest,x,y) cimg_forZV(*this,z,v) + dest(x,y,z,v) = (*this)(cimg::mod((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),dimx()), + cimg::mod((int)(h2 - (x-dw2)*sa + (y-dh2)*ca),dimy()),z,v); + } + } break; + default : + throw CImgArgumentException("CImg<%s>::get_rotate() : Invalid border conditions %d (should be 0,1 or 2).", + pixel_type(),border_conditions); + } + } + return dest; + } + + //! Rotate an image around a center point (\c cx,\c cy). + /** + \param angle = rotation angle (in degrees). + \param cx = X-coordinate of the rotation center. + \param cy = Y-coordinate of the rotation center. + \param zoom = zoom. + \param cond = rotation type. can be : + - 0 = zero-value at borders + - 1 = repeat image at borders + - 2 = zero-value at borders and linear interpolation + **/ + CImg& rotate(const float angle, const float cx, const float cy, const float zoom, + const unsigned int border_conditions=3, const unsigned int interpolation=1) { + return get_rotate(angle,cx,cy,zoom,border_conditions,interpolation).transfer_to(*this); + } + + CImg get_rotate(const float angle, const float cx, const float cy, const float zoom, + const unsigned int border_conditions=3, const unsigned int interpolation=1) const { + if (interpolation>2) + throw CImgArgumentException("CImg<%s>::get_rotate() : Invalid interpolation parameter %d (should be {0=none, 1=linear or 2=cubic}).", + pixel_type(),interpolation); + if (is_empty()) return *this; + CImg dest(width,height,depth,dim); + const float nangle = cimg::mod(angle,360.0f); + if (border_conditions!=1 && zoom==1 && cimg::mod(nangle,90.0f)==0) { // optimized version for orthogonal angles + const int iangle = (int)nangle/90; + switch (iangle) { + case 1 : { + dest.fill(0); + const unsigned int + xmin = cimg::max(0,(dimx()-dimy())/2), xmax = cimg::min(width,xmin+height), + ymin = cimg::max(0,(dimy()-dimx())/2), ymax = cimg::min(height,ymin+width), + xoff = xmin + cimg::min(0,(dimx()-dimy())/2), + yoff = ymin + cimg::min(0,(dimy()-dimx())/2); + cimg_forZV(dest,z,v) for (unsigned int y = ymin; y::get_rotate() : Incorrect border conditions %d (should be 0,1 or 2).", + pixel_type(),border_conditions); + } + } + return dest; + } + + //! Resize an image. + /** + \param pdx Number of columns (new size along the X-axis). + \param pdy Number of rows (new size along the Y-axis). + \param pdz Number of slices (new size along the Z-axis). + \param pdv Number of vector-channels (new size along the V-axis). + \param interpolation_type Method of interpolation : + - -1 = no interpolation : raw memory resizing. + - 0 = no interpolation : additional space is filled according to \p border_condition. + - 1 = bloc interpolation (nearest point). + - 2 = moving average interpolation. + - 3 = linear interpolation. + - 4 = grid interpolation. + - 5 = bi-cubic interpolation. + \param border_condition Border condition type. + \param center Set centering type (only if \p interpolation_type=0). + \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100). + **/ + CImg& resize(const int pdx, const int pdy=-100, const int pdz=-100, const int pdv=-100, + const int interpolation_type=1, const int border_condition=-1, const bool center=false) { + if (!pdx || !pdy || !pdz || !pdv) return assign(); + const unsigned int + tdx = pdx<0?-pdx*width/100:pdx, + tdy = pdy<0?-pdy*height/100:pdy, + tdz = pdz<0?-pdz*depth/100:pdz, + tdv = pdv<0?-pdv*dim/100:pdv, + dx = tdx?tdx:1, + dy = tdy?tdy:1, + dz = tdz?tdz:1, + dv = tdv?tdv:1; + if (width==dx && height==dy && depth==dz && dim==dv) return *this; + if (interpolation_type==-1 && dx*dy*dz*dv==size()) { + width = dx; height = dy; depth = dz; dim = dv; + return *this; + } + return get_resize(dx,dy,dz,dv,interpolation_type,border_condition,center).transfer_to(*this); + } + + CImg get_resize(const int pdx, const int pdy=-100, const int pdz=-100, const int pdv=-100, + const int interpolation_type=1, const int border_condition=-1, const bool center=false) const { + if (!pdx || !pdy || !pdz || !pdv) return CImg(); + const unsigned int + tdx = pdx<0?-pdx*width/100:pdx, + tdy = pdy<0?-pdy*height/100:pdy, + tdz = pdz<0?-pdz*depth/100:pdz, + tdv = pdv<0?-pdv*dim/100:pdv, + dx = tdx?tdx:1, + dy = tdy?tdy:1, + dz = tdz?tdz:1, + dv = tdv?tdv:1; + if (width==dx && height==dy && depth==dz && dim==dv) return +*this; + if (is_empty()) return CImg(dx,dy,dz,dv,0); + + CImg res; + + switch (interpolation_type) { + case -1 : // Raw resizing + cimg_std::memcpy(res.assign(dx,dy,dz,dv,0).data,data,sizeof(T)*cimg::min(size(),(long unsigned int)dx*dy*dz*dv)); + break; + + case 0 : { // No interpolation + const unsigned int bx = width-1, by = height-1, bz = depth-1, bv = dim-1; + res.assign(dx,dy,dz,dv); + switch (border_condition) { + case 1 : { + if (center) { + const int + x0 = (res.dimx()-dimx())/2, + y0 = (res.dimy()-dimy())/2, + z0 = (res.dimz()-dimz())/2, + v0 = (res.dimv()-dimv())/2, + x1 = x0 + (int)bx, + y1 = y0 + (int)by, + z1 = z0 + (int)bz, + v1 = v0 + (int)bv; + res.draw_image(x0,y0,z0,v0,*this); + cimg_for_outXYZV(res,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) res(x,y,z,v) = _atXYZV(x-x0,y-y0,z-z0,v-v0); + } else { + res.draw_image(*this); + cimg_for_outXYZV(res,0,0,0,0,bx,by,bz,bv,x,y,z,v) res(x,y,z,v) = _atXYZV(x,y,z,v); + } + } break; + case 2 : { + int nx0 = 0, ny0 = 0, nz0 = 0, nv0 = 0; + if (center) { + const int + x0 = (res.dimx()-dimx())/2, + y0 = (res.dimy()-dimy())/2, + z0 = (res.dimz()-dimz())/2, + v0 = (res.dimv()-dimv())/2; + nx0 = x0>0?x0-(1+x0/width)*width:x0; + ny0 = y0>0?y0-(1+y0/height)*height:y0; + nz0 = z0>0?z0-(1+z0/depth)*depth:z0; + nv0 = v0>0?v0-(1+v0/dim)*dim:v0; + } + for (int k = nv0; k<(int)dv; k+=dimv()) + for (int z = nz0; z<(int)dz; z+=dimz()) + for (int y = ny0; y<(int)dy; y+=dimy()) + for (int x = nx0; x<(int)dx; x+=dimx()) res.draw_image(x,y,z,k,*this); + } break; + default : { + res.fill(0); + if (center) res.draw_image((res.dimx()-dimx())/2,(res.dimy()-dimy())/2,(res.dimz()-dimz())/2,(res.dimv()-dimv())/2,*this); + else res.draw_image(*this); + } + } + } break; + + case 1 : { // Nearest-neighbor interpolation + res.assign(dx,dy,dz,dv); + unsigned int + *const offx = new unsigned int[dx], + *const offy = new unsigned int[dy+1], + *const offz = new unsigned int[dz+1], + *const offv = new unsigned int[dv+1], + *poffx, *poffy, *poffz, *poffv, + curr, old; + const unsigned int wh = width*height, whd = width*height*depth, rwh = dx*dy, rwhd = dx*dy*dz; + poffx = offx; curr = 0; { cimg_forX(res,x) { old=curr; curr=(x+1)*width/dx; *(poffx++) = (unsigned int)curr-(unsigned int)old; }} + poffy = offy; curr = 0; { cimg_forY(res,y) { old=curr; curr=(y+1)*height/dy; *(poffy++) = width*((unsigned int)curr-(unsigned int)old); }} *poffy=0; + poffz = offz; curr = 0; { cimg_forZ(res,z) { old=curr; curr=(z+1)*depth/dz; *(poffz++) = wh*((unsigned int)curr-(unsigned int)old); }} *poffz=0; + poffv = offv; curr = 0; { cimg_forV(res,k) { old=curr; curr=(k+1)*dim/dv; *(poffv++) = whd*((unsigned int)curr-(unsigned int)old); }} *poffv=0; + T *ptrd = res.data; + const T* ptrv = data; + poffv = offv; + for (unsigned int k=0; k tmp(dx,height,depth,dim,0); + for (unsigned int a = width*dx, b = width, c = dx, s = 0, t = 0; a; ) { + const unsigned int d = cimg::min(b,c); + a-=d; b-=d; c-=d; + cimg_forYZV(tmp,y,z,v) tmp(t,y,z,v)+=(Tfloat)(*this)(s,y,z,v)*d; + if (!b) { cimg_forYZV(tmp,y,z,v) tmp(t,y,z,v)/=width; ++t; b = width; } + if (!c) { ++s; c = dx; } + } + tmp.transfer_to(res); + instance_first = false; + } + if (dy!=height) { + CImg tmp(dx,dy,depth,dim,0); + for (unsigned int a = height*dy, b = height, c = dy, s = 0, t = 0; a; ) { + const unsigned int d = cimg::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)(*this)(x,s,z,v)*d; + else cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)res(x,s,z,v)*d; + if (!b) { cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)/=height; ++t; b = height; } + if (!c) { ++s; c = dy; } + } + tmp.transfer_to(res); + instance_first = false; + } + if (dz!=depth) { + CImg tmp(dx,dy,dz,dim,0); + for (unsigned int a = depth*dz, b = depth, c = dz, s = 0, t = 0; a; ) { + const unsigned int d = cimg::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)(*this)(x,y,s,v)*d; + else cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)res(x,y,s,v)*d; + if (!b) { cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)/=depth; ++t; b = depth; } + if (!c) { ++s; c = dz; } + } + tmp.transfer_to(res); + instance_first = false; + } + if (dv!=dim) { + CImg tmp(dx,dy,dz,dv,0); + for (unsigned int a = dim*dv, b = dim, c = dv, s = 0, t = 0; a; ) { + const unsigned int d = cimg::min(b,c); + a-=d; b-=d; c-=d; + if (instance_first) cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)(*this)(x,y,z,s)*d; + else cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)res(x,y,z,s)*d; + if (!b) { cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)/=dim; ++t; b = dim; } + if (!c) { ++s; c = dv; } + } + tmp.transfer_to(res); + instance_first = false; + } + } break; + + case 3 : { // Linear interpolation + const unsigned int dimmax = cimg::max(dx,dy,dz,dv); + const float + sx = (border_condition<0 && dx>width )?(dx>1?(width-1.0f)/(dx-1) :0):(float)width/dx, + sy = (border_condition<0 && dy>height)?(dy>1?(height-1.0f)/(dy-1):0):(float)height/dy, + sz = (border_condition<0 && dz>depth )?(dz>1?(depth-1.0f)/(dz-1) :0):(float)depth/dz, + sv = (border_condition<0 && dv>dim )?(dv>1?(dim-1.0f)/(dv-1) :0):(float)dim/dv; + + unsigned int *const off = new unsigned int[dimmax], *poff; + float *const foff = new float[dimmax], *pfoff, old, curr; + CImg resx, resy, resz, resv; + T *ptrd; + + if (dx!=width) { + if (width==1) resx = get_resize(dx,height,depth,dim,1,0); + else { + resx.assign(dx,height,depth,dim); + curr = old = 0; poff = off; pfoff = foff; + cimg_forX(resx,x) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sx; *(poff++) = (unsigned int)curr-(unsigned int)old; } + ptrd = resx.data; + const T *ptrs0 = data; + cimg_forYZV(resx,y,z,k) { + poff = off; pfoff = foff; + const T *ptrs = ptrs0, *const ptrsmax = ptrs0 + (width-1); + cimg_forX(resx,x) { + const float alpha = *(pfoff++); + const T val1 = *ptrs, val2 = ptrswidth )?(dx>1?(width-1.0f)/(dx-1) :0):(float)width/dx, + sy = (border_condition<0 && dy>height)?(dy>1?(height-1.0f)/(dy-1):0):(float)height/dy, + sz = (border_condition<0 && dz>depth )?(dz>1?(depth-1.0f)/(dz-1) :0):(float)depth/dz, + sv = (border_condition<0 && dv>dim )?(dv>1?(dim-1.0f)/(dv-1) :0):(float)dim/dv; + res.assign(dx,dy,dz,dv); + T *ptrd = res.ptr(); + float cx, cy, cz, ck = 0; + cimg_forV(res,k) { cz = 0; + cimg_forZ(res,z) { cy = 0; + cimg_forY(res,y) { cx = 0; + cimg_forX(res,x) { + *(ptrd++) = (T)(border_condition?_cubic_atXY(cx,cy,(int)cz,(int)ck):cubic_atXY(cx,cy,(int)cz,(int)ck,0)); + cx+=sx; + } cy+=sy; + } cz+=sz; + } ck+=sv; + } + } break; + + default : // Invalid interpolation method + throw CImgArgumentException("CImg<%s>::resize() : Invalid interpolation_type %d " + "(should be { -1=raw, 0=zero, 1=nearest, 2=average, 3=linear, 4=grid, 5=bicubic}).", + pixel_type(),interpolation_type); + } + return res; + } + + //! Resize an image. + /** + \param src Image giving the geometry of the resize. + \param interpolation_type Interpolation method : + - 1 = raw memory + - 0 = no interpolation : additional space is filled with 0. + - 1 = bloc interpolation (nearest point). + - 2 = mosaic : image is repeated if necessary. + - 3 = linear interpolation. + - 4 = grid interpolation. + - 5 = bi-cubic interpolation. + \param border_condition Border condition type. + \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100). + **/ + template + CImg& resize(const CImg& src, const int interpolation_type=1, + const int border_condition=-1, const bool center=false) { + return resize(src.width,src.height,src.depth,src.dim,interpolation_type,border_condition,center); + } + + template + CImg get_resize(const CImg& src, const int interpolation_type=1, + const int border_condition=-1, const bool center=false) const { + return get_resize(src.width,src.height,src.depth,src.dim,interpolation_type,border_condition,center); + } + + //! Resize an image. + /** + \param disp = Display giving the geometry of the resize. + \param interpolation_type = Resizing type : + - 0 = no interpolation : additional space is filled with 0. + - 1 = bloc interpolation (nearest point). + - 2 = mosaic : image is repeated if necessary. + - 3 = linear interpolation. + - 4 = grid interpolation. + - 5 = bi-cubic interpolation. + - 6 = moving average (best quality for photographs) + \param border_condition Border condition type. + \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100). + **/ + CImg& resize(const CImgDisplay& disp, const int interpolation_type=1, + const int border_condition=-1, const bool center=false) { + return resize(disp.width,disp.height,depth,dim,interpolation_type,border_condition,center); + } + + CImg get_resize(const CImgDisplay& disp, const int interpolation_type=1, + const int border_condition=-1, const bool center=false) const { + return get_resize(disp.width,disp.height,depth,dim,interpolation_type,border_condition,center); + } + + //! Half-resize an image, using a special optimized filter. + CImg& resize_halfXY() { + return get_resize_halfXY().transfer_to(*this); + } + + CImg get_resize_halfXY() const { + if (is_empty()) return *this; + const Tfloat mask[9] = { 0.07842776544f, 0.1231940459f, 0.07842776544f, + 0.1231940459f, 0.1935127547f, 0.1231940459f, + 0.07842776544f, 0.1231940459f, 0.07842776544f }; + T I[9] = { 0 }; + CImg dest(width/2,height/2,depth,dim); + cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) + if (x%2 && y%2) dest(x/2,y/2,z,k) = (T) + (I[0]*mask[0] + I[1]*mask[1] + I[2]*mask[2] + + I[3]*mask[3] + I[4]*mask[4] + I[5]*mask[5] + + I[6]*mask[6] + I[7]*mask[7] + I[8]*mask[8]); + return dest; + } + + //! Upscale an image by a factor 2x. + /** + Use anisotropic upscaling algorithm described at + http://scale2x.sourceforge.net/algorithm.html + **/ + CImg& resize_doubleXY() { + return get_resize_doubleXY().transfer_to(*this); + } + + CImg get_resize_doubleXY() const { +#define _cimg_gs2x_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound)-1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i, ptrd1+=(res).width, ptrd2+=(res).width) + +#define _cimg_gs2x_for3x3(img,x,y,z,v,I) \ + _cimg_gs2x_for3((img).height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[1] = (img)(0,_p1##y,z,v)), \ + (I[3] = I[4] = (img)(0,y,z,v)), \ + (I[7] = (img)(0,_n1##y,z,v)), \ + 1>=(img).width?(int)((img).width)-1:1); \ + (_n1##x<(int)((img).width) && ( \ + (I[2] = (img)(_n1##x,_p1##y,z,v)), \ + (I[5] = (img)(_n1##x,y,z,v)), \ + (I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \ + x==--_n1##x; \ + I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + + if (is_empty()) return *this; + CImg res(2*width,2*height,depth,dim); + CImg_3x3(I,T); + cimg_forZV(*this,z,k) { + T + *ptrd1 = res.ptr(0,0,0,k), + *ptrd2 = ptrd1 + res.width; + _cimg_gs2x_for3x3(*this,x,y,0,k,I) { + if (Icp!=Icn && Ipc!=Inc) { + *(ptrd1++) = Ipc==Icp?Ipc:Icc; + *(ptrd1++) = Icp==Inc?Inc:Icc; + *(ptrd2++) = Ipc==Icn?Ipc:Icc; + *(ptrd2++) = Icn==Inc?Inc:Icc; + } else { *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; } + } + } + return res; + } + + //! Upscale an image by a factor 3x. + /** + Use anisotropic upscaling algorithm described at + http://scale2x.sourceforge.net/algorithm.html + **/ + CImg& resize_tripleXY() { + return get_resize_tripleXY().transfer_to(*this); + } + + CImg get_resize_tripleXY() const { +#define _cimg_gs3x_for3(bound,i) \ + for (int i = 0, _p1##i = 0, \ + _n1##i = 1>=(bound)?(int)(bound)-1:1; \ + _n1##i<(int)(bound) || i==--_n1##i; \ + _p1##i = i++, ++_n1##i, ptrd1+=2*(res).width, ptrd2+=2*(res).width, ptrd3+=2*(res).width) + +#define _cimg_gs3x_for3x3(img,x,y,z,v,I) \ + _cimg_gs3x_for3((img).height,y) for (int x = 0, \ + _p1##x = 0, \ + _n1##x = (int)( \ + (I[0] = I[1] = (img)(0,_p1##y,z,v)), \ + (I[3] = I[4] = (img)(0,y,z,v)), \ + (I[6] = I[7] = (img)(0,_n1##y,z,v)), \ + 1>=(img).width?(int)((img).width)-1:1); \ + (_n1##x<(int)((img).width) && ( \ + (I[2] = (img)(_n1##x,_p1##y,z,v)), \ + (I[5] = (img)(_n1##x,y,z,v)), \ + (I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \ + x==--_n1##x; \ + I[0] = I[1], I[1] = I[2], \ + I[3] = I[4], I[4] = I[5], \ + I[6] = I[7], I[7] = I[8], \ + _p1##x = x++, ++_n1##x) + + if (is_empty()) return *this; + CImg res(3*width,3*height,depth,dim); + CImg_3x3(I,T); + cimg_forZV(*this,z,k) { + T + *ptrd1 = res.ptr(0,0,0,k), + *ptrd2 = ptrd1 + res.width, + *ptrd3 = ptrd2 + res.width; + _cimg_gs3x_for3x3(*this,x,y,0,k,I) { + if (Icp != Icn && Ipc != Inc) { + *(ptrd1++) = Ipc==Icp?Ipc:Icc; + *(ptrd1++) = (Ipc==Icp && Icc!=Inp) || (Icp==Inc && Icc!=Ipp)?Icp:Icc; + *(ptrd1++) = Icp==Inc?Inc:Icc; + *(ptrd2++) = (Ipc==Icp && Icc!=Ipn) || (Ipc==Icn && Icc!=Ipp)?Ipc:Icc; + *(ptrd2++) = Icc; + *(ptrd2++) = (Icp==Inc && Icc!=Inn) || (Icn==Inc && Icc!=Inp)?Inc:Icc; + *(ptrd3++) = Ipc==Icn?Ipc:Icc; + *(ptrd3++) = (Ipc==Icn && Icc!=Inn) || (Icn==Inc && Icc!=Ipn)?Icn:Icc; + *(ptrd3++) = Icn==Inc?Inc:Icc; + } else { + *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd1++) = Icc; + *(ptrd2++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; + *(ptrd3++) = Icc; *(ptrd3++) = Icc; *(ptrd3++) = Icc; + } + } + } + return res; + } + + // Warp an image. + template + CImg& warp(const CImg& warp, const bool relative=false, + const bool interpolation=true, const unsigned int border_conditions=0) { + return get_warp(warp,relative,interpolation,border_conditions).transfer_to(*this); + } + + template + CImg get_warp(const CImg& warp, const bool relative=false, + const bool interpolation=true, const unsigned int border_conditions=0) const { + if (is_empty() || !warp) return *this; + if (!is_sameXYZ(warp)) + throw CImgArgumentException("CImg<%s>::warp() : Instance image (%u,%u,%u,%u,%p) and warping field (%u,%u,%u,%u,%p) " + "have different XYZ dimensions.", + pixel_type(),width,height,depth,dim,data, + warp.width,warp.height,warp.depth,warp.dim,warp.data); + CImg res(width,height,depth,dim); + switch (warp.dim) { + case 1 : // 1D warping. + if (relative) { // Relative warp coordinates + if (interpolation) switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atX(cimg::mod(x-(float)warp(x,y,z,0),(float)width),y,z,v); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atX(x-(float)warp(x,y,z,0),y,z,v); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)linear_atX(x-(float)warp(x,y,z,0),y,z,v,0); + } + } else switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),y,z,v); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = _atX(x-(int)warp(x,y,z,0),y,z,v); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = atX(x-(int)warp(x,y,z,0),y,z,v,0); + } + } + } else { // Absolute warp coordinates + if (interpolation) switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atX(cimg::mod((float)warp(x,y,z,0),(float)width),y,z,v); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atX((float)warp(x,y,z,0),y,z,v); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)linear_atX((float)warp(x,y,z,0),y,z,v,0); + } + } else switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),y,z,v); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = _atX((int)warp(x,y,z,0),y,z,v); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = atX((int)warp(x,y,z,0),y,z,v,0); + } + } + } + break; + + case 2 : // 2D warping + if (relative) { // Relative warp coordinates + if (interpolation) switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atXY(cimg::mod(x-(float)warp(x,y,z,0),(float)width), + cimg::mod(y-(float)warp(x,y,z,1),(float)height),z,v); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atXY(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z,v); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)linear_atXY(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z,v,0); + } + } else switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width), + cimg::mod(y-(int)warp(x,y,z,1),(int)height),z,v); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = _atXY(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z,v); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = atXY(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z,v,0); + } + } + } else { // Absolute warp coordinates + if (interpolation) switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atXY(cimg::mod((float)warp(x,y,z,0),(float)width), + cimg::mod((float)warp(x,y,z,1),(float)height),z,v); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atXY((float)warp(x,y,z,0),(float)warp(x,y,z,1),z,v); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)linear_atXY((float)warp(x,y,z,0),(float)warp(x,y,z,1),z,v,0); + } + } else switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width), + cimg::mod((int)warp(x,y,z,1),(int)depth),z,v); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = _atXY((int)warp(x,y,z,0),(int)warp(x,y,z,1),z,v); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = atXY((int)warp(x,y,z,0),(int)warp(x,y,z,1),z,v,0); + } + } + } + break; + + case 3 : // 3D warping + if (relative) { // Relative warp coordinates + if (interpolation) switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atXYZ(cimg::mod(x-(float)warp(x,y,z,0),(float)width), + cimg::mod(y-(float)warp(x,y,z,1),(float)height), + cimg::mod(z-(float)warp(x,y,z,2),(float)depth),v); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atXYZ(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)linear_atXYZ(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v,0); + } + } else switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width), + cimg::mod(y-(int)warp(x,y,z,1),(int)height), + cimg::mod(z-(int)warp(x,y,z,2),(int)depth),v); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = _atXYZ(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = atXYZ(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v,0); + } + } + } else { // Absolute warp coordinates + if (interpolation) switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atXYZ(cimg::mod((float)warp(x,y,z,0),(float)width), + cimg::mod((float)warp(x,y,z,1),(float)height), + cimg::mod((float)warp(x,y,z,2),(float)depth),v); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atXYZ((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),v); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)linear_atXYZ((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),v,0); + } + } else switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width), + cimg::mod((int)warp(x,y,z,1),(int)height), + cimg::mod((int)warp(x,y,z,2),(int)depth),v); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = _atXYZ((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),v); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = atXYZ((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),v,0); + } + } + } + break; + + default : // 4D warping + if (relative) { // Relative warp coordinates + if (interpolation) switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atXYZV(cimg::mod(x-(float)warp(x,y,z,0),(float)width), + cimg::mod(y-(float)warp(x,y,z,1),(float)height), + cimg::mod(z-(float)warp(x,y,z,2),(float)depth), + cimg::mod(z-(float)warp(x,y,z,3),(float)dim)); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atXYZV(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v-(float)warp(x,y,z,3)); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)linear_atXYZV(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v-(float)warp(x,y,z,3),0); + } + } else switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width), + cimg::mod(y-(int)warp(x,y,z,1),(int)height), + cimg::mod(z-(int)warp(x,y,z,2),(int)depth), + cimg::mod(v-(int)warp(x,y,z,3),(int)dim)); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = _atXYZV(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v-(int)warp(x,y,z,3)); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = atXYZ(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v-(int)warp(x,y,z,3),0); + } + } + } else { // Absolute warp coordinates + if (interpolation) switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atXYZV(cimg::mod((float)warp(x,y,z,0),(float)width), + cimg::mod((float)warp(x,y,z,1),(float)height), + cimg::mod((float)warp(x,y,z,2),(float)depth), + cimg::mod((float)warp(x,y,z,3),(float)dim)); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)_linear_atXYZV((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),(float)warp(x,y,z,3)); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (T)linear_atXYZV((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),(float)warp(x,y,z,3),0); + } + } else switch (border_conditions) { + case 2 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width), + cimg::mod((int)warp(x,y,z,1),(int)height), + cimg::mod((int)warp(x,y,z,2),(int)depth), + cimg::mod((int)warp(x,y,z,3),(int)dim)); + } break; + case 1 : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = _atXYZV((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),(int)warp(x,y,z,3)); + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) + res(x,y,z,v) = atXYZV((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),(int)warp(x,y,z,3),0); + } + } + } + } + return res; + } + + // Permute axes order (internal). + template + CImg _get_permute_axes(const char *permut, const t&) const { + if (is_empty() || !permut) return CImg(*this,false); + CImg res; + const T* ptrs = data; + if (!cimg::strncasecmp(permut,"xyzv",4)) return (+*this); + if (!cimg::strncasecmp(permut,"xyvz",4)) { + res.assign(width,height,dim,depth); + cimg_forXYZV(*this,x,y,z,v) res(x,y,v,z) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"xzyv",4)) { + res.assign(width,depth,height,dim); + cimg_forXYZV(*this,x,y,z,v) res(x,z,y,v) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"xzvy",4)) { + res.assign(width,depth,dim,height); + cimg_forXYZV(*this,x,y,z,v) res(x,z,v,y) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"xvyz",4)) { + res.assign(width,dim,height,depth); + cimg_forXYZV(*this,x,y,z,v) res(x,v,y,z) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"xvzy",4)) { + res.assign(width,dim,depth,height); + cimg_forXYZV(*this,x,y,z,v) res(x,v,z,y) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"yxzv",4)) { + res.assign(height,width,depth,dim); + cimg_forXYZV(*this,x,y,z,v) res(y,x,z,v) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"yxvz",4)) { + res.assign(height,width,dim,depth); + cimg_forXYZV(*this,x,y,z,v) res(y,x,v,z) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"yzxv",4)) { + res.assign(height,depth,width,dim); + cimg_forXYZV(*this,x,y,z,v) res(y,z,x,v) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"yzvx",4)) { + res.assign(height,depth,dim,width); + switch (width) { + case 1 : { + t *ptrR = res.ptr(0,0,0,0); + for (unsigned long siz = height*depth*dim; siz; --siz) { + *(ptrR++) = (t)*(ptrs++); + } + } break; + case 2 : { + t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1); + for (unsigned long siz = height*depth*dim; siz; --siz) { + *(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++); + } + } break; + case 3 : { // Optimization for the classical conversion from interleaved RGB to planar RGB + t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1), *ptrB = res.ptr(0,0,0,2); + for (unsigned long siz = height*depth*dim; siz; --siz) { + *(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++); *(ptrB++) = (t)*(ptrs++); + } + } break; + case 4 : { // Optimization for the classical conversion from interleaved RGBA to planar RGBA + t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1), *ptrB = res.ptr(0,0,0,2), *ptrA = res.ptr(0,0,0,3); + for (unsigned long siz = height*depth*dim; siz; --siz) { + *(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++); *(ptrB++) = (t)*(ptrs++); *(ptrA++) = (t)*(ptrs++); + } + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) res(y,z,v,x) = *(ptrs++); + return res; + } + } + } + if (!cimg::strncasecmp(permut,"yvxz",4)) { + res.assign(height,dim,width,depth); + cimg_forXYZV(*this,x,y,z,v) res(y,v,x,z) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"yvzx",4)) { + res.assign(height,dim,depth,width); + cimg_forXYZV(*this,x,y,z,v) res(y,v,z,x) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"zxyv",4)) { + res.assign(depth,width,height,dim); + cimg_forXYZV(*this,x,y,z,v) res(z,x,y,v) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"zxvy",4)) { + res.assign(depth,width,dim,height); + cimg_forXYZV(*this,x,y,z,v) res(z,x,v,y) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"zyxv",4)) { + res.assign(depth,height,width,dim); + cimg_forXYZV(*this,x,y,z,v) res(z,y,x,v) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"zyvx",4)) { + res.assign(depth,height,dim,width); + cimg_forXYZV(*this,x,y,z,v) res(z,y,v,x) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"zvxy",4)) { + res.assign(depth,dim,width,height); + cimg_forXYZV(*this,x,y,z,v) res(z,v,x,y) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"zvyx",4)) { + res.assign(depth,dim,height,width); + cimg_forXYZV(*this,x,y,z,v) res(z,v,y,x) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"vxyz",4)) { + res.assign(dim,width,height,depth); + switch (dim) { + case 1 : { + const T *ptrR = ptr(0,0,0,0); + t *ptrd = res.ptr(); + for (unsigned long siz = width*height*depth; siz; --siz) { + *(ptrd++) = (t)*(ptrR++); + } + } break; + case 2 : { + const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1); + t *ptrd = res.ptr(); + for (unsigned long siz = width*height*depth; siz; --siz) { + *(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++); + } + } break; + case 3 : { // Optimization for the classical conversion from planar RGB to interleaved RGB + const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1), *ptrB = ptr(0,0,0,2); + t *ptrd = res.ptr(); + for (unsigned long siz = width*height*depth; siz; --siz) { + *(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++); *(ptrd++) = (t)*(ptrB++); + } + } break; + case 4 : { // Optimization for the classical conversion from planar RGBA to interleaved RGBA + const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1), *ptrB = ptr(0,0,0,2), *ptrA = ptr(0,0,0,3); + t *ptrd = res.ptr(); + for (unsigned long siz = width*height*depth; siz; --siz) { + *(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++); *(ptrd++) = (t)*(ptrB++); *(ptrd++) = (t)*(ptrA++); + } + } break; + default : { + cimg_forXYZV(*this,x,y,z,v) res(v,x,y,z) = (t)*(ptrs++); + } + } + } + if (!cimg::strncasecmp(permut,"vxzy",4)) { + res.assign(dim,width,depth,height); + cimg_forXYZV(*this,x,y,z,v) res(v,x,z,y) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"vyxz",4)) { + res.assign(dim,height,width,depth); + cimg_forXYZV(*this,x,y,z,v) res(v,y,x,z) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"vyzx",4)) { + res.assign(dim,height,depth,width); + cimg_forXYZV(*this,x,y,z,v) res(v,y,z,x) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"vzxy",4)) { + res.assign(dim,depth,width,height); + cimg_forXYZV(*this,x,y,z,v) res(v,z,x,y) = (t)*(ptrs++); + } + if (!cimg::strncasecmp(permut,"vzyx",4)) { + res.assign(dim,depth,height,width); + cimg_forXYZV(*this,x,y,z,v) res(v,z,y,x) = (t)*(ptrs++); + } + if (!res) + throw CImgArgumentException("CImg<%s>::permute_axes() : Invalid input permutation '%s'.", + pixel_type(),permut); + return res; + } + + //! Permute axes order. + /** + This function permutes image axes. + \param permut = String describing the permutation (4 characters). + **/ + CImg& permute_axes(const char *order) { + return get_permute_axes(order).transfer_to(*this); + } + + CImg get_permute_axes(const char *order) const { + const T foo = (T)0; + return _get_permute_axes(order,foo); + } + + //! Invert endianness. + CImg& invert_endianness() { + cimg::invert_endianness(data,size()); + return *this; + } + + CImg get_invert_endianness() const { + return (+*this).invert_endianness(); + } + + //! Mirror an image along the specified axis. + CImg& mirror(const char axis) { + if (is_empty()) return *this; + T *pf, *pb, *buf = 0; + switch (cimg::uncase(axis)) { + case 'x' : { + pf = data; pb = ptr(width-1); + const unsigned int width2 = width/2; + for (unsigned int yzv = 0; yzv::mirror() : unknow axis '%c', must be 'x','y','z' or 'v'.", + pixel_type(),axis); + } + if (buf) delete[] buf; + return *this; + } + + CImg get_mirror(const char axis) const { + return (+*this).mirror(axis); + } + + //! Translate the image. + /** + \param deltax Amount of displacement along the X-axis. + \param deltay Amount of displacement along the Y-axis. + \param deltaz Amount of displacement along the Z-axis. + \param deltav Amount of displacement along the V-axis. + \param border_condition Border condition. + + - \c border_condition can be : + - 0 : Zero border condition (Dirichlet). + - 1 : Nearest neighbors (Neumann). + - 2 : Repeat Pattern (Fourier style). + **/ + CImg& translate(const int deltax, const int deltay=0, const int deltaz=0, const int deltav=0, + const int border_condition=0) { + if (is_empty()) return *this; + if (deltax) // Translate along X-axis + switch (border_condition) { + case 0 : + if (cimg::abs(deltax)>=dimx()) return fill(0); + if (deltax>0) cimg_forYZV(*this,y,z,k) { + cimg_std::memmove(ptr(0,y,z,k),ptr(deltax,y,z,k),(width-deltax)*sizeof(T)); + cimg_std::memset(ptr(width-deltax,y,z,k),0,deltax*sizeof(T)); + } else cimg_forYZV(*this,y,z,k) { + cimg_std::memmove(ptr(-deltax,y,z,k),ptr(0,y,z,k),(width+deltax)*sizeof(T)); + cimg_std::memset(ptr(0,y,z,k),0,-deltax*sizeof(T)); + } + break; + case 1 : + if (deltax>0) { + const int ndeltax = (deltax>=dimx())?width-1:deltax; + if (!ndeltax) return *this; + cimg_forYZV(*this,y,z,k) { + cimg_std::memmove(ptr(0,y,z,k),ptr(ndeltax,y,z,k),(width-ndeltax)*sizeof(T)); + T *ptrd = ptr(width-1,y,z,k); + const T val = *ptrd; + for (int l = 0; l=dimx())?width-1:-deltax; + if (!ndeltax) return *this; + cimg_forYZV(*this,y,z,k) { + cimg_std::memmove(ptr(ndeltax,y,z,k),ptr(0,y,z,k),(width-ndeltax)*sizeof(T)); + T *ptrd = ptr(0,y,z,k); + const T val = *ptrd; + for (int l = 0; l0) cimg_forYZV(*this,y,z,k) { + cimg_std::memcpy(buf,ptr(0,y,z,k),ndeltax*sizeof(T)); + cimg_std::memmove(ptr(0,y,z,k),ptr(ndeltax,y,z,k),(width-ndeltax)*sizeof(T)); + cimg_std::memcpy(ptr(width-ndeltax,y,z,k),buf,ndeltax*sizeof(T)); + } else cimg_forYZV(*this,y,z,k) { + cimg_std::memcpy(buf,ptr(width+ndeltax,y,z,k),-ndeltax*sizeof(T)); + cimg_std::memmove(ptr(-ndeltax,y,z,k),ptr(0,y,z,k),(width+ndeltax)*sizeof(T)); + cimg_std::memcpy(ptr(0,y,z,k),buf,-ndeltax*sizeof(T)); + } + delete[] buf; + } break; + } + + if (deltay) // Translate along Y-axis + switch (border_condition) { + case 0 : + if (cimg::abs(deltay)>=dimy()) return fill(0); + if (deltay>0) cimg_forZV(*this,z,k) { + cimg_std::memmove(ptr(0,0,z,k),ptr(0,deltay,z,k),width*(height-deltay)*sizeof(T)); + cimg_std::memset(ptr(0,height-deltay,z,k),0,width*deltay*sizeof(T)); + } else cimg_forZV(*this,z,k) { + cimg_std::memmove(ptr(0,-deltay,z,k),ptr(0,0,z,k),width*(height+deltay)*sizeof(T)); + cimg_std::memset(ptr(0,0,z,k),0,-deltay*width*sizeof(T)); + } + break; + case 1 : + if (deltay>0) { + const int ndeltay = (deltay>=dimy())?height-1:deltay; + if (!ndeltay) return *this; + cimg_forZV(*this,z,k) { + cimg_std::memmove(ptr(0,0,z,k),ptr(0,ndeltay,z,k),width*(height-ndeltay)*sizeof(T)); + T *ptrd = ptr(0,height-ndeltay,z,k), *ptrs = ptr(0,height-1,z,k); + for (int l = 0; l=dimy())?height-1:-deltay; + if (!ndeltay) return *this; + cimg_forZV(*this,z,k) { + cimg_std::memmove(ptr(0,ndeltay,z,k),ptr(0,0,z,k),width*(height-ndeltay)*sizeof(T)); + T *ptrd = ptr(0,1,z,k), *ptrs = ptr(0,0,z,k); + for (int l = 0; l0) cimg_forZV(*this,z,k) { + cimg_std::memcpy(buf,ptr(0,0,z,k),width*ndeltay*sizeof(T)); + cimg_std::memmove(ptr(0,0,z,k),ptr(0,ndeltay,z,k),width*(height-ndeltay)*sizeof(T)); + cimg_std::memcpy(ptr(0,height-ndeltay,z,k),buf,width*ndeltay*sizeof(T)); + } else cimg_forZV(*this,z,k) { + cimg_std::memcpy(buf,ptr(0,height+ndeltay,z,k),-ndeltay*width*sizeof(T)); + cimg_std::memmove(ptr(0,-ndeltay,z,k),ptr(0,0,z,k),width*(height+ndeltay)*sizeof(T)); + cimg_std::memcpy(ptr(0,0,z,k),buf,-ndeltay*width*sizeof(T)); + } + delete[] buf; + } break; + } + + if (deltaz) // Translate along Z-axis + switch (border_condition) { + case 0 : + if (cimg::abs(deltaz)>=dimz()) return fill(0); + if (deltaz>0) cimg_forV(*this,k) { + cimg_std::memmove(ptr(0,0,0,k),ptr(0,0,deltaz,k),width*height*(depth-deltaz)*sizeof(T)); + cimg_std::memset(ptr(0,0,depth-deltaz,k),0,width*height*deltaz*sizeof(T)); + } else cimg_forV(*this,k) { + cimg_std::memmove(ptr(0,0,-deltaz,k),ptr(0,0,0,k),width*height*(depth+deltaz)*sizeof(T)); + cimg_std::memset(ptr(0,0,0,k),0,-deltaz*width*height*sizeof(T)); + } + break; + case 1 : + if (deltaz>0) { + const int ndeltaz = (deltaz>=dimz())?depth-1:deltaz; + if (!ndeltaz) return *this; + cimg_forV(*this,k) { + cimg_std::memmove(ptr(0,0,0,k),ptr(0,0,ndeltaz,k),width*height*(depth-ndeltaz)*sizeof(T)); + T *ptrd = ptr(0,0,depth-ndeltaz,k), *ptrs = ptr(0,0,depth-1,k); + for (int l = 0; l=dimz())?depth-1:-deltaz; + if (!ndeltaz) return *this; + cimg_forV(*this,k) { + cimg_std::memmove(ptr(0,0,ndeltaz,k),ptr(0,0,0,k),width*height*(depth-ndeltaz)*sizeof(T)); + T *ptrd = ptr(0,0,1,k), *ptrs = ptr(0,0,0,k); + for (int l = 0; l0) cimg_forV(*this,k) { + cimg_std::memcpy(buf,ptr(0,0,0,k),width*height*ndeltaz*sizeof(T)); + cimg_std::memmove(ptr(0,0,0,k),ptr(0,0,ndeltaz,k),width*height*(depth-ndeltaz)*sizeof(T)); + cimg_std::memcpy(ptr(0,0,depth-ndeltaz,k),buf,width*height*ndeltaz*sizeof(T)); + } else cimg_forV(*this,k) { + cimg_std::memcpy(buf,ptr(0,0,depth+ndeltaz,k),-ndeltaz*width*height*sizeof(T)); + cimg_std::memmove(ptr(0,0,-ndeltaz,k),ptr(0,0,0,k),width*height*(depth+ndeltaz)*sizeof(T)); + cimg_std::memcpy(ptr(0,0,0,k),buf,-ndeltaz*width*height*sizeof(T)); + } + delete[] buf; + } break; + } + + if (deltav) // Translate along V-axis + switch (border_condition) { + case 0 : + if (cimg::abs(deltav)>=dimv()) return fill(0); + if (deltav>0) { + cimg_std::memmove(data,ptr(0,0,0,deltav),width*height*depth*(dim-deltav)*sizeof(T)); + cimg_std::memset(ptr(0,0,0,dim-deltav),0,width*height*depth*deltav*sizeof(T)); + } else cimg_forV(*this,k) { + cimg_std::memmove(ptr(0,0,0,-deltav),data,width*height*depth*(dim+deltav)*sizeof(T)); + cimg_std::memset(data,0,-deltav*width*height*depth*sizeof(T)); + } + break; + case 1 : + if (deltav>0) { + const int ndeltav = (deltav>=dimv())?dim-1:deltav; + if (!ndeltav) return *this; + cimg_std::memmove(data,ptr(0,0,0,ndeltav),width*height*depth*(dim-ndeltav)*sizeof(T)); + T *ptrd = ptr(0,0,0,dim-ndeltav), *ptrs = ptr(0,0,0,dim-1); + for (int l = 0; l=dimv())?dim-1:-deltav; + if (!ndeltav) return *this; + cimg_std::memmove(ptr(0,0,0,ndeltav),data,width*height*depth*(dim-ndeltav)*sizeof(T)); + T *ptrd = ptr(0,0,0,1); + for (int l = 0; l0) { + cimg_std::memcpy(buf,data,width*height*depth*ndeltav*sizeof(T)); + cimg_std::memmove(data,ptr(0,0,0,ndeltav),width*height*depth*(dim-ndeltav)*sizeof(T)); + cimg_std::memcpy(ptr(0,0,0,dim-ndeltav),buf,width*height*depth*ndeltav*sizeof(T)); + } else { + cimg_std::memcpy(buf,ptr(0,0,0,dim+ndeltav),-ndeltav*width*height*depth*sizeof(T)); + cimg_std::memmove(ptr(0,0,0,-ndeltav),data,width*height*depth*(dim+ndeltav)*sizeof(T)); + cimg_std::memcpy(data,buf,-ndeltav*width*height*depth*sizeof(T)); + } + delete[] buf; + } break; + } + return *this; + } + + CImg get_translate(const int deltax, const int deltay=0, const int deltaz=0, const int deltav=0, + const int border_condition=0) const { + return (+*this).translate(deltax,deltay,deltaz,deltav,border_condition); + } + + //! Get a square region of the image. + /** + \param x0 = X-coordinate of the upper-left crop rectangle corner. + \param y0 = Y-coordinate of the upper-left crop rectangle corner. + \param z0 = Z-coordinate of the upper-left crop rectangle corner. + \param v0 = V-coordinate of the upper-left crop rectangle corner. + \param x1 = X-coordinate of the lower-right crop rectangle corner. + \param y1 = Y-coordinate of the lower-right crop rectangle corner. + \param z1 = Z-coordinate of the lower-right crop rectangle corner. + \param v1 = V-coordinate of the lower-right crop rectangle corner. + \param border_condition = Dirichlet (false) or Neumann border conditions. + **/ + CImg& crop(const int x0, const int y0, const int z0, const int v0, + const int x1, const int y1, const int z1, const int v1, + const bool border_condition=false) { + return get_crop(x0,y0,z0,v0,x1,y1,z1,v1,border_condition).transfer_to(*this); + } + + CImg get_crop(const int x0, const int y0, const int z0, const int v0, + const int x1, const int y1, const int z1, const int v1, + const bool border_condition=false) const { + if (is_empty()) return *this; + const int + nx0 = x0 dest(1U+nx1-nx0,1U+ny1-ny0,1U+nz1-nz0,1U+nv1-nv0); + if (nx0<0 || nx1>=dimx() || ny0<0 || ny1>=dimy() || nz0<0 || nz1>=dimz() || nv0<0 || nv1>=dimv()) { + if (border_condition) cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = _atXYZV(nx0+x,ny0+y,nz0+z,nv0+v); + else dest.fill(0).draw_image(-nx0,-ny0,-nz0,-nv0,*this); + } else dest.draw_image(-nx0,-ny0,-nz0,-nv0,*this); + return dest; + } + + //! Get a rectangular part of the instance image. + /** + \param x0 = X-coordinate of the upper-left crop rectangle corner. + \param y0 = Y-coordinate of the upper-left crop rectangle corner. + \param z0 = Z-coordinate of the upper-left crop rectangle corner. + \param x1 = X-coordinate of the lower-right crop rectangle corner. + \param y1 = Y-coordinate of the lower-right crop rectangle corner. + \param z1 = Z-coordinate of the lower-right crop rectangle corner. + \param border_condition = determine the type of border condition if + some of the desired region is outside the image. + **/ + CImg& crop(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const bool border_condition=false) { + return crop(x0,y0,z0,0,x1,y1,z1,dim-1,border_condition); + } + + CImg get_crop(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const bool border_condition=false) const { + return get_crop(x0,y0,z0,0,x1,y1,z1,dim-1,border_condition); + } + + //! Get a rectangular part of the instance image. + /** + \param x0 = X-coordinate of the upper-left crop rectangle corner. + \param y0 = Y-coordinate of the upper-left crop rectangle corner. + \param x1 = X-coordinate of the lower-right crop rectangle corner. + \param y1 = Y-coordinate of the lower-right crop rectangle corner. + \param border_condition = determine the type of border condition if + some of the desired region is outside the image. + **/ + CImg& crop(const int x0, const int y0, + const int x1, const int y1, + const bool border_condition=false) { + return crop(x0,y0,0,0,x1,y1,depth-1,dim-1,border_condition); + } + + CImg get_crop(const int x0, const int y0, + const int x1, const int y1, + const bool border_condition=false) const { + return get_crop(x0,y0,0,0,x1,y1,depth-1,dim-1,border_condition); + } + + //! Get a rectangular part of the instance image. + /** + \param x0 = X-coordinate of the upper-left crop rectangle corner. + \param x1 = X-coordinate of the lower-right crop rectangle corner. + \param border_condition = determine the type of border condition if + some of the desired region is outside the image. + **/ + CImg& crop(const int x0, const int x1, const bool border_condition=false) { + return crop(x0,0,0,0,x1,height-1,depth-1,dim-1,border_condition); + } + + CImg get_crop(const int x0, const int x1, const bool border_condition=false) const { + return get_crop(x0,0,0,0,x1,height-1,depth-1,dim-1,border_condition); + } + + //! Autocrop an image, regarding of the specified backround value. + CImg& autocrop(const T value, const char *const axes="vzyx") { + if (is_empty()) return *this; + const int lmax = cimg::strlen(axes); + for (int l = 0; l get_autocrop(const T value, const char *const axes="vzyx") const { + return (+*this).autocrop(value,axes); + } + + //! Autocrop an image, regarding of the specified backround color. + CImg& autocrop(const T *const color, const char *const axes="zyx") { + if (is_empty()) return *this; + const int lmax = cimg::strlen(axes); + for (int l = 0; l get_autocrop(const T *const color, const char *const axes="zyx") const { + return (+*this).autocrop(color,axes); + } + + //! Autocrop an image, regarding of the specified backround color. + template CImg& autocrop(const CImg& color, const char *const axes="zyx") { + return get_autocrop(color,axes).transfer_to(*this); + } + + template CImg get_autocrop(const CImg& color, const char *const axes="zyx") const { + return get_autocrop(color.data,axes); + } + + //! Autocrop an image along specified axis, regarding of the specified backround value. + CImg& autocrop(const T value, const char axis) { + return get_autocrop(value,axis).transfer_to(*this); + } + + CImg get_autocrop(const T value, const char axis) const { + if (is_empty()) return *this; + CImg res; + const CImg coords = _get_autocrop(value,axis); + switch (cimg::uncase(axis)) { + case 'x' : { + const int x0 = coords[0], x1 = coords[1]; + if (x0>=0 && x1>=0) res = get_crop(x0,x1); + } break; + case 'y' : { + const int y0 = coords[0], y1 = coords[1]; + if (y0>=0 && y1>=0) res = get_crop(0,y0,width-1,y1); + } break; + case 'z' : { + const int z0 = coords[0], z1 = coords[1]; + if (z0>=0 && z1>=0) res = get_crop(0,0,z0,width-1,height-1,z1); + } break; + case 'v' : { + const int v0 = coords[0], v1 = coords[1]; + if (v0>=0 && v1>=0) res = get_crop(0,0,0,v0,width-1,height-1,depth-1,v1); + } break; + } + return res; + } + + //! Autocrop an image along specified axis, regarding of the specified backround color. + CImg& autocrop(const T *const color, const char axis) { + return get_autocrop(color,axis).transfer_to(*this); + } + + CImg get_autocrop(const T *const color, const char axis) const { + if (is_empty()) return *this; + CImg res; + switch (cimg::uncase(axis)) { + case 'x' : { + int x0 = width, x1 = -1; + cimg_forV(*this,k) { + const CImg coords = get_shared_channel(k)._get_autocrop(color[k],axis); + const int nx0 = coords[0], nx1 = coords[1]; + if (nx0>=0 && nx1>=0) { x0 = cimg::min(x0,nx0); x1 = cimg::max(x1,nx1); } + } + if (x0<=x1) res = get_crop(x0,x1); + } break; + case 'y' : { + int y0 = height, y1 = -1; + cimg_forV(*this,k) { + const CImg coords = get_shared_channel(k)._get_autocrop(color[k],axis); + const int ny0 = coords[0], ny1 = coords[1]; + if (ny0>=0 && ny1>=0) { y0 = cimg::min(y0,ny0); y1 = cimg::max(y1,ny1); } + } + if (y0<=y1) res = get_crop(0,y0,width-1,y1); + } break; + case 'z' : { + int z0 = depth, z1 = -1; + cimg_forV(*this,k) { + const CImg coords = get_shared_channel(k)._get_autocrop(color[k],axis); + const int nz0 = coords[0], nz1 = coords[1]; + if (nz0>=0 && nz1>=0) { z0 = cimg::min(z0,nz0); z1 = cimg::max(z1,nz1); } + } + if (z0<=z1) res = get_crop(0,0,z0,width-1,height-1,z1); + } break; + default : + throw CImgArgumentException("CImg<%s>::autocrop() : Invalid axis '%c', must be 'x','y' or 'z'.", + pixel_type(),axis); + } + return res; + } + + //! Autocrop an image along specified axis, regarding of the specified backround color. + template CImg& autocrop(const CImg& color, const char axis) { + return get_autocrop(color,axis).transfer_to(*this); + } + + template CImg get_autocrop(const CImg& color, const char axis) const { + return get_autocrop(color.data,axis); + } + + CImg _get_autocrop(const T value, const char axis) const { + CImg res; + int x0 = -1, y0 = -1, z0 = -1, v0 = -1, x1 = -1, y1 = -1, z1 = -1, v1 = -1; + switch (cimg::uncase(axis)) { + case 'x' : { + cimg_forX(*this,x) cimg_forYZV(*this,y,z,v) + if ((*this)(x,y,z,v)!=value) { x0 = x; x = dimx(); y = dimy(); z = dimz(); v = dimv(); } + if (x0>=0) { + for (int x = dimx()-1; x>=0; --x) cimg_forYZV(*this,y,z,v) + if ((*this)(x,y,z,v)!=value) { x1 = x; x = 0; y = dimy(); z = dimz(); v = dimv(); } + } + res = CImg::vector(x0,x1); + } break; + case 'y' : { + cimg_forY(*this,y) cimg_forXZV(*this,x,z,v) + if ((*this)(x,y,z,v)!=value) { y0 = y; x = dimx(); y = dimy(); z = dimz(); v = dimv(); } + if (y0>=0) { + for (int y = dimy()-1; y>=0; --y) cimg_forXZV(*this,x,z,v) + if ((*this)(x,y,z,v)!=value) { y1 = y; x = dimx(); y = 0; z = dimz(); v = dimv(); } + } + res = CImg::vector(y0,y1); + } break; + case 'z' : { + cimg_forZ(*this,z) cimg_forXYV(*this,x,y,v) + if ((*this)(x,y,z,v)!=value) { z0 = z; x = dimx(); y = dimy(); z = dimz(); v = dimv(); } + if (z0>=0) { + for (int z = dimz()-1; z>=0; --z) cimg_forXYV(*this,x,y,v) + if ((*this)(x,y,z,v)!=value) { z1 = z; x = dimx(); y = dimy(); z = 0; v = dimv(); } + } + res = CImg::vector(z0,z1); + } break; + case 'v' : { + cimg_forV(*this,v) cimg_forXYZ(*this,x,y,z) + if ((*this)(x,y,z,v)!=value) { v0 = v; x = dimx(); y = dimy(); z = dimz(); v = dimv(); } + if (v0>=0) { + for (int v = dimv()-1; v>=0; --v) cimg_forXYZ(*this,x,y,z) + if ((*this)(x,y,z,v)!=value) { v1 = v; x = dimx(); y = dimy(); z = dimz(); v = 0; } + } + res = CImg::vector(v0,v1); + } break; + default : + throw CImgArgumentException("CImg<%s>::autocrop() : unknow axis '%c', must be 'x','y','z' or 'v'", + pixel_type(),axis); + } + return res; + } + + //! Get a set of columns. + CImg& columns(const unsigned int x0, const unsigned int x1) { + return get_columns(x0,x1).transfer_to(*this); + } + + CImg get_columns(const unsigned int x0, const unsigned int x1) const { + return get_crop((int)x0,0,0,0,(int)x1,dimy()-1,dimz()-1,dimv()-1); + } + + //! Get one column. + CImg& column(const unsigned int x0) { + return columns(x0,x0); + } + + CImg get_column(const unsigned int x0) const { + return get_columns(x0,x0); + } + + //! Get a set of lines. + CImg& lines(const unsigned int y0, const unsigned int y1) { + return get_lines(y0,y1).transfer_to(*this); + } + + CImg get_lines(const unsigned int y0, const unsigned int y1) const { + return get_crop(0,(int)y0,0,0,dimx()-1,(int)y1,dimz()-1,dimv()-1); + } + + //! Get a line. + CImg& line(const unsigned int y0) { + return lines(y0,y0); + } + + CImg get_line(const unsigned int y0) const { + return get_lines(y0,y0); + } + + //! Get a set of slices. + CImg& slices(const unsigned int z0, const unsigned int z1) { + return get_slices(z0,z1).transfer_to(*this); + } + + CImg get_slices(const unsigned int z0, const unsigned int z1) const { + return get_crop(0,0,(int)z0,0,dimx()-1,dimy()-1,(int)z1,dimv()-1); + } + + //! Get a slice. + CImg& slice(const unsigned int z0) { + return slices(z0,z0); + } + + CImg get_slice(const unsigned int z0) const { + return get_slices(z0,z0); + } + + //! Get a set of channels. + CImg& channels(const unsigned int v0, const unsigned int v1) { + return get_channels(v0,v1).transfer_to(*this); + } + + CImg get_channels(const unsigned int v0, const unsigned int v1) const { + return get_crop(0,0,0,(int)v0,dimx()-1,dimy()-1,dimz()-1,(int)v1); + } + + //! Get a channel. + CImg& channel(const unsigned int v0) { + return channels(v0,v0); + } + + CImg get_channel(const unsigned int v0) const { + return get_channels(v0,v0); + } + + //! Get a shared-memory image referencing a set of points of the instance image. + CImg get_shared_points(const unsigned int x0, const unsigned int x1, + const unsigned int y0=0, const unsigned int z0=0, const unsigned int v0=0) { + const unsigned long beg = offset(x0,y0,z0,v0), end = offset(x1,y0,z0,v0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException("CImg<%s>::get_shared_points() : Cannot return a shared-memory subset (%u->%u,%u,%u,%u) from " + "a (%u,%u,%u,%u) image.", + pixel_type(),x0,x1,y0,z0,v0,width,height,depth,dim); + return CImg(data+beg,x1-x0+1,1,1,1,true); + } + + const CImg get_shared_points(const unsigned int x0, const unsigned int x1, + const unsigned int y0=0, const unsigned int z0=0, const unsigned int v0=0) const { + const unsigned long beg = offset(x0,y0,z0,v0), end = offset(x1,y0,z0,v0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException("CImg<%s>::get_shared_points() : Cannot return a shared-memory subset (%u->%u,%u,%u,%u) from " + "a (%u,%u,%u,%u) image.", + pixel_type(),x0,x1,y0,z0,v0,width,height,depth,dim); + return CImg(data+beg,x1-x0+1,1,1,1,true); + } + + //! Return a shared-memory image referencing a set of lines of the instance image. + CImg get_shared_lines(const unsigned int y0, const unsigned int y1, + const unsigned int z0=0, const unsigned int v0=0) { + const unsigned long beg = offset(0,y0,z0,v0), end = offset(0,y1,z0,v0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException("CImg<%s>::get_shared_lines() : Cannot return a shared-memory subset (0->%u,%u->%u,%u,%u) from " + "a (%u,%u,%u,%u) image.", + pixel_type(),width-1,y0,y1,z0,v0,width,height,depth,dim); + return CImg(data+beg,width,y1-y0+1,1,1,true); + } + + const CImg get_shared_lines(const unsigned int y0, const unsigned int y1, + const unsigned int z0=0, const unsigned int v0=0) const { + const unsigned long beg = offset(0,y0,z0,v0), end = offset(0,y1,z0,v0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException("CImg<%s>::get_shared_lines() : Cannot return a shared-memory subset (0->%u,%u->%u,%u,%u) from " + "a (%u,%u,%u,%u) image.", + pixel_type(),width-1,y0,y1,z0,v0,width,height,depth,dim); + return CImg(data+beg,width,y1-y0+1,1,1,true); + } + + //! Return a shared-memory image referencing one particular line (y0,z0,v0) of the instance image. + CImg get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int v0=0) { + return get_shared_lines(y0,y0,z0,v0); + } + + const CImg get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int v0=0) const { + return get_shared_lines(y0,y0,z0,v0); + } + + //! Return a shared memory image referencing a set of planes (z0->z1,v0) of the instance image. + CImg get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int v0=0) { + const unsigned long beg = offset(0,0,z0,v0), end = offset(0,0,z1,v0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException("CImg<%s>::get_shared_planes() : Cannot return a shared-memory subset (0->%u,0->%u,%u->%u,%u) from " + "a (%u,%u,%u,%u) image.", + pixel_type(),width-1,height-1,z0,z1,v0,width,height,depth,dim); + return CImg(data+beg,width,height,z1-z0+1,1,true); + } + + const CImg get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int v0=0) const { + const unsigned long beg = offset(0,0,z0,v0), end = offset(0,0,z1,v0); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException("CImg<%s>::get_shared_planes() : Cannot return a shared-memory subset (0->%u,0->%u,%u->%u,%u) from " + "a (%u,%u,%u,%u) image.", + pixel_type(),width-1,height-1,z0,z1,v0,width,height,depth,dim); + return CImg(data+beg,width,height,z1-z0+1,1,true); + } + + //! Return a shared-memory image referencing one plane (z0,v0) of the instance image. + CImg get_shared_plane(const unsigned int z0, const unsigned int v0=0) { + return get_shared_planes(z0,z0,v0); + } + + const CImg get_shared_plane(const unsigned int z0, const unsigned int v0=0) const { + return get_shared_planes(z0,z0,v0); + } + + //! Return a shared-memory image referencing a set of channels (v0->v1) of the instance image. + CImg get_shared_channels(const unsigned int v0, const unsigned int v1) { + const unsigned long beg = offset(0,0,0,v0), end = offset(0,0,0,v1); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException("CImg<%s>::get_shared_channels() : Cannot return a shared-memory subset (0->%u,0->%u,0->%u,%u->%u) from " + "a (%u,%u,%u,%u) image.", + pixel_type(),width-1,height-1,depth-1,v0,v1,width,height,depth,dim); + return CImg(data+beg,width,height,depth,v1-v0+1,true); + } + + const CImg get_shared_channels(const unsigned int v0, const unsigned int v1) const { + const unsigned long beg = offset(0,0,0,v0), end = offset(0,0,0,v1); + if (beg>end || beg>=size() || end>=size()) + throw CImgArgumentException("CImg<%s>::get_shared_channels() : Cannot return a shared-memory subset (0->%u,0->%u,0->%u,%u->%u) from " + "a (%u,%u,%u,%u) image.", + pixel_type(),width-1,height-1,depth-1,v0,v1,width,height,depth,dim); + return CImg(data+beg,width,height,depth,v1-v0+1,true); + } + + //! Return a shared-memory image referencing one channel v0 of the instance image. + CImg get_shared_channel(const unsigned int v0) { + return get_shared_channels(v0,v0); + } + + const CImg get_shared_channel(const unsigned int v0) const { + return get_shared_channels(v0,v0); + } + + //! Return a shared version of the instance image. + CImg get_shared() { + return CImg(data,width,height,depth,dim,true); + } + + const CImg get_shared() const { + return CImg(data,width,height,depth,dim,true); + } + + //! Return a 2D representation of a 3D image, with three slices. + CImg& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0, + const int dx=-100, const int dy=-100, const int dz=-100) { + return get_projections2d(x0,y0,z0,dx,dy,dz).transfer_to(*this); + } + + CImg get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0, + const int dx=-100, const int dy=-100, const int dz=-100) const { + if (is_empty()) return *this; + const unsigned int + nx0 = (x0>=width)?width-1:x0, + ny0 = (y0>=height)?height-1:y0, + nz0 = (z0>=depth)?depth-1:z0; + CImg + imgxy(width,height,1,dim), + imgzy(depth,height,1,dim), + imgxz(width,depth,1,dim); + { cimg_forXYV(*this,x,y,k) imgxy(x,y,k) = (*this)(x,y,nz0,k); } + { cimg_forYZV(*this,y,z,k) imgzy(z,y,k) = (*this)(nx0,y,z,k); } + { cimg_forXZV(*this,x,z,k) imgxz(x,z,k) = (*this)(x,ny0,z,k); } + imgxy.resize(dx,dy,1,dim,1); + imgzy.resize(dz,dy,1,dim,1); + imgxz.resize(dx,dz,1,dim,1); + return CImg(imgxy.width+imgzy.width,imgxy.height+imgxz.height,1,dim,0). + draw_image(imgxy).draw_image(imgxy.width,imgzy).draw_image(0,imgxy.height,imgxz); + } + + //! Compute the image histogram. + /** + The histogram H of an image I is a 1D-function where H(x) is the number of + occurences of the value x in I. + \param nblevels = Number of different levels of the computed histogram. + For classical images, this value is 256. You should specify more levels + if you are working with CImg or images with high range of pixel values. + \param val_min = Minimum value considered for the histogram computation. All pixel values lower than val_min + won't be counted. + \param val_max = Maximum value considered for the histogram computation. All pixel values higher than val_max + won't be counted. + \note If val_min==val_max==0 (default values), the function first estimates the minimum and maximum + pixel values of the current image, then uses these values for the histogram computation. + \result The histogram is returned as a 1D CImg image H, having a size of (nblevels,1,1,1) such that + H(0) and H(nblevels-1) are respectively equal to the number of occurences of the values val_min and val_max in I. + \note Histogram computation always returns a 1D function. Histogram of multi-valued (such as color) images + are not multi-dimensional. + **/ + CImg& histogram(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) { + return get_histogram(nblevels,val_min,val_max).transfer_to(*this); + } + + CImg get_histogram(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) const { + if (is_empty()) return CImg(); + if (!nblevels) + throw CImgArgumentException("CImg<%s>::get_histogram() : Can't compute an histogram with 0 levels", + pixel_type()); + T vmin = val_min, vmax = val_max; + CImg res(nblevels,1,1,1,0); + if (vmin>=vmax && vmin==0) vmin = minmax(vmax); + if (vmin=0 && pos<(int)nblevels) ++res[pos]; + } else res[0]+=size(); + return res; + } + + //! Compute the histogram-equalized version of the instance image. + /** + The histogram equalization is a classical image processing algorithm that enhances the image contrast + by expanding its histogram. + \param nblevels = Number of different levels of the computed histogram. + For classical images, this value is 256. You should specify more levels + if you are working with CImg or images with high range of pixel values. + \param val_min = Minimum value considered for the histogram computation. All pixel values lower than val_min + won't be changed. + \param val_max = Maximum value considered for the histogram computation. All pixel values higher than val_max + won't be changed. + \note If val_min==val_max==0 (default values), the function acts on all pixel values of the image. + \return A new image with same size is returned, where pixels have been equalized. + **/ + CImg& equalize(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) { + if (is_empty()) return *this; + T vmin = val_min, vmax = val_max; + if (vmin==vmax && vmin==0) vmin = minmax(vmax); + if (vmin hist = get_histogram(nblevels,vmin,vmax); + float cumul = 0; + cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos]=cumul; } + cimg_for(*this,ptr,T) { + const int pos = (unsigned int)((*ptr-vmin)*(nblevels-1)/(vmax-vmin)); + if (pos>=0 && pos<(int)nblevels) *ptr = (T)(vmin + (vmax-vmin)*hist[pos]/size()); + } + } + return *this; + } + + CImg get_equalize(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) const { + return (+*this).equalize(nblevels,val_min,val_max); + } + + //! Get a label map of disconnected regions with same intensities. + CImg& label_regions() { + return get_label_regions().transfer_to(*this); + } + + CImg get_label_regions() const { +#define _cimg_get_label_test(p,q) { \ + flag = true; \ + const T *ptr1 = ptr(x,y) + siz, *ptr2 = ptr(p,q) + siz; \ + for (unsigned int i = dim; flag && i; --i) { ptr1-=wh; ptr2-=wh; flag = (*ptr1==*ptr2); } \ +} + if (depth>1) + throw CImgInstanceException("CImg<%s>::label_regions() : Instance image must be a 2D image"); + CImg res(width,height,depth,1,0); + unsigned int label = 1; + const unsigned int wh = width*height, siz = width*height*dim; + const int W1 = dimx()-1, H1 = dimy()-1; + bool flag; + cimg_forXY(*this,x,y) { + bool done = false; + if (y) { + _cimg_get_label_test(x,y-1); + if (flag) { + const unsigned int lab = (res(x,y) = res(x,y-1)); + done = true; + if (x && res(x-1,y)!=lab) { + _cimg_get_label_test(x-1,y); + if (flag) { + const unsigned int lold = res(x-1,y), *const cptr = res.ptr(x,y); + for (unsigned int *ptr = res.ptr(); ptr=0; --y) for (int x=W1; x>=0; --x) { + bool done = false; + if (ycptr; --ptr) if (*ptr==lold) *ptr = lab; + } + } + } + } + if (x1), this function computes the L1,L2 or Linf norm of each + vector-valued pixel. + \param norm_type = Type of the norm being computed (1 = L1, 2 = L2, -1 = Linf). + \return A scalar-valued image CImg with size (dimx(),dimy(),dimz(),1), where each pixel is the norm + of the corresponding pixels in the original vector-valued image. + **/ + CImg& pointwise_norm(int norm_type=2) { + return get_pointwise_norm(norm_type).transfer_to(*this); + } + + CImg get_pointwise_norm(int norm_type=2) const { + if (is_empty()) return *this; + if (dim==1) return get_abs(); + CImg res(width,height,depth); + switch (norm_type) { + case -1 : { // Linf norm + cimg_forXYZ(*this,x,y,z) { + Tfloat n = 0; cimg_forV(*this,v) { + const Tfloat tmp = (Tfloat)cimg::abs((*this)(x,y,z,v)); + if (tmp>n) n=tmp; res(x,y,z) = n; + } + } + } break; + case 1 : { // L1 norm + cimg_forXYZ(*this,x,y,z) { + Tfloat n = 0; cimg_forV(*this,v) n+=cimg::abs((*this)(x,y,z,v)); res(x,y,z) = n; + } + } break; + default : { // L2 norm + cimg_forXYZ(*this,x,y,z) { + Tfloat n = 0; cimg_forV(*this,v) n+=(*this)(x,y,z,v)*(*this)(x,y,z,v); res(x,y,z) = (Tfloat)cimg_std::sqrt((double)n); + } + } + } + return res; + } + + //! Compute the image of normalized vectors. + /** + When dealing with vector-valued images (i.e images with dimv()>1), this function return the image of normalized vectors + (unit vectors). Null vectors are unchanged. The L2-norm is computed for the normalization. + \return A new vector-valued image with same size, where each vector-valued pixels have been normalized. + **/ + CImg& pointwise_orientation() { + cimg_forXYZ(*this,x,y,z) { + float n = 0; + cimg_forV(*this,v) n+=(float)((*this)(x,y,z,v)*(*this)(x,y,z,v)); + n = (float)cimg_std::sqrt(n); + if (n>0) cimg_forV(*this,v) (*this)(x,y,z,v) = (T)((*this)(x,y,z,v)/n); + else cimg_forV(*this,v) (*this)(x,y,z,v) = 0; + } + return *this; + } + + CImg get_pointwise_orientation() const { + if (is_empty()) return *this; + return CImg(*this,false).pointwise_orientation(); + } + + //! Split image into a list. + CImgList get_split(const char axis, const unsigned int nb=0) const { + if (is_empty()) return CImgList(); + CImgList res; + switch (cimg::uncase(axis)) { + case 'x' : { + if (nb>width) + throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'x' into %u images.", + pixel_type(),width,height,depth,dim,data,nb); + res.assign(nb?nb:width); + const unsigned int delta = (unsigned int)cimg::round((float)width/res.size,1); + unsigned int l, x; + for (l = 0, x = 0; lheight) + throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'y' into %u images.", + pixel_type(),width,height,depth,dim,data,nb); + res.assign(nb?nb:height); + const unsigned int delta = (unsigned int)cimg::round((float)height/res.size,1); + unsigned int l, y; + for (l = 0, y = 0; ldepth) + throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'z' into %u images.", + pixel_type(),width,height,depth,dim,data,nb); + res.assign(nb?nb:depth); + const unsigned int delta = (unsigned int)cimg::round((float)depth/res.size,1); + unsigned int l, z; + for (l = 0, z = 0; ldim) + throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'v' into %u images.", + pixel_type(),width,height,depth,dim,data,nb); + res.assign(nb?nb:dim); + const unsigned int delta = (unsigned int)cimg::round((float)dim/res.size,1); + unsigned int l, v; + for (l = 0, v = 0; l::get_split() : Unknow axis '%c', must be 'x','y','z' or 'v'", + pixel_type(),axis); + } + return res; + } + + // Split image into a list of vectors, according to a given splitting value. + CImgList get_split(const T value, const bool keep_values, const bool shared) const { + CImgList res; + const T *ptr0 = data, *const ptr_end = data + size(); + while (ptr0(ptr0,1,siz0,1,1,shared)); + ptr0 = ptr1; + while (ptr1(ptr0,1,siz1,1,1,shared),~0U,shared); + ptr0 = ptr1; + } + return res; + } + + //! Append an image to another one. + CImg& append(const CImg& img, const char axis, const char align='p') { + if (!img) return *this; + if (is_empty()) return (*this=img); + return get_append(img,axis,align).transfer_to(*this); + } + + CImg get_append(const CImg& img, const char axis, const char align='p') const { + if (!img) return *this; + if (is_empty()) return img; + CImgList temp(2); + temp[0].width = width; temp[0].height = height; temp[0].depth = depth; + temp[0].dim = dim; temp[0].data = data; + temp[1].width = img.width; temp[1].height = img.height; temp[1].depth = img.depth; + temp[1].dim = img.dim; temp[1].data = img.data; + const CImg res = temp.get_append(axis,align); + temp[0].width = temp[0].height = temp[0].depth = temp[0].dim = 0; temp[0].data = 0; + temp[1].width = temp[1].height = temp[1].depth = temp[1].dim = 0; temp[1].data = 0; + return res; + } + + //! Compute the list of images, corresponding to the XY-gradients of an image. + /** + \param scheme = Numerical scheme used for the gradient computation : + - -1 = Backward finite differences + - 0 = Centered finite differences + - 1 = Forward finite differences + - 2 = Using Sobel masks + - 3 = Using rotation invariant masks + - 4 = Using Deriche recusrsive filter. + **/ + CImgList get_gradient(const char *const axes=0, const int scheme=3) const { + CImgList grad(2,width,height,depth,dim); + bool threed = false; + if (axes) { + for (unsigned int a = 0; axes[a]; ++a) { + const char axis = cimg::uncase(axes[a]); + switch (axis) { + case 'x' : case 'y' : break; + case 'z' : threed = true; break; + default : + throw CImgArgumentException("CImg<%s>::get_gradient() : Unknown specified axis '%c'.", + pixel_type(),axis); + } + } + } else threed = (depth>1); + if (threed) { + grad.insert(1); grad[2].assign(width,height,depth,dim); + switch (scheme) { // Compute 3D gradient + case -1 : { // backward finite differences + CImg_3x3x3(I,T); + cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) { + grad[0](x,y,z,k) = (Tfloat)Iccc - Ipcc; + grad[1](x,y,z,k) = (Tfloat)Iccc - Icpc; + grad[2](x,y,z,k) = (Tfloat)Iccc - Iccp; + } + } break; + case 1 : { // forward finite differences + CImg_2x2x2(I,T); + cimg_forV(*this,k) cimg_for2x2x2(*this,x,y,z,k,I) { + grad[0](x,y,z,k) = (Tfloat)Incc - Iccc; + grad[1](x,y,z,k) = (Tfloat)Icnc - Iccc; + grad[2](x,y,z,k) = (Tfloat)Iccn - Iccc; + } + } break; + case 4 : { // using Deriche filter with low standard variation + grad[0] = get_deriche(0,1,'x'); + grad[1] = get_deriche(0,1,'y'); + grad[2] = get_deriche(0,1,'z'); + } break; + default : { // central finite differences + CImg_3x3x3(I,T); + cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) { + grad[0](x,y,z,k) = 0.5f*((Tfloat)Incc - Ipcc); + grad[1](x,y,z,k) = 0.5f*((Tfloat)Icnc - Icpc); + grad[2](x,y,z,k) = 0.5f*((Tfloat)Iccn - Iccp); + } + } + } + } else switch (scheme) { // Compute 2D-gradient + case -1 : { // backward finite differences + CImg_3x3(I,T); + cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) { + grad[0](x,y,z,k) = (Tfloat)Icc - Ipc; + grad[1](x,y,z,k) = (Tfloat)Icc - Icp; + } + } break; + case 1 : { // forward finite differences + CImg_2x2(I,T); + cimg_forZV(*this,z,k) cimg_for2x2(*this,x,y,z,k,I) { + grad[0](x,y,0,k) = (Tfloat)Inc - Icc; + grad[1](x,y,z,k) = (Tfloat)Icn - Icc; + } + } break; + case 2 : { // using Sobel mask + CImg_3x3(I,T); + const Tfloat a = 1, b = 2; + cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) { + grad[0](x,y,z,k) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn; + grad[1](x,y,z,k) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn; + } + } break; + case 3 : { // using rotation invariant mask + CImg_3x3(I,T); + const Tfloat a = (Tfloat)(0.25f*(2-cimg_std::sqrt(2.0f))), b = (Tfloat)(0.5f*(cimg_std::sqrt(2.0f)-1)); + cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) { + grad[0](x,y,z,k) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn; + grad[1](x,y,z,k) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn; + } + } break; + case 4 : { // using Deriche filter with low standard variation + grad[0] = get_deriche(0,1,'x'); + grad[1] = get_deriche(0,1,'y'); + } break; + default : { // central finite differences + CImg_3x3(I,T); + cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) { + grad[0](x,y,z,k) = 0.5f*((Tfloat)Inc - Ipc); + grad[1](x,y,z,k) = 0.5f*((Tfloat)Icn - Icp); + } + } + } + if (!axes) return grad; + CImgList res; + for (unsigned int l = 0; axes[l]; ++l) { + const char axis = cimg::uncase(axes[l]); + switch (axis) { + case 'x' : res.insert(grad[0]); break; + case 'y' : res.insert(grad[1]); break; + case 'z' : res.insert(grad[2]); break; + } + } + grad.assign(); + return res; + } + + //! Compute the structure tensor field of an image. + CImg& structure_tensor(const bool central_scheme=false) { + return get_structure_tensor(central_scheme).transfer_to(*this); + } + + CImg get_structure_tensor(const bool central_scheme=false) const { + if (is_empty()) return *this; + CImg res; + if (depth>1) { // 3D version + res.assign(width,height,depth,6,0); + CImg_3x3x3(I,T); + if (central_scheme) cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) { // classical central finite differences + const Tfloat + ix = 0.5f*((Tfloat)Incc - Ipcc), + iy = 0.5f*((Tfloat)Icnc - Icpc), + iz = 0.5f*((Tfloat)Iccn - Iccp); + res(x,y,z,0)+=ix*ix; + res(x,y,z,1)+=ix*iy; + res(x,y,z,2)+=ix*iz; + res(x,y,z,3)+=iy*iy; + res(x,y,z,4)+=iy*iz; + res(x,y,z,5)+=iz*iz; + } else cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) { // Precise forward/backward finite differences + const Tfloat + ixf = (Tfloat)Incc - Iccc, ixb = (Tfloat)Iccc - Ipcc, + iyf = (Tfloat)Icnc - Iccc, iyb = (Tfloat)Iccc - Icpc, + izf = (Tfloat)Iccn - Iccc, izb = (Tfloat)Iccc - Iccp; + res(x,y,z,0) += 0.5f*(ixf*ixf + ixb*ixb); + res(x,y,z,1) += 0.25f*(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb); + res(x,y,z,2) += 0.25f*(ixf*izf + ixf*izb + ixb*izf + ixb*izb); + res(x,y,z,3) += 0.5f*(iyf*iyf + iyb*iyb); + res(x,y,z,4) += 0.25f*(iyf*izf + iyf*izb + iyb*izf + iyb*izb); + res(x,y,z,5) += 0.5f*(izf*izf + izb*izb); + } + } else { // 2D version + res.assign(width,height,depth,3,0); + CImg_3x3(I,T); + if (central_scheme) cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) { // classical central finite differences + const Tfloat + ix = 0.5f*((Tfloat)Inc - Ipc), + iy = 0.5f*((Tfloat)Icn - Icp); + res(x,y,0,0)+=ix*ix; + res(x,y,0,1)+=ix*iy; + res(x,y,0,2)+=iy*iy; + } else cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) { // Precise forward/backward finite differences + const Tfloat + ixf = (Tfloat)Inc - Icc, ixb = (Tfloat)Icc - Ipc, + iyf = (Tfloat)Icn - Icc, iyb = (Tfloat)Icc - Icp; + res(x,y,0,0) += 0.5f*(ixf*ixf+ixb*ixb); + res(x,y,0,1) += 0.25f*(ixf*iyf+ixf*iyb+ixb*iyf+ixb*iyb); + res(x,y,0,2) += 0.5f*(iyf*iyf+iyb*iyb); + } + } + return res; + } + + //! Get components of the Hessian matrix of an image. + CImgList get_hessian(const char *const axes=0) const { + const char *naxes = axes, *const def_axes2d = "xxxyyy", *const def_axes3d = "xxxyxzyyyzzz"; + if (!axes) naxes = depth>1?def_axes3d:def_axes2d; + CImgList res; + const int lmax = cimg::strlen(naxes); + if (lmax%2) + throw CImgArgumentException("CImg<%s>::get_hessian() : Incomplete parameter axes = '%s'.", + pixel_type(),naxes); + res.assign(lmax/2,width,height,depth,dim); + if (!cimg::strcasecmp(naxes,def_axes3d)) { // Default 3D version + CImg_3x3x3(I,T); + cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) { + res[0](x,y,z,k) = (Tfloat)Ipcc + Incc - 2*Iccc; // Ixx + res[1](x,y,z,k) = 0.25f*((Tfloat)Ippc + Innc - Ipnc - Inpc); // Ixy + res[2](x,y,z,k) = 0.25f*((Tfloat)Ipcp + Incn - Ipcn - Incp); // Ixz + res[3](x,y,z,k) = (Tfloat)Icpc + Icnc - 2*Iccc; // Iyy + res[4](x,y,z,k) = 0.25f*((Tfloat)Icpp + Icnn - Icpn - Icnp); // Iyz + res[5](x,y,z,k) = (Tfloat)Iccn + Iccp - 2*Iccc; // Izz + } + } else if (!cimg::strcasecmp(naxes,def_axes2d)) { // Default 2D version + CImg_3x3(I,T); + cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) { + res[0](x,y,0,k) = (Tfloat)Ipc + Inc - 2*Icc; // Ixx + res[1](x,y,0,k) = 0.25f*((Tfloat)Ipp + Inn - Ipn - Inp); // Ixy + res[2](x,y,0,k) = (Tfloat)Icp + Icn - 2*Icc; // Iyy + } + } else for (int l = 0; laxis2) cimg::swap(axis1,axis2); + bool valid_axis = false; + if (axis1=='x' && axis2=='x') { // Ixx + valid_axis = true; CImg_3x3(I,T); + cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = (Tfloat)Ipc + Inc - 2*Icc; + } + else if (axis1=='x' && axis2=='y') { // Ixy + valid_axis = true; CImg_3x3(I,T); + cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = 0.25f*((Tfloat)Ipp + Inn - Ipn - Inp); + } + else if (axis1=='x' && axis2=='z') { // Ixz + valid_axis = true; CImg_3x3x3(I,T); + cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = 0.25f*((Tfloat)Ipcp + Incn - Ipcn - Incp); + } + else if (axis1=='y' && axis2=='y') { // Iyy + valid_axis = true; CImg_3x3(I,T); + cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = (Tfloat)Icp + Icn - 2*Icc; + } + else if (axis1=='y' && axis2=='z') { // Iyz + valid_axis = true; CImg_3x3x3(I,T); + cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = 0.25f*((Tfloat)Icpp + Icnn - Icpn - Icnp); + } + else if (axis1=='z' && axis2=='z') { // Izz + valid_axis = true; CImg_3x3x3(I,T); + cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = (Tfloat)Iccn + Iccp - 2*Iccc; + } + else if (!valid_axis) throw CImgArgumentException("CImg<%s>::get_hessian() : Invalid parameter axes = '%s'.", + pixel_type(),naxes); + } + return res; + } + + //! Compute distance function from 0-valued isophotes by the application of an Hamilton-Jacobi PDE. + CImg& distance_hamilton(const unsigned int nb_iter, const float band_size=0, const float precision=0.5f) { + if (is_empty()) return *this; + CImg veloc(*this); + for (unsigned int iter = 0; iter1) { // 3D version + CImg_3x3x3(I,T); + cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) if (band_size<=0 || cimg::abs(Iccc)0?(Tfloat)Incc - Iccc:(Tfloat)Iccc - Ipcc, + iy = gy*sgn>0?(Tfloat)Icnc - Iccc:(Tfloat)Iccc - Icpc, + iz = gz*sgn>0?(Tfloat)Iccn - Iccc:(Tfloat)Iccc - Iccp, + ng = 1e-5f + (Tfloat)cimg_std::sqrt(gx*gx + gy*gy + gz*gz), + ngx = gx/ng, + ngy = gy/ng, + ngz = gz/ng; + veloc(x,y,z,k) = sgn*(ngx*ix + ngy*iy + ngz*iz - 1); + } + } else { // 2D version + CImg_3x3(I,T); + cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) if (band_size<=0 || cimg::abs(Icc)0?(Tfloat)Inc - Icc:(Tfloat)Icc - Ipc, + iy = gy*sgn>0?(Tfloat)Icn - Icc:(Tfloat)Icc - Icp, + ng = 1e-5f + (Tfloat)cimg_std::sqrt(gx*gx + gy*gy), + ngx = gx/ng, + ngy = gy/ng; + veloc(x,y,k) = sgn*(ngx*ix + ngy*iy - 1); + } + } + float m, M = (float)veloc.maxmin(m), xdt = precision/(float)cimg::max(cimg::abs(m),cimg::abs(M)); + *this+=(veloc*=xdt); + } + return *this; + } + + CImg get_distance_hamilton(const unsigned int nb_iter, const float band_size=0, const float precision=0.5f) const { + return CImg(*this,false).distance_hamilton(nb_iter,band_size,precision); + } + + //! Compute the Euclidean distance map to a shape of specified isovalue. + CImg& distance(const T isovalue, + const float sizex=1, const float sizey=1, const float sizez=1, + const bool compute_sqrt=true) { + return get_distance(isovalue,sizex,sizey,sizez,compute_sqrt).transfer_to(*this); + } + + CImg get_distance(const T isovalue, + const float sizex=1, const float sizey=1, const float sizez=1, + const bool compute_sqrt=true) const { + if (is_empty()) return *this; + const int dx = dimx(), dy = dimy(), dz = dimz(); + CImg res(dx,dy,dz,dim); + const float maxdist = (float)cimg_std::sqrt((float)dx*dx + dy*dy + dz*dz); + cimg_forV(*this,k) { + bool is_isophote = false; + + if (depth>1) { // 3D version + { cimg_forYZ(*this,y,z) { + if ((*this)(0,y,z,k)==isovalue) { is_isophote = true; res(0,y,z,k) = 0; } else res(0,y,z,k) = maxdist; + for (int x = 1; x=0; --x) if (res(x+1,y,z,k)::max()); continue; } + CImg tmp(cimg::max(dy,dz)); + CImg s(tmp.width), t(s.width); + { cimg_forXZ(*this,x,z) { + { cimg_forY(*this,y) tmp[y] = res(x,y,z,k); } + int q = s[0] = t[0] = 0; + { for (int y = 1; y=0 && _distance_f(t[q],s[q],cimg::sqr(tmp[s[q]]),sizey)>_distance_f(t[q],y,val2,sizey)) --q; + if (q<0) { q = 0; s[0] = y; } + else { + const int w = 1 + _distance_sep(s[q],y,(int)cimg::sqr(tmp[s[q]]),(int)val2,sizey); + if (w=0; --y) { + res(x,y,z,k) = _distance_f(y,s[q],cimg::sqr(tmp[s[q]]),sizey); + if (y==t[q]) --q; + }} + }} + { cimg_forXY(*this,x,y) { + { cimg_forZ(*this,z) tmp[z] = res(x,y,z,k); } + int q = s[0] = t[0] = 0; + { for (int z = 1; z=0 && _distance_f(t(q),s[q],tmp[s[q]],sizez)>_distance_f(t[q],z,tmp[z],sizez)) --q; + if (q<0) { q = 0; s[0] = z; } + else { + const int w = 1 + _distance_sep(s[q],z,(int)tmp[s[q]],(int)val,sizez); + if (w=0; --z) { + const float val = _distance_f(z,s[q],tmp[s[q]],sizez); + res(x,y,z,k) = compute_sqrt?(float)cimg_std::sqrt(val):val; + if (z==t[q]) --q; + }} + }} + } else { // 2D version (with small optimizations) + cimg_forX(*this,x) { + const T *ptrs = ptr(x,0,0,k); + float *ptrd = res.ptr(x,0,0,k), d = *ptrd = *ptrs==isovalue?(is_isophote=true),0:maxdist; + for (int y = 1; y=0; --y) { ptrd-=width; if (d<*ptrd) *ptrd = (d+=sizey); else d = *ptrd; }} + } + if (!is_isophote) { res.get_shared_channel(k).fill(cimg::type::max()); continue; } + CImg tmp(dx); + CImg s(dx), t(dx); + cimg_forY(*this,y) { + float *ptmp = tmp.ptr(); + cimg_std::memcpy(ptmp,res.ptr(0,y,0,k),sizeof(float)*dx); + int q = s[0] = t[0] = 0; + for (int x = 1; x=0 && _distance_f(t[q],s[q],cimg::sqr(tmp[s[q]]),sizex)>_distance_f(t[q],x,val2,sizex)) --q; + if (q<0) { q = 0; s[0] = x; } + else { + const int w = 1 + _distance_sep(s[q],x,(int)cimg::sqr(tmp[s[q]]),(int)val2,sizex); + if (w=0; --x) { + const float val = _distance_f(x,s[q],cimg::sqr(tmp[s[q]]),sizex); + *(--pres) = compute_sqrt?(float)cimg_std::sqrt(val):val; + if (x==t[q]) --q; + }} + } + } + } + return res; + } + + static float _distance_f(const int x, const int i, const float gi2, const float fact) { + const float xmi = fact*((float)x - i); + return xmi*xmi + gi2; + } + static int _distance_sep(const int i, const int u, const int gi2, const int gu2, const float fact) { + const float fact2 = fact*fact; + return (int)(fact2*(u*u - i*i) + gu2 - gi2)/(int)(2*fact2*(u - i)); + } + + //! Compute minimal path in a graph, using the Dijkstra algorithm. + /** + \param distance An object having operator()(unsigned int i, unsigned int j) which returns distance between two nodes (i,j). + \param nb_nodes Number of graph nodes. + \param starting_node Indice of the starting node. + \param ending_node Indice of the ending node (set to ~0U to ignore ending node). + \param previous Array that gives the previous node indice in the path to the starting node (optional parameter). + \return Array of distances of each node to the starting node. + **/ + template + static CImg dijkstra(const tf& distance, const unsigned int nb_nodes, + const unsigned int starting_node, const unsigned int ending_node, + CImg& previous) { + + CImg dist(1,nb_nodes,1,1,cimg::type::max()); + dist(starting_node) = 0; + previous.assign(1,nb_nodes,1,1,(t)-1); + previous(starting_node) = (t)starting_node; + CImg Q(nb_nodes); + cimg_forX(Q,u) Q(u) = u; + cimg::swap(Q(starting_node),Q(0)); + unsigned int sizeQ = nb_nodes; + while (sizeQ) { + // Update neighbors from minimal vertex + const unsigned int umin = Q(0); + if (umin==ending_node) sizeQ = 0; + else { + const T dmin = dist(umin); + const T infty = cimg::type::max(); + for (unsigned int q=1; qdist(Q(left))) || (rightdist(Q(right)));) { + if (right + static CImg dijkstra(const tf& distance, const unsigned int nb_nodes, + const unsigned int starting_node, const unsigned int ending_node=~0U) { + CImg foo; + return dijkstra(distance,nb_nodes,starting_node,ending_node,foo); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm. + /** + Instance image corresponds to the adjacency matrix of the graph. + \param starting_node Indice of the starting node. + \param previous Array that gives the previous node indice in the path to the starting node (optional parameter). + \return Array of distances of each node to the starting node. + **/ + template + CImg& dijkstra(const unsigned int starting_node, const unsigned int ending_node, CImg& previous) { + return get_dijkstra(starting_node,ending_node,previous).transfer_to(*this); + } + + template + CImg get_dijkstra(const unsigned int starting_node, const unsigned int ending_node, CImg& previous) const { + if (width!=height || depth!=1 || dim!=1) + throw CImgInstanceException("CImg<%s>::dijkstra() : Instance image (%u,%u,%u,%u,%p) is not a graph adjacency matrix", + pixel_type(),width,height,depth,dim,data); + return dijkstra(*this,width,starting_node,ending_node,previous); + } + + //! Return minimal path in a graph, using the Dijkstra algorithm. + CImg& dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) { + return get_dijkstra(starting_node,ending_node).transfer_to(*this); + } + + CImg get_dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) const { + CImg foo; + return get_dijkstra(starting_node,ending_node,foo); + } + + //@} + //------------------------------------- + // + //! \name Meshes and Triangulations + //@{ + //------------------------------------- + + //! Return a 3D centered cube. + template + static CImg cube3d(CImgList& primitives, const float size=100) { + const double s = size/2.0; + primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5); + return CImg(8,3,1,1, + -s,s,s,-s,-s,s,s,-s, + -s,-s,s,s,-s,-s,s,s, + -s,-s,-s,-s,s,s,s,s); + } + + //! Return a 3D centered cuboid. + template + static CImg cuboid3d(CImgList& primitives, const float sizex=200, + const float sizey=100, const float sizez=100) { + const double sx = sizex/2.0, sy = sizey/2.0, sz = sizez/2.0; + primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5); + return CImg(8,3,1,1, + -sx,sx,sx,-sx,-sx,sx,sx,-sx, + -sy,-sy,sy,sy,-sy,-sy,sy,sy, + -sz,-sz,-sz,-sz,sz,sz,sz,sz); + } + + //! Return a 3D centered cone. + template + static CImg cone3d(CImgList& primitives, const float radius=50, const float height=100, + const unsigned int subdivisions=24, const bool symetrize=false) { + primitives.assign(); + if (!subdivisions) return CImg(); + const double r = (double)radius, h = (double)height/2; + CImgList points(2,1,3,1,1, + 0.0,0.0,h, + 0.0,0.0,-h); + const float delta = 360.0f/subdivisions, nh = symetrize?0:-(float)h; + for (float angle = 0; angle<360; angle+=delta) { + const float a = (float)(angle*cimg::valuePI/180); + points.insert(CImg::vector((float)(r*cimg_std::cos(a)),(float)(r*cimg_std::sin(a)),nh)); + } + const unsigned int nbr = points.size-2; + for (unsigned int p = 0; p::vector(1,next,curr)). + insert(CImg::vector(0,curr,next)); + } + return points.get_append('x'); + } + + //! Return a 3D centered cylinder. + template + static CImg cylinder3d(CImgList& primitives, const float radius=50, const float height=100, + const unsigned int subdivisions=24) { + primitives.assign(); + if (!subdivisions) return CImg(); + const double r = (double)radius, h = (double)height/2; + CImgList points(2,1,3,1,1, + 0.0,0.0,-h, + 0.0,0.0,h); + + const float delta = 360.0f/subdivisions; + for (float angle = 0; angle<360; angle+=delta) { + const float a = (float)(angle*cimg::valuePI/180); + points.insert(CImg::vector((float)(r*cimg_std::cos(a)),(float)(r*cimg_std::sin(a)),-(float)h)); + points.insert(CImg::vector((float)(r*cimg_std::cos(a)),(float)(r*cimg_std::sin(a)),(float)h)); + } + const unsigned int nbr = (points.size-2)/2; + for (unsigned int p = 0; p::vector(0,next,curr)). + insert(CImg::vector(1,curr+1,next+1)). + insert(CImg::vector(curr,next,next+1,curr+1)); + } + return points.get_append('x'); + } + + //! Return a 3D centered torus. + template + static CImg torus3d(CImgList& primitives, const float radius1=100, const float radius2=30, + const unsigned int subdivisions1=24, const unsigned int subdivisions2=12) { + primitives.assign(); + if (!subdivisions1 || !subdivisions2) return CImg(); + CImgList points; + for (unsigned int v = 0; v::vector(x,y,z)); + } + } + for (unsigned int vv = 0; vv::vector(svv+nu,svv+uu,snv+uu)); + primitives.insert(CImg::vector(svv+nu,snv+uu,snv+nu)); + } + } + return points.get_append('x'); + } + + //! Return a 3D centered XY plane. + template + static CImg plane3d(CImgList& primitives, const float sizex=100, const float sizey=100, + const unsigned int subdivisionsx=3, const unsigned int subdivisionsy=3, + const bool double_sided=false) { + primitives.assign(); + if (!subdivisionsx || !subdivisionsy) return CImg(); + CImgList points; + const unsigned int w = subdivisionsx + 1, h = subdivisionsy + 1; + const float w2 = subdivisionsx/2.0f, h2 = subdivisionsy/2.0f, fx = (float)sizex/w, fy = (float)sizey/h; + for (unsigned int yy = 0; yy::vector(fx*(xx-w2),fy*(yy-h2),0)); + for (unsigned int y = 0; y::vector(off1,off4,off3,off2)); + if (double_sided) primitives.insert(CImg::vector(off1,off2,off3,off4)); + } + return points.get_append('x'); + } + + //! Return a 3D centered sphere. + template + static CImg sphere3d(CImgList& primitives, const float radius=50, const unsigned int subdivisions=3) { + + // Create initial icosahedron + primitives.assign(); + if (!subdivisions) return CImg(); + const double tmp = (1+cimg_std::sqrt(5.0f))/2, a = 1.0/cimg_std::sqrt(1+tmp*tmp), b = tmp*a; + CImgList points(12,1,3,1,1, b,a,0.0, -b,a,0.0, -b,-a,0.0, b,-a,0.0, a,0.0,b, a,0.0,-b, + -a,0.0,-b, -a,0.0,b, 0.0,b,a, 0.0,-b,a, 0.0,-b,-a, 0.0,b,-a); + primitives.assign(20,1,3,1,1, 4,8,7, 4,7,9, 5,6,11, 5,10,6, 0,4,3, 0,3,5, 2,7,1, 2,1,6, + 8,0,11, 8,11,1, 9,10,3, 9,2,10, 8,4,0, 11,0,5, 4,9,3, + 5,3,10, 7,8,1, 6,1,11, 7,2,9, 6,10,2); + + // Recurse subdivisions + for (unsigned int i = 0; i::vector(nx0,ny0,nz0)); i0 = points.size-1; } + if (i1<0) { points.insert(CImg::vector(nx1,ny1,nz1)); i1 = points.size-1; } + if (i2<0) { points.insert(CImg::vector(nx2,ny2,nz2)); i2 = points.size-1; } + primitives.remove(0); + primitives.insert(CImg::vector(p0,i0,i1)). + insert(CImg::vector((tf)i0,(tf)p1,(tf)i2)). + insert(CImg::vector((tf)i1,(tf)i2,(tf)p2)). + insert(CImg::vector((tf)i1,(tf)i0,(tf)i2)); + } + } + return points.get_append('x')*=radius; + } + + //! Return a 3D centered ellipsoid. + template + static CImg ellipsoid3d(CImgList& primitives, const CImg& tensor, + const unsigned int subdivisions=3) { + primitives.assign(); + if (!subdivisions) return CImg(); + typedef typename cimg::superset::type tfloat; + CImg S,V; + tensor.symmetric_eigen(S,V); + const tfloat l0 = S[0], l1 = S[1], l2 = S[2]; + CImg points = sphere(primitives,subdivisions); + cimg_forX(points,p) { + points(p,0) = (float)(points(p,0)*l0); + points(p,1) = (float)(points(p,1)*l1); + points(p,2) = (float)(points(p,2)*l2); + } + V.transpose(); + points = V*points; + return points; + } + + //! Return a 3D elevation object of the instance image. + template + CImg get_elevation3d(CImgList& primitives, CImgList& colors, const CImg& elevation) const { + primitives.assign(); + colors.assign(); + if (is_empty()) return *this; + if (depth>1) + throw CImgInstanceException("CImg<%s>::get_elevation3d() : Instance image (%u,%u,%u,%u,%p) is not a 2D image.", + pixel_type(),width,height,depth,dim,data); + if (!is_sameXY(elevation)) + throw CImgArgumentException("CImg<%s>::get_elevation3d() : Elevation image (%u,%u,%u,%u,%p) and instance image (%u,%u,%u,%u,%p) " + "have different sizes.",pixel_type(), + elevation.width,elevation.height,elevation.depth,elevation.dim,elevation.data, + width,height,depth,dim,data,pixel_type()); + float m, M = (float)maxmin(m); + if (M==m) ++M; + const unsigned int w = width + 1, h = height + 1; + CImg points(w*h,3); + cimg_forXY(*this,x,y) { + const int yw = y*w, xpyw = x + yw, xpyww = xpyw + w; + points(xpyw,0) = points(xpyw+1,0) = points(xpyww+1,0) = points(xpyww,0) = (float)x; + points(xpyw,1) = points(xpyw+1,1) = points(xpyww+1,1) = points(xpyww,1) = (float)y; + points(xpyw,2) = points(xpyw+1,2) = points(xpyww+1,2) = points(xpyww,2) = (float)elevation(x,y); + primitives.insert(CImg::vector(xpyw,xpyw+1,xpyww+1,xpyww)); + const unsigned char + r = (unsigned char)(((*this)(x,y,0) - m)*255/(M-m)), + g = dim>1?(unsigned char)(((*this)(x,y,1) - m)*255/(M-m)):r, + b = dim>2?(unsigned char)(((*this)(x,y,2) - m)*255/(M-m)):(dim>1?0:r); + colors.insert(CImg::vector((tc)r,(tc)g,(tc)b)); + } + return points; + } + + // Inner routine used by the Marching square algorithm. + template + static int _marching_squares_indice(const unsigned int edge, const CImg& indices1, const CImg& indices2, + const unsigned int x, const unsigned int nx) { + switch (edge) { + case 0 : return (int)indices1(x,0); + case 1 : return (int)indices1(nx,1); + case 2 : return (int)indices2(x,0); + case 3 : return (int)indices1(x,1); + } + return 0; + } + + //! Polygonize an implicit 2D function by the marching squares algorithm. + template + static CImg marching_squares(CImgList& primitives, const tfunc& func, const float isovalue, + const float x0, const float y0, + const float x1, const float y1, + const float resx, const float resy) { + static unsigned int edges[16] = { 0x0, 0x9, 0x3, 0xa, 0x6, 0xf, 0x5, 0xc, 0xc, 0x5, 0xf, 0x6, 0xa, 0x3, 0x9, 0x0 }; + static int segments[16][4] = { { -1,-1,-1,-1 }, { 0,3,-1,-1 }, { 0,1,-1,-1 }, { 1,3,-1,-1 }, + { 1,2,-1,-1 }, { 0,1,2,3 }, { 0,2,-1,-1 }, { 2,3,-1,-1 }, + { 2,3,-1,-1 }, { 0,2,-1,-1}, { 0,3,1,2 }, { 1,2,-1,-1 }, + { 1,3,-1,-1 }, { 0,1,-1,-1}, { 0,3,-1,-1}, { -1,-1,-1,-1 } }; + const unsigned int + nx = (unsigned int)((x1-x0+1)/resx), nxm1 = nx-1, + ny = (unsigned int)((y1-y0+1)/resy), nym1 = ny-1; + if (!nxm1 || !nym1) return CImg(); + + primitives.assign(); + CImgList points; + CImg indices1(nx,1,1,2,-1), indices2(nx,1,1,2); + CImg values1(nx), values2(nx); + float X = 0, Y = 0, nX = 0, nY = 0; + + // Fill first line with values + cimg_forX(values1,x) { values1(x) = (float)func(X,Y); X+=resx; } + + // Run the marching squares algorithm + Y = y0; nY = Y + resy; + for (unsigned int yi = 0, nyi = 1; yi::vector(Xi,Y)); + } + if ((edge&2) && indices1(nxi,1)<0) { + const float Yi = Y + (isovalue-val1)*resy/(val2-val1); + indices1(nxi,1) = points.size; + points.insert(CImg::vector(nX,Yi)); + } + if ((edge&4) && indices2(xi,0)<0) { + const float Xi = X + (isovalue-val3)*resx/(val2-val3); + indices2(xi,0) = points.size; + points.insert(CImg::vector(Xi,nY)); + } + if ((edge&8) && indices1(xi,1)<0) { + const float Yi = Y + (isovalue-val0)*resy/(val3-val0); + indices1(xi,1) = points.size; + points.insert(CImg::vector(X,Yi)); + } + + // Create segments + for (int *segment = segments[configuration]; *segment!=-1; ) { + const unsigned int p0 = *(segment++), p1 = *(segment++); + const tf + i0 = (tf)(_marching_squares_indice(p0,indices1,indices2,xi,nxi)), + i1 = (tf)(_marching_squares_indice(p1,indices1,indices2,xi,nxi)); + primitives.insert(CImg::vector(i0,i1)); + } + } + } + values1.swap(values2); + indices1.swap(indices2); + } + return points.get_append('x'); + } + + // Inner routine used by the Marching cube algorithm. + template + static int _marching_cubes_indice(const unsigned int edge, const CImg& indices1, const CImg& indices2, + const unsigned int x, const unsigned int y, const unsigned int nx, const unsigned int ny) { + switch (edge) { + case 0 : return indices1(x,y,0); + case 1 : return indices1(nx,y,1); + case 2 : return indices1(x,ny,0); + case 3 : return indices1(x,y,1); + case 4 : return indices2(x,y,0); + case 5 : return indices2(nx,y,1); + case 6 : return indices2(x,ny,0); + case 7 : return indices2(x,y,1); + case 8 : return indices1(x,y,2); + case 9 : return indices1(nx,y,2); + case 10 : return indices1(nx,ny,2); + case 11 : return indices1(x,ny,2); + } + return 0; + } + + //! Polygonize an implicit function + // This function uses the Marching Cubes Tables published on the web page : + // http://astronomy.swin.edu.au/~pbourke/modelling/polygonise/ + template + static CImg marching_cubes(CImgList& primitives, + const tfunc& func, const float isovalue, + const float x0, const float y0, const float z0, + const float x1, const float y1, const float z1, + const float resx, const float resy, const float resz, + const bool invert_faces=false) { + + static unsigned int edges[256] = { + 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000 }; + + static int triangles[256][16] = + {{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, { 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1 }, + { 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, { 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1 }, + { 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1 }, { 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1 }, + { 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, { 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, { 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1 }, + { 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, { 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1 }, + { 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1 }, { 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1 }, + { 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, { 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, { 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1 }, { 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1 }, + { 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1 }, { 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1 }, + { 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1 }, { 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1 }, { 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1 }, + { 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1 }, { 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1 }, + { 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, { 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1 }, + { 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, + { 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1 }, { 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, { 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1 }, + { 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1 }, + { 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1 }, { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1 }, + { 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, { 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1 }, + { 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1 }, { 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1 }, + { 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, { 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1 }, + { 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1 }, { 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1 }, + { 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, + { 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1 }, + { 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1 }, + { 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, { 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1 }, + { 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1 }, { 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1 }, + { 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, { 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, { 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1 }, + { 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1 }, { 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1 }, + { 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, { 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, { 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1 }, + { 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1 }, { 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1 }, + { 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1 }, { 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1 }, { 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, { 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1 }, + { 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1 }, { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1 }, + { 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, { 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1 }, + { 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1 }, { 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1 }, + { 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1 }, + { 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1 }, { 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1 }, + { 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, { 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1 }, { 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1 }, { 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1 }, { 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, { 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1 }, + { 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, { 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1 }, + { 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1 }, { 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1 }, + { 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1 }, + { 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1 }, { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1 }, + { 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1 }, { 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1 }, + { 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1 }, { 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1 }, + { 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, { 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1 }, + { 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1 }, { 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1 }, { 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1 }, + { 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1 }, { 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1 }, + { 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1 }, { 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1 }, { 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1 }, { 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1 }, + { 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1 }, { 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1 }, + { 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1 }, + { 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1 }, { 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1 }, + { 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, { 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1 }, + { 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1 }, { 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1 }, + { 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, { 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, { 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1 }, + { 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1 }, { 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1 }, + { 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1 }, { 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1 }, + { 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1 }, { 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1 }, { 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1 }, + { 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1 }, { 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1 }, + { 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1 }, { 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1 }, + { 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1 }, { 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1 }, + { 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1 }, { 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1 }, + { 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, { 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1 }, + { 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1 }, { 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1 }, + { 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1 }, { 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1 }, + { 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, { 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1 }, + { 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1 }, { 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }}; + + const unsigned int + nx = (unsigned int)((x1-x0+1)/resx), nxm1 = nx-1, + ny = (unsigned int)((y1-y0+1)/resy), nym1 = ny-1, + nz = (unsigned int)((z1-z0+1)/resz), nzm1 = nz-1; + if (!nxm1 || !nym1 || !nzm1) return CImg(); + + primitives.assign(); + CImgList points; + CImg indices1(nx,ny,1,3,-1), indices2(indices1); + CImg values1(nx,ny), values2(nx,ny); + float X = 0, Y = 0, Z = 0, nX = 0, nY = 0, nZ = 0; + + // Fill the first plane with function values + Y = y0; + cimg_forY(values1,y) { + X = x0; + cimg_forX(values1,x) { values1(x,y) = (float)func(X,Y,z0); X+=resx; } + Y+=resy; + } + + // Run Marching Cubes algorithm + Z = z0; nZ = Z + resz; + for (unsigned int zi = 0; zi::vector(Xi,Y,Z)); + } + if ((edge&2) && indices1(nxi,yi,1)<0) { + const float Yi = Y + (isovalue-val1)*resy/(val2-val1); + indices1(nxi,yi,1) = points.size; + points.insert(CImg::vector(nX,Yi,Z)); + } + if ((edge&4) && indices1(xi,nyi,0)<0) { + const float Xi = X + (isovalue-val3)*resx/(val2-val3); + indices1(xi,nyi,0) = points.size; + points.insert(CImg::vector(Xi,nY,Z)); + } + if ((edge&8) && indices1(xi,yi,1)<0) { + const float Yi = Y + (isovalue-val0)*resy/(val3-val0); + indices1(xi,yi,1) = points.size; + points.insert(CImg::vector(X,Yi,Z)); + } + if ((edge&16) && indices2(xi,yi,0)<0) { + const float Xi = X + (isovalue-val4)*resx/(val5-val4); + indices2(xi,yi,0) = points.size; + points.insert(CImg::vector(Xi,Y,nZ)); + } + if ((edge&32) && indices2(nxi,yi,1)<0) { + const float Yi = Y + (isovalue-val5)*resy/(val6-val5); + indices2(nxi,yi,1) = points.size; + points.insert(CImg::vector(nX,Yi,nZ)); + } + if ((edge&64) && indices2(xi,nyi,0)<0) { + const float Xi = X + (isovalue-val7)*resx/(val6-val7); + indices2(xi,nyi,0) = points.size; + points.insert(CImg::vector(Xi,nY,nZ)); + } + if ((edge&128) && indices2(xi,yi,1)<0) { + const float Yi = Y + (isovalue-val4)*resy/(val7-val4); + indices2(xi,yi,1) = points.size; + points.insert(CImg::vector(X,Yi,nZ)); + } + if ((edge&256) && indices1(xi,yi,2)<0) { + const float Zi = Z+ (isovalue-val0)*resz/(val4-val0); + indices1(xi,yi,2) = points.size; + points.insert(CImg::vector(X,Y,Zi)); + } + if ((edge&512) && indices1(nxi,yi,2)<0) { + const float Zi = Z + (isovalue-val1)*resz/(val5-val1); + indices1(nxi,yi,2) = points.size; + points.insert(CImg::vector(nX,Y,Zi)); + } + if ((edge&1024) && indices1(nxi,nyi,2)<0) { + const float Zi = Z + (isovalue-val2)*resz/(val6-val2); + indices1(nxi,nyi,2) = points.size; + points.insert(CImg::vector(nX,nY,Zi)); + } + if ((edge&2048) && indices1(xi,nyi,2)<0) { + const float Zi = Z + (isovalue-val3)*resz/(val7-val3); + indices1(xi,nyi,2) = points.size; + points.insert(CImg::vector(X,nY,Zi)); + } + + // Create triangles + for (int *triangle = triangles[configuration]; *triangle!=-1; ) { + const unsigned int p0 = *(triangle++), p1 = *(triangle++), p2 = *(triangle++); + const tf + i0 = (tf)(_marching_cubes_indice(p0,indices1,indices2,xi,yi,nxi,nyi)), + i1 = (tf)(_marching_cubes_indice(p1,indices1,indices2,xi,yi,nxi,nyi)), + i2 = (tf)(_marching_cubes_indice(p2,indices1,indices2,xi,yi,nxi,nyi)); + if (invert_faces) primitives.insert(CImg::vector(i0,i1,i2)); + else primitives.insert(CImg::vector(i0,i2,i1)); + } + } + } + } + cimg::swap(values1,values2); + cimg::swap(indices1,indices2); + } + return points.get_append('x'); + } + + struct _marching_squares_func { + const CImg& ref; + _marching_squares_func(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y) const { + return (float)ref((int)x,(int)y); + } + }; + + struct _marching_cubes_func { + const CImg& ref; + _marching_cubes_func(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z) const { + return (float)ref((int)x,(int)y,(int)z); + } + }; + + struct _marching_squares_func_float { + const CImg& ref; + _marching_squares_func_float(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y) const { + return (float)ref._linear_atXY(x,y); + } + }; + + struct _marching_cubes_func_float { + const CImg& ref; + _marching_cubes_func_float(const CImg& pref):ref(pref) {} + float operator()(const float x, const float y, const float z) const { + return (float)ref._linear_atXYZ(x,y,z); + } + }; + + //! Compute a vectorization of an implicit function. + template + CImg get_isovalue3d(CImgList& primitives, const float isovalue, + const float resx=1, const float resy=1, const float resz=1, + const bool invert_faces=false) const { + primitives.assign(); + if (is_empty()) return *this; + if (dim>1) + throw CImgInstanceException("CImg<%s>::get_isovalue3d() : Instance image (%u,%u,%u,%u,%p) is not a scalar image.", + pixel_type(),width,height,depth,dim,data); + CImg points; + if (depth>1) { + if (resx==1 && resy==1 && resz==1) { + const _marching_cubes_func func(*this); + points = marching_cubes(primitives,func,isovalue,0,0,0,dimx()-1.0f,dimy()-1.0f,dimz()-1.0f,resx,resy,resz,invert_faces); + } else { + const _marching_cubes_func_float func(*this); + points = marching_cubes(primitives,func,isovalue,0,0,0,dimx()-1.0f,dimy()-1.0f,dimz()-1.0f,resx,resy,resz,invert_faces); + } + } else { + if (resx==1 && resy==1) { + const _marching_squares_func func(*this); + points = marching_squares(primitives,func,isovalue,0,0,dimx()-1.0f,dimy()-1.0f,resx,resy); + } else { + const _marching_squares_func_float func(*this); + points = marching_squares(primitives,func,isovalue,0,0,dimx()-1.0f,dimy()-1.0f,resx,resy); + } + if (points) points.resize(-100,3,1,1,0); + } + return points; + } + + //! Translate a 3D object. + CImg& translate_object3d(const float tx, const float ty=0, const float tz=0) { + get_shared_line(0)+=tx; get_shared_line(1)+=ty; get_shared_line(2)+=tz; + return *this; + } + + CImg get_translate_object3d(const float tx, const float ty=0, const float tz=0) const { + return CImg(*this,false).translate_object3d(tx,ty,tz); + } + + //! Translate a 3D object so that it becomes centered. + CImg& translate_object3d() { + CImg xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2); + float xm, xM = (float)xcoords.maxmin(xm), ym, yM = (float)ycoords.maxmin(ym), zm, zM = (float)zcoords.maxmin(zm); + xcoords-=(xm + xM)/2; ycoords-=(ym + yM)/2; zcoords-=(zm + zM)/2; + return *this; + } + + CImg get_translate_object3d() const { + return CImg(*this,false).translate_object3d(); + } + + //! Resize a 3D object. + CImg& resize_object3d(const float sx, const float sy=-100, const float sz=-100) { + CImg xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2); + float xm, xM = (float)xcoords.maxmin(xm), ym, yM = (float)ycoords.maxmin(ym), zm, zM = (float)zcoords.maxmin(zm); + if (xm0) xcoords*=sx/(xM-xm); else xcoords*=-sx/100; } + if (ym0) ycoords*=sy/(yM-ym); else ycoords*=-sy/100; } + if (zm0) zcoords*=sz/(zM-zm); else zcoords*=-sz/100; } + return *this; + } + + CImg get_resize_object3d(const float sx, const float sy=-100, const float sz=-100) const { + return CImg(*this,false).resize_object3d(sx,sy,sz); + } + + // Resize a 3D object so that its max dimension if one. + CImg resize_object3d() const { + CImg xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2); + float xm, xM = (float)xcoords.maxmin(xm), ym, yM = (float)ycoords.maxmin(ym), zm, zM = (float)zcoords.maxmin(zm); + const float dx = xM - xm, dy = yM - ym, dz = zM - zm, dmax = cimg::max(dx,dy,dz); + if (dmax>0) { xcoords/=dmax; ycoords/=dmax; zcoords/=dmax; } + return *this; + } + + CImg get_resize_object3d() const { + return CImg(*this,false).resize_object3d(); + } + + //! Append a 3D object to another one. + template + CImg& append_object3d(CImgList& primitives, const CImg& obj_points, const CImgList& obj_primitives) { + const unsigned int P = width; + append(obj_points,'x'); + const unsigned int N = primitives.size; + primitives.insert(obj_primitives); + for (unsigned int i = N; i &p = primitives[i]; + if (p.size()!=5) p+=P; + else { p[0]+=P; if (p[2]==0) p[1]+=P; } + } + return *this; + } + + //@} + //---------------------------- + // + //! \name Color bases + //@{ + //---------------------------- + + //! Return a default indexed color palette with 256 (R,G,B) entries. + /** + The default color palette is used by %CImg when displaying images on 256 colors displays. + It consists in the quantification of the (R,G,B) color space using 3:3:2 bits for color coding + (i.e 8 levels for the Red and Green and 4 levels for the Blue). + \return a 1x256x1x3 color image defining the palette entries. + **/ + static CImg default_LUT8() { + static CImg palette; + if (!palette) { + palette.assign(1,256,1,3); + for (unsigned int index = 0, r = 16; r<256; r+=32) + for (unsigned int g = 16; g<256; g+=32) + for (unsigned int b = 32; b<256; b+=64) { + palette(0,index,0) = (Tuchar)r; + palette(0,index,1) = (Tuchar)g; + palette(0,index++,2) = (Tuchar)b; + } + } + return palette; + } + + //! Return a rainbow color palette with 256 (R,G,B) entries. + static CImg rainbow_LUT8() { + static CImg palette; + if (!palette) { + CImg tmp(1,256,1,3,1); + tmp.get_shared_channel(0).sequence(0,359); + palette = tmp.HSVtoRGB(); + } + return palette; + } + + //! Return a contrasted color palette with 256 (R,G,B) entries. + static CImg contrast_LUT8() { + static const unsigned char pal[] = { + 217,62,88,75,1,237,240,12,56,160,165,116,1,1,204,2,15,248,148,185,133,141,46,246,222,116,16,5,207,226, + 17,114,247,1,214,53,238,0,95,55,233,235,109,0,17,54,33,0,90,30,3,0,94,27,19,0,68,212,166,130,0,15,7,119, + 238,2,246,198,0,3,16,10,13,2,25,28,12,6,2,99,18,141,30,4,3,140,12,4,30,233,7,10,0,136,35,160,168,184,20, + 233,0,1,242,83,90,56,180,44,41,0,6,19,207,5,31,214,4,35,153,180,75,21,76,16,202,218,22,17,2,136,71,74, + 81,251,244,148,222,17,0,234,24,0,200,16,239,15,225,102,230,186,58,230,110,12,0,7,129,249,22,241,37,219, + 1,3,254,210,3,212,113,131,197,162,123,252,90,96,209,60,0,17,0,180,249,12,112,165,43,27,229,77,40,195,12, + 87,1,210,148,47,80,5,9,1,137,2,40,57,205,244,40,8,252,98,0,40,43,206,31,187,0,180,1,69,70,227,131,108,0, + 223,94,228,35,248,243,4,16,0,34,24,2,9,35,73,91,12,199,51,1,249,12,103,131,20,224,2,70,32, + 233,1,165,3,8,154,246,233,196,5,0,6,183,227,247,195,208,36,0,0,226,160,210,198,69,153,210,1,23,8,192,2,4, + 137,1,0,52,2,249,241,129,0,0,234,7,238,71,7,32,15,157,157,252,158,2,250,6,13,30,11,162,0,199,21,11,27,224, + 4,157,20,181,111,187,218,3,0,11,158,230,196,34,223,22,248,135,254,210,157,219,0,117,239,3,255,4,227,5,247, + 11,4,3,188,111,11,105,195,2,0,14,1,21,219,192,0,183,191,113,241,1,12,17,248,0,48,7,19,1,254,212,0,239,246, + 0,23,0,250,165,194,194,17,3,253,0,24,6,0,141,167,221,24,212,2,235,243,0,0,205,1,251,133,204,28,4,6,1,10, + 141,21,74,12,236,254,228,19,1,0,214,1,186,13,13,6,13,16,27,209,6,216,11,207,251,59,32,9,155,23,19,235,143, + 116,6,213,6,75,159,23,6,0,228,4,10,245,249,1,7,44,234,4,102,174,0,19,239,103,16,15,18,8,214,22,4,47,244, + 255,8,0,251,173,1,212,252,250,251,252,6,0,29,29,222,233,246,5,149,0,182,180,13,151,0,203,183,0,35,149,0, + 235,246,254,78,9,17,203,73,11,195,0,3,5,44,0,0,237,5,106,6,130,16,214,20,168,247,168,4,207,11,5,1,232,251, + 129,210,116,231,217,223,214,27,45,38,4,177,186,249,7,215,172,16,214,27,249,230,236,2,34,216,217,0,175,30, + 243,225,244,182,20,212,2,226,21,255,20,0,2,13,62,13,191,14,76,64,20,121,4,118,0,216,1,147,0,2,210,1,215, + 95,210,236,225,184,46,0,248,24,11,1,9,141,250,243,9,221,233,160,11,147,2,55,8,23,12,253,9,0,54,0,231,6,3, + 141,8,2,246,9,180,5,11,8,227,8,43,110,242,1,130,5,97,36,10,6,219,86,133,11,108,6,1,5,244,67,19,28,0,174, + 154,16,127,149,252,188,196,196,228,244,9,249,0,0,0,37,170,32,250,0,73,255,23,3,224,234,38,195,198,0,255,87, + 33,221,174,31,3,0,189,228,6,153,14,144,14,108,197,0,9,206,245,254,3,16,253,178,248,0,95,125,8,0,3,168,21, + 23,168,19,50,240,244,185,0,1,144,10,168,31,82,1,13 }; + static const CImg palette(pal,1,256,1,3,false); + return palette; + } + + //! Convert (R,G,B) color image to indexed color image. + template + CImg& RGBtoLUT(const CImg& palette, const bool dithering=true, const bool indexing=false) { + return get_RGBtoLUT(palette,dithering,indexing).transfer_to(*this); + } + + template + CImg get_RGBtoLUT(const CImg& palette, const bool dithering=true, const bool indexing=false) const { + if (is_empty()) return CImg(); + if (dim!=3) + throw CImgInstanceException("CImg<%s>::RGBtoLUT() : Input image dimension is dim=%u, " + "should be a (R,G,B) image.", + pixel_type(),dim); + if (palette.data && palette.dim!=3) + throw CImgArgumentException("CImg<%s>::RGBtoLUT() : Given palette dimension is dim=%u, " + "should be a (R,G,B) palette", + pixel_type(),palette.dim); + CImg res(width,height,depth,indexing?1:3); + float *line1 = new float[3*width], *line2 = new float[3*width]; + t *pRd = res.ptr(0,0,0,0), *pGd = indexing?pRd:res.ptr(0,0,0,1), *pBd = indexing?pRd:res.ptr(0,0,0,2); + cimg_forZ(*this,z) { + const T *pRs = ptr(0,0,z,0), *pGs = ptr(0,0,z,1), *pBs = ptr(0,0,z,2); + float *ptrd = line2; cimg_forX(*this,x) { *(ptrd++) = (float)*(pRs++); *(ptrd++) = (float)*(pGs++); *(ptrd++) = (float)*(pBs++); } + cimg_forY(*this,y) { + cimg::swap(line1,line2); + if (y255?255:R); G = G<0?0:(G>255?255:G); B = B<0?0:(B>255?255:B); + t Rbest = 0, Gbest = 0, Bbest = 0; + int best_index = 0; + if (palette) { // find best match in given color palette + const t *pRs = palette.ptr(0,0,0,0), *pGs = palette.ptr(0,0,0,1), *pBs = palette.ptr(0,0,0,2); + const unsigned int Npal = palette.width*palette.height*palette.depth; + float min = cimg::type::max(); + for (unsigned int off = 0; off>3) | ((unsigned char)Bbest>>6); + } + if (indexing) *(pRd++) = (t)best_index; else { *(pRd++) = Rbest; *(pGd++) = Gbest; *(pBd++) = Bbest; } + if (dithering) { // apply dithering to neighborhood pixels if needed + const float dR = (float)(R-Rbest), dG = (float)(G-Gbest), dB = (float)(B-Bbest); + if (x0) { *(--ptr2)+= dB*3/16; *(--ptr2)+= dG*3/16; *(--ptr2)+= dR*3/16; ptr2+=3; } + if (x& RGBtoLUT(const bool dithering=true, const bool indexing=false) { + return get_RGBtoLUT(dithering,indexing).transfer_to(*this); + } + + CImg get_RGBtoLUT(const bool dithering=true, const bool indexing=false) const { + static const CImg empty; + return get_RGBtoLUT(empty,dithering,indexing); + } + + //! Convert an indexed image to a (R,G,B) image using the specified color palette. + CImg& LUTtoRGB(const CImg& palette) { + return get_LUTtoRGB(palette).transfer_to(*this); + } + + template + CImg get_LUTtoRGB(const CImg& palette) const { + if (is_empty()) return CImg(); + if (dim!=1) + throw CImgInstanceException("CImg<%s>::LUTtoRGB() : Input image dimension is dim=%u, " + "should be a LUT image", + pixel_type(),dim); + if (palette.data && palette.dim!=3) + throw CImgArgumentException("CImg<%s>::LUTtoRGB() : Given palette dimension is dim=%u, " + "should be a (R,G,B) palette", + pixel_type(),palette.dim); + const CImg pal = palette.data?palette:CImg(default_LUT8()); + CImg res(width,height,depth,3); + const t *pRs = pal.ptr(0,0,0,0), *pGs = pal.ptr(0,0,0,1), *pBs = pal.ptr(0,0,0,2); + t *pRd = res.ptr(0,0,0,1), *pGd = pRd + width*height*depth, *pBd = pGd + width*height*depth; + const unsigned int Npal = palette.width*palette.height*palette.depth; + cimg_for(*this,ptr,T) { + const unsigned int index = ((unsigned int)*ptr)%Npal; + *(--pRd) = pRs[index]; *(--pGd) = pGs[index]; *(--pBd) = pBs[index]; + } + return res; + } + + //! Convert an indexed image (with the default palette) to a (R,G,B) image. + CImg& LUTtoRGB() { + return get_LUTtoRGB().transfer_to(*this); + } + + CImg get_LUTtoRGB() const { + static const CImg empty; + return get_LUTtoRGB(empty); + } + + //! Convert color pixels from (R,G,B) to (H,S,V). + CImg& RGBtoHSV() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::RGBtoHSV() : Input image dimension is dim=%u, " + "should be a (R,G,B) image.", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + R = (Tfloat)*p1, + G = (Tfloat)*p2, + B = (Tfloat)*p3, + nR = (R<0?0:(R>255?255:R))/255, + nG = (G<0?0:(G>255?255:G))/255, + nB = (B<0?0:(B>255?255:B))/255, + m = cimg::min(nR,nG,nB), + M = cimg::max(nR,nG,nB); + Tfloat H = 0, S = 0; + if (M!=m) { + const Tfloat + f = (nR==m)?(nG-nB):((nG==m)?(nB-nR):(nR-nG)), + i = (Tfloat)((nR==m)?3:((nG==m)?5:1)); + H = (i-f/(M-m)); + if (H>=6) H-=6; + H*=60; + S = (M-m)/M; + } + *(p1++) = (T)H; + *(p2++) = (T)S; + *(p3++) = (T)M; + } + return *this; + } + + CImg get_RGBtoHSV() const { + return CImg(*this,false).RGBtoHSV(); + } + + //! Convert color pixels from (H,S,V) to (R,G,B). + CImg& HSVtoRGB() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::HSVtoRGB() : Input image dimension is dim=%u, " + "should be a (H,S,V) image", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + Tfloat + H = (Tfloat)*p1, + S = (Tfloat)*p2, + V = (Tfloat)*p3, + R = 0, G = 0, B = 0; + if (H==0 && S==0) R = G = B = V; + else { + H/=60; + const int i = (int)cimg_std::floor(H); + const Tfloat + f = (i&1)?(H-i):(1-H+i), + m = V*(1-S), + n = V*(1-S*f); + switch (i) { + case 6 : + case 0 : R = V; G = n; B = m; break; + case 1 : R = n; G = V; B = m; break; + case 2 : R = m; G = V; B = n; break; + case 3 : R = m; G = n; B = V; break; + case 4 : R = n; G = m; B = V; break; + case 5 : R = V; G = m; B = n; break; + } + } + R*=255; G*=255; B*=255; + *(p1++) = (T)(R<0?0:(R>255?255:R)); + *(p2++) = (T)(G<0?0:(G>255?255:G)); + *(p3++) = (T)(B<0?0:(B>255?255:B)); + } + return *this; + } + + CImg get_HSVtoRGB() const { + return CImg(*this,false).HSVtoRGB(); + } + + //! Convert color pixels from (R,G,B) to (H,S,L). + CImg& RGBtoHSL() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::RGBtoHSL() : Input image dimension is dim=%u, " + "should be a (R,G,B) image.", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + R = (Tfloat)*p1, + G = (Tfloat)*p2, + B = (Tfloat)*p3, + nR = (R<0?0:(R>255?255:R))/255, + nG = (G<0?0:(G>255?255:G))/255, + nB = (B<0?0:(B>255?255:B))/255, + m = cimg::min(nR,nG,nB), + M = cimg::max(nR,nG,nB), + L = (m+M)/2; + Tfloat H = 0, S = 0; + if (M==m) H = S = 0; + else { + const Tfloat + f = (nR==m)?(nG-nB):((nG==m)?(nB-nR):(nR-nG)), + i = (nR==m)?3.0f:((nG==m)?5.0f:1.0f); + H = (i-f/(M-m)); + if (H>=6) H-=6; + H*=60; + S = (2*L<=1)?((M-m)/(M+m)):((M-m)/(2-M-m)); + } + *(p1++) = (T)H; + *(p2++) = (T)S; + *(p3++) = (T)L; + } + return *this; + } + + CImg get_RGBtoHSL() const { + return CImg< Tfloat>(*this,false).RGBtoHSL(); + } + + //! Convert color pixels from (H,S,L) to (R,G,B). + CImg& HSLtoRGB() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::HSLtoRGB() : Input image dimension is dim=%u, " + "should be a (H,S,V) image", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + H = (Tfloat)*p1, + S = (Tfloat)*p2, + L = (Tfloat)*p3, + q = 2*L<1?L*(1+S):(L+S-L*S), + p = 2*L-q, + h = H/360, + tr = h + 1.0f/3, + tg = h, + tb = h - 1.0f/3, + ntr = tr<0?tr+1:(tr>1?tr-1:tr), + ntg = tg<0?tg+1:(tg>1?tg-1:tg), + ntb = tb<0?tb+1:(tb>1?tb-1:tb), + R = 255*(6*ntr<1?p+(q-p)*6*ntr:(2*ntr<1?q:(3*ntr<2?p+(q-p)*6*(2.0f/3-ntr):p))), + G = 255*(6*ntg<1?p+(q-p)*6*ntg:(2*ntg<1?q:(3*ntg<2?p+(q-p)*6*(2.0f/3-ntg):p))), + B = 255*(6*ntb<1?p+(q-p)*6*ntb:(2*ntb<1?q:(3*ntb<2?p+(q-p)*6*(2.0f/3-ntb):p))); + *(p1++) = (T)(R<0?0:(R>255?255:R)); + *(p2++) = (T)(G<0?0:(G>255?255:G)); + *(p3++) = (T)(B<0?0:(B>255?255:B)); + } + return *this; + } + + CImg get_HSLtoRGB() const { + return CImg(*this,false).HSLtoRGB(); + } + + //! Convert color pixels from (R,G,B) to (H,S,I). + //! Reference: "Digital Image Processing, 2nd. edition", R. Gonzalez and R. Woods. Prentice Hall, 2002. + CImg& RGBtoHSI() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::RGBtoHSI() : Input image dimension is dim=%u, " + "should be a (R,G,B) image.", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + R = (Tfloat)*p1, + G = (Tfloat)*p2, + B = (Tfloat)*p3, + nR = (R<0?0:(R>255?255:R))/255, + nG = (G<0?0:(G>255?255:G))/255, + nB = (B<0?0:(B>255?255:B))/255, + m = cimg::min(nR,nG,nB), + theta = (Tfloat)(cimg_std::acos(0.5f*((nR-nG)+(nR-nB))/cimg_std::sqrt(cimg_std::pow(nR-nG,2)+(nR-nB)*(nG-nB)))*180/cimg::valuePI), + sum = nR + nG + nB; + Tfloat H = 0, S = 0, I = 0; + if (theta>0) H = (nB<=nG)?theta:360-theta; + if (sum>0) S = 1 - 3/sum*m; + I = sum/3; + *(p1++) = (T)H; + *(p2++) = (T)S; + *(p3++) = (T)I; + } + return *this; + } + + CImg get_RGBtoHSI() const { + return CImg(*this,false).RGBtoHSI(); + } + + //! Convert color pixels from (H,S,I) to (R,G,B). + CImg& HSItoRGB() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::HSItoRGB() : Input image dimension is dim=%u, " + "should be a (H,S,I) image", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + Tfloat + H = (Tfloat)*p1, + S = (Tfloat)*p2, + I = (Tfloat)*p3, + a = I*(1-S), + R = 0, G = 0, B = 0; + if (H<120) { + B = a; + R = (Tfloat)(I*(1+S*cimg_std::cos(H*cimg::valuePI/180)/cimg_std::cos((60-H)*cimg::valuePI/180))); + G = 3*I-(R+B); + } else if (H<240) { + H-=120; + R = a; + G = (Tfloat)(I*(1+S*cimg_std::cos(H*cimg::valuePI/180)/cimg_std::cos((60-H)*cimg::valuePI/180))); + B = 3*I-(R+G); + } else { + H-=240; + G = a; + B = (Tfloat)(I*(1+S*cimg_std::cos(H*cimg::valuePI/180)/cimg_std::cos((60-H)*cimg::valuePI/180))); + R = 3*I-(G+B); + } + R*=255; G*=255; B*=255; + *(p1++) = (T)(R<0?0:(R>255?255:R)); + *(p2++) = (T)(G<0?0:(G>255?255:G)); + *(p3++) = (T)(B<0?0:(B>255?255:B)); + } + return *this; + } + + CImg get_HSItoRGB() const { + return CImg< Tuchar>(*this,false).HSItoRGB(); + } + + //! Convert color pixels from (R,G,B) to (Y,Cb,Cr)_8. + CImg& RGBtoYCbCr() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::RGBtoYCbCr() : Input image dimension is dim=%u, " + "should be a (R,G,B) image (dim=3)", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + R = (Tfloat)*p1, + G = (Tfloat)*p2, + B = (Tfloat)*p3, + Y = (66*R + 129*G + 25*B + 128)/256 + 16, + Cb = (-38*R - 74*G + 112*B + 128)/256 + 128, + Cr = (112*R - 94*G - 18*B + 128)/256 + 128; + *(p1++) = (T)(Y<0?0:(Y>255?255:Y)); + *(p2++) = (T)(Cb<0?0:(Cb>255?255:Cb)); + *(p3++) = (T)(Cr<0?0:(Cr>255?255:Cr)); + } + return *this; + } + + CImg get_RGBtoYCbCr() const { + return CImg(*this,false).RGBtoYCbCr(); + } + + //! Convert color pixels from (R,G,B) to (Y,Cb,Cr)_8. + CImg& YCbCrtoRGB() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::YCbCrtoRGB() : Input image dimension is dim=%u, " + "should be a (Y,Cb,Cr)_8 image (dim=3)", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + Y = (Tfloat)*p1 - 16, + Cb = (Tfloat)*p2 - 128, + Cr = (Tfloat)*p3 - 128, + R = (298*Y + 409*Cr + 128)/256, + G = (298*Y - 100*Cb - 208*Cr + 128)/256, + B = (298*Y + 516*Cb + 128)/256; + *(p1++) = (T)(R<0?0:(R>255?255:R)); + *(p2++) = (T)(G<0?0:(G>255?255:G)); + *(p3++) = (T)(B<0?0:(B>255?255:B)); + } + return *this; + } + + CImg get_YCbCrtoRGB() const { + return CImg(*this,false).YCbCrtoRGB(); + } + + //! Convert color pixels from (R,G,B) to (Y,U,V). + CImg& RGBtoYUV() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::RGBtoYUV() : Input image dimension is dim=%u, " + "should be a (R,G,B) image (dim=3)", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + R = (Tfloat)*p1/255, + G = (Tfloat)*p2/255, + B = (Tfloat)*p3/255, + Y = 0.299f*R + 0.587f*G + 0.114f*B; + *(p1++) = (T)Y; + *(p2++) = (T)(0.492f*(B-Y)); + *(p3++) = (T)(0.877*(R-Y)); + } + return *this; + } + + CImg get_RGBtoYUV() const { + return CImg(*this,false).RGBtoYUV(); + } + + //! Convert color pixels from (Y,U,V) to (R,G,B). + CImg& YUVtoRGB() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::YUVtoRGB() : Input image dimension is dim=%u, " + "should be a (Y,U,V) image (dim=3)", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + Y = (Tfloat)*p1, + U = (Tfloat)*p2, + V = (Tfloat)*p3, + R = (Y + 1.140f*V)*255, + G = (Y - 0.395f*U - 0.581f*V)*255, + B = (Y + 2.032f*U)*255; + *(p1++) = (T)(R<0?0:(R>255?255:R)); + *(p2++) = (T)(G<0?0:(G>255?255:G)); + *(p3++) = (T)(B<0?0:(B>255?255:B)); + } + return *this; + } + + CImg get_YUVtoRGB() const { + return CImg< Tuchar>(*this,false).YUVtoRGB(); + } + + //! Convert color pixels from (R,G,B) to (C,M,Y). + CImg& RGBtoCMY() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::RGBtoCMY() : Input image dimension is dim=%u, " + "should be a (R,G,B) image (dim=3)", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + R = (Tfloat)*p1/255, + G = (Tfloat)*p2/255, + B = (Tfloat)*p3/255; + *(p1++) = (T)(1 - R); + *(p2++) = (T)(1 - G); + *(p3++) = (T)(1 - B); + } + return *this; + } + + CImg get_RGBtoCMY() const { + return CImg(*this,false).RGBtoCMY(); + } + + //! Convert (C,M,Y) pixels of a color image into the (R,G,B) color space. + CImg& CMYtoRGB() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::CMYtoRGB() : Input image dimension is dim=%u, " + "should be a (C,M,Y) image (dim=3)", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + C = (Tfloat)*p1, + M = (Tfloat)*p2, + Y = (Tfloat)*p3, + R = 255*(1 - C), + G = 255*(1 - M), + B = 255*(1 - Y); + *(p1++) = (T)(R<0?0:(R>255?255:R)); + *(p2++) = (T)(G<0?0:(G>255?255:G)); + *(p3++) = (T)(B<0?0:(B>255?255:B)); + } + return *this; + } + + CImg get_CMYtoRGB() const { + return CImg(*this,false).CMYtoRGB(); + } + + //! Convert color pixels from (C,M,Y) to (C,M,Y,K). + CImg& CMYtoCMYK() { + return get_CMYtoCMYK().transfer_to(*this); + } + + CImg get_CMYtoCMYK() const { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::CMYtoCMYK() : Input image dimension is dim=%u, " + "should be a (C,M,Y) image (dim=3)", + pixel_type(),dim); + CImg res(width,height,depth,4); + const T *ps1 = ptr(0,0,0,0), *ps2 = ptr(0,0,0,1), *ps3 = ptr(0,0,0,2); + Tfloat *pd1 = res.ptr(0,0,0,0), *pd2 = res.ptr(0,0,0,1), *pd3 = res.ptr(0,0,0,2), *pd4 = res.ptr(0,0,0,3); + for (unsigned long N = width*height*depth; N; --N) { + Tfloat + C = (Tfloat)*(ps1++), + M = (Tfloat)*(ps2++), + Y = (Tfloat)*(ps3++), + K = cimg::min(C,M,Y); + if (K==1) C = M = Y = 0; + else { const Tfloat K1 = 1 - K; C = (C - K)/K1; M = (M - K)/K1; Y = (Y - K)/K1; } + *(pd1++) = C; + *(pd2++) = M; + *(pd3++) = Y; + *(pd4++) = K; + } + return res; + } + + //! Convert (C,M,Y,K) pixels of a color image into the (C,M,Y) color space. + CImg& CMYKtoCMY() { + return get_CMYKtoCMY().transfer_to(*this); + } + + CImg get_CMYKtoCMY() const { + if (is_empty()) return *this; + if (dim!=4) + throw CImgInstanceException("CImg<%s>::CMYKtoCMY() : Input image dimension is dim=%u, " + "should be a (C,M,Y,K) image (dim=4)", + pixel_type(),dim); + CImg res(width,height,depth,3); + const T *ps1 = ptr(0,0,0,0), *ps2 = ptr(0,0,0,1), *ps3 = ptr(0,0,0,2), *ps4 = ptr(0,0,0,3); + Tfloat *pd1 = res.ptr(0,0,0,0), *pd2 = res.ptr(0,0,0,1), *pd3 = res.ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + C = (Tfloat)*ps1, + M = (Tfloat)*ps2, + Y = (Tfloat)*ps3, + K = (Tfloat)*ps4, + K1 = 1 - K; + *(pd1++) = C*K1 + K; + *(pd2++) = M*K1 + K; + *(pd3++) = Y*K1 + K; + } + return res; + } + + //! Convert color pixels from (R,G,B) to (X,Y,Z)_709. + CImg& RGBtoXYZ() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::RGBtoXYZ() : Input image dimension is dim=%u, " + "should be a (R,G,B) image (dim=3)", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + R = (Tfloat)*p1/255, + G = (Tfloat)*p2/255, + B = (Tfloat)*p3/255; + *(p1++) = (T)(0.412453f*R + 0.357580f*G + 0.180423f*B); + *(p2++) = (T)(0.212671f*R + 0.715160f*G + 0.072169f*B); + *(p3++) = (T)(0.019334f*R + 0.119193f*G + 0.950227f*B); + } + return *this; + } + + CImg get_RGBtoXYZ() const { + return CImg(*this,false).RGBtoXYZ(); + } + + //! Convert (X,Y,Z)_709 pixels of a color image into the (R,G,B) color space. + CImg& XYZtoRGB() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::XYZtoRGB() : Input image dimension is dim=%u, " + "should be a (X,Y,Z) image (dim=3)", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + X = (Tfloat)*p1*255, + Y = (Tfloat)*p2*255, + Z = (Tfloat)*p3*255, + R = 3.240479f*X - 1.537150f*Y - 0.498535f*Z, + G = -0.969256f*X + 1.875992f*Y + 0.041556f*Z, + B = 0.055648f*X - 0.204043f*Y + 1.057311f*Z; + *(p1++) = (T)(R<0?0:(R>255?255:R)); + *(p2++) = (T)(G<0?0:(G>255?255:G)); + *(p3++) = (T)(B<0?0:(B>255?255:B)); + } + return *this; + } + + CImg get_XYZtoRGB() const { + return CImg(*this,false).XYZtoRGB(); + } + + //! Convert (X,Y,Z)_709 pixels of a color image into the (L*,a*,b*) color space. + CImg& XYZtoLab() { +#define _cimg_Labf(x) ((x)>=0.008856f?(cimg_std::pow(x,(Tfloat)1/3)):(7.787f*(x)+16.0f/116)) + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::XYZtoLab() : Input image dimension is dim=%u, " + "should be a (X,Y,Z) image (dim=3)", + pixel_type(),dim); + const Tfloat + Xn = (Tfloat)(0.412453f + 0.357580f + 0.180423f), + Yn = (Tfloat)(0.212671f + 0.715160f + 0.072169f), + Zn = (Tfloat)(0.019334f + 0.119193f + 0.950227f); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + X = (Tfloat)*p1, + Y = (Tfloat)*p2, + Z = (Tfloat)*p3, + XXn = X/Xn, YYn = Y/Yn, ZZn = Z/Zn, + fX = (Tfloat)_cimg_Labf(XXn), + fY = (Tfloat)_cimg_Labf(YYn), + fZ = (Tfloat)_cimg_Labf(ZZn); + *(p1++) = (T)(116*fY - 16); + *(p2++) = (T)(500*(fX - fY)); + *(p3++) = (T)(200*(fY - fZ)); + } + return *this; + } + + CImg get_XYZtoLab() const { + return CImg(*this,false).XYZtoLab(); + } + + //! Convert (L,a,b) pixels of a color image into the (X,Y,Z) color space. + CImg& LabtoXYZ() { +#define _cimg_Labfi(x) ((x)>=0.206893f?((x)*(x)*(x)):(((x)-16.0f/116)/7.787f)) + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::LabtoXYZ() : Input image dimension is dim=%u, " + "should be a (X,Y,Z) image (dim=3)", + pixel_type(),dim); + const Tfloat + Xn = (Tfloat)(0.412453f + 0.357580f + 0.180423f), + Yn = (Tfloat)(0.212671f + 0.715160f + 0.072169f), + Zn = (Tfloat)(0.019334f + 0.119193f + 0.950227f); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + L = (Tfloat)*p1, + a = (Tfloat)*p2, + b = (Tfloat)*p3, + cY = (L + 16)/116, + Y = (Tfloat)(Yn*_cimg_Labfi(cY)), + pY = (Tfloat)cimg_std::pow(Y/Yn,(Tfloat)1/3), + cX = a/500 + pY, + X = Xn*cX*cX*cX, + cZ = pY - b/200, + Z = Zn*cZ*cZ*cZ; + *(p1++) = (T)(X); + *(p2++) = (T)(Y); + *(p3++) = (T)(Z); + } + return *this; + } + + CImg get_LabtoXYZ() const { + return CImg(*this,false).LabtoXYZ(); + } + + //! Convert (X,Y,Z)_709 pixels of a color image into the (x,y,Y) color space. + CImg& XYZtoxyY() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::XYZtoxyY() : Input image dimension is dim=%u, " + "should be a (X,Y,Z) image (dim=3)", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + X = (Tfloat)*p1, + Y = (Tfloat)*p2, + Z = (Tfloat)*p3, + sum = (X+Y+Z), + nsum = sum>0?sum:1; + *(p1++) = (T)(X/nsum); + *(p2++) = (T)(Y/nsum); + *(p3++) = (T)Y; + } + return *this; + } + + CImg get_XYZtoxyY() const { + return CImg(*this,false).XYZtoxyY(); + } + + //! Convert (x,y,Y) pixels of a color image into the (X,Y,Z)_709 color space. + CImg& xyYtoXYZ() { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::xyYtoXYZ() : Input image dimension is dim=%u, " + "should be a (x,y,Y) image (dim=3)", + pixel_type(),dim); + T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2); + for (unsigned long N = width*height*depth; N; --N) { + const Tfloat + px = (Tfloat)*p1, + py = (Tfloat)*p2, + Y = (Tfloat)*p3, + ny = py>0?py:1; + *(p1++) = (T)(px*Y/ny); + *(p2++) = (T)Y; + *(p3++) = (T)((1-px-py)*Y/ny); + } + return *this; + } + + CImg get_xyYtoXYZ() const { + return CImg(*this,false).xyYtoXYZ(); + } + + //! Convert a (R,G,B) image to a (L,a,b) one. + CImg& RGBtoLab() { + return RGBtoXYZ().XYZtoLab(); + } + + CImg get_RGBtoLab() const { + return CImg(*this,false).RGBtoLab(); + } + + //! Convert a (L,a,b) image to a (R,G,B) one. + CImg& LabtoRGB() { + return LabtoXYZ().XYZtoRGB(); + } + + CImg get_LabtoRGB() const { + return CImg(*this,false).LabtoRGB(); + } + + //! Convert a (R,G,B) image to a (x,y,Y) one. + CImg& RGBtoxyY() { + return RGBtoXYZ().XYZtoxyY(); + } + + CImg get_RGBtoxyY() const { + return CImg(*this,false).RGBtoxyY(); + } + + //! Convert a (x,y,Y) image to a (R,G,B) one. + CImg& xyYtoRGB() { + return xyYtoXYZ().XYZtoRGB(); + } + + CImg get_xyYtoRGB() const { + return CImg(*this,false).xyYtoRGB(); + } + + //! Convert a (R,G,B) image to a (C,M,Y,K) one. + CImg& RGBtoCMYK() { + return RGBtoCMY().CMYtoCMYK(); + } + + CImg get_RGBtoCMYK() const { + return CImg(*this,false).RGBtoCMYK(); + } + + //! Convert a (C,M,Y,K) image to a (R,G,B) one. + CImg& CMYKtoRGB() { + return CMYKtoCMY().CMYtoRGB(); + } + + CImg get_CMYKtoRGB() const { + return CImg(*this,false).CMYKtoRGB(); + } + + //! Convert a (R,G,B) image to a Bayer-coded representation. + /** + \note First (upper-left) pixel if the red component of the pixel color. + **/ + CImg& RGBtoBayer() { + return get_RGBtoBayer().transfer_to(*this); + } + + CImg get_RGBtoBayer() const { + if (is_empty()) return *this; + if (dim!=3) + throw CImgInstanceException("CImg<%s>::RGBtoBayer() : Input image dimension is dim=%u, " + "should be a (R,G,B) image (dim=3)", + pixel_type(),dim); + CImg res(width,height,depth,1); + const T *pR = ptr(0,0,0,0), *pG = ptr(0,0,0,1), *pB = ptr(0,0,0,2); + T *ptrd = res.data; + cimg_forXYZ(*this,x,y,z) { + if (y%2) { + if (x%2) *(ptrd++) = *pB; + else *(ptrd++) = *pG; + } else { + if (x%2) *(ptrd++) = *pG; + else *(ptrd++) = *pR; + } + ++pR; ++pG; ++pB; + } + return res; + } + + //! Convert a Bayer-coded image to a (R,G,B) color image. + CImg& BayertoRGB(const unsigned int interpolation_type=3) { + return get_BayertoRGB(interpolation_type).transfer_to(*this); + } + + CImg get_BayertoRGB(const unsigned int interpolation_type=3) const { + if (is_empty()) return *this; + if (dim!=1) + throw CImgInstanceException("CImg<%s>::BayertoRGB() : Input image dimension is dim=%u, " + "should be a Bayer image (dim=1)", + pixel_type(),dim); + CImg res(width,height,depth,3); + CImg_3x3(I,T); + Tuchar *pR = res.ptr(0,0,0,0), *pG = res.ptr(0,0,0,1), *pB = res.ptr(0,0,0,2); + switch (interpolation_type) { + case 3 : { // Edge-directed + CImg_3x3(R,T); + CImg_3x3(G,T); + CImg_3x3(B,T); + cimg_forXYZ(*this,x,y,z) { + const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x + CImg& _draw_scanline(const int x0, const int x1, const int y, + const tc *const color, const float opacity=1, + const float brightness=1, const bool init=false) { + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + static float nopacity = 0, copacity = 0; + static unsigned int whz = 0; + static const tc *col = 0; + if (init) { + nopacity = cimg::abs(opacity); + copacity = 1 - cimg::max(opacity,0); + whz = width*height*depth; + } else { + const int nx0 = x0>0?x0:0, nx1 = x1=0) { + col = color; + const unsigned int off = whz-dx-1; + T *ptrd = ptr(nx0,y); + if (opacity>=1) { // ** Opaque drawing ** + if (brightness==1) { // Brightness==1 + if (sizeof(T)!=1) cimg_forV(*this,k) { + const T val = (T)*(col++); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forV(*this,k) { + const T val = (T)*(col++); + cimg_std::memset(ptrd,(int)val,dx+1); + ptrd+=whz; + } + } else if (brightness<1) { // Brightness<1 + if (sizeof(T)!=1) cimg_forV(*this,k) { + const T val = (T)(*(col++)*brightness); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forV(*this,k) { + const T val = (T)(*(col++)*brightness); + cimg_std::memset(ptrd,(int)val,dx+1); + ptrd+=whz; + } + } else { // Brightness>1 + if (sizeof(T)!=1) cimg_forV(*this,k) { + const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval); + for (int x = dx; x>=0; --x) *(ptrd++) = val; + ptrd+=off; + } else cimg_forV(*this,k) { + const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval); + cimg_std::memset(ptrd,(int)val,dx+1); + ptrd+=whz; + } + } + } else { // ** Transparent drawing ** + if (brightness==1) { // Brightness==1 + cimg_forV(*this,k) { + const T val = (T)*(col++); + for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } else if (brightness<=1) { // Brightness<1 + cimg_forV(*this,k) { + const T val = (T)(*(col++)*brightness); + for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } else { // Brightness>1 + cimg_forV(*this,k) { + const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval); + for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; } + ptrd+=off; + } + } + } + } + } + return *this; + } + + template + CImg& _draw_scanline(const tc *const color, const float opacity=1) { + return _draw_scanline(0,0,0,color,opacity,0,true); + } + + //! Draw a 2D colored point (pixel). + /** + \param x0 X-coordinate of the point. + \param y0 Y-coordinate of the point. + \param color Pointer to \c dimv() consecutive values, defining the color values. + \param opacity Drawing opacity (optional). + \note + - Clipping is supported. + - To set pixel values without clipping needs, you should use the faster CImg::operator()() function. + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,128,64 }; + img.draw_point(50,50,color); + \endcode + **/ + template + CImg& draw_point(const int x0, const int y0, + const tc *const color, const float opacity=1) { + return draw_point(x0,y0,0,color,opacity); + } + + //! Draw a 2D colored point (pixel). + template + CImg& draw_point(const int x0, const int y0, + const CImg& color, const float opacity=1) { + return draw_point(x0,y0,color.data,opacity); + } + + //! Draw a 3D colored point (voxel). + template + CImg& draw_point(const int x0, const int y0, const int z0, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_point() : Specified color is (null)", + pixel_type()); + if (x0>=0 && y0>=0 && z0>=0 && x0=1) cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; } + else cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; } + } + return *this; + } + + //! Draw a 3D colored point (voxel). + template + CImg& draw_point(const int x0, const int y0, const int z0, + const CImg& color, const float opacity=1) { + return draw_point(x0,y0,z0,color.data,opacity); + } + + // Draw a cloud of colored point (internal). + template + CImg& _draw_point(const t& points, const unsigned int W, const unsigned int H, + const tc *const color, const float opacity) { + if (is_empty() || !points || !W) return *this; + switch (H) { + case 0 : case 1 : + throw CImgArgumentException("CImg<%s>::draw_point() : Given list of points is not valid.", + pixel_type()); + case 2 : { + for (unsigned int i = 0; i img(100,100,1,3,0); + const unsigned char color[] = { 255,128,64 }; + CImgList points; + points.insert(CImg::vector(0,0)). + .insert(CImg::vector(70,10)). + .insert(CImg::vector(80,60)). + .insert(CImg::vector(10,90)); + img.draw_point(points,color); + \endcode + **/ + template + CImg& draw_point(const CImgList& points, + const tc *const color, const float opacity=1) { + unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size())); + return _draw_point(points,points.size,H,color,opacity); + } + + //! Draw a cloud of colored points. + template + CImg& draw_point(const CImgList& points, + const CImg& color, const float opacity=1) { + return draw_point(points,color.data,opacity); + } + + //! Draw a cloud of colored points. + /** + \note + - Similar to the previous function, where the N vertex coordinates are stored as a Nx2 or Nx3 image + (sequence of vectors aligned along the x-axis). + **/ + template + CImg& draw_point(const CImg& points, + const tc *const color, const float opacity=1) { + return _draw_point(points,points.width,points.height,color,opacity); + } + + //! Draw a cloud of colored points. + template + CImg& draw_point(const CImg& points, + const CImg& color, const float opacity=1) { + return draw_point(points,color.data,opacity); + } + + //! Draw a 2D colored line. + /** + \param x0 X-coordinate of the starting line point. + \param y0 Y-coordinate of the starting line point. + \param x1 X-coordinate of the ending line point. + \param y1 Y-coordinate of the ending line point. + \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity (optional). + \param pattern An integer whose bits describe the line pattern (optional). + \param init_hatch Flag telling if a reinitialization of the hash state must be done (optional). + \note + - Clipping is supported. + - Line routine uses Bresenham's algorithm. + - Set \p init_hatch = false to draw consecutive hatched segments without breaking the line pattern. + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,128,64 }; + img.draw_line(40,40,80,70,color); + \endcode + **/ + template + CImg& draw_line(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null)", + pixel_type()); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=dimx()) return *this; + if (xleft<0) { yleft-=xleft*(yright - yleft)/(xright - xleft); xleft = 0; } + if (xright>=dimx()) { yright-=(xright - dimx())*(yright - yleft)/(xright - xleft); xright = dimx()-1; } + if (ydown<0 || yup>=dimy()) return *this; + if (yup<0) { xup-=yup*(xdown - xup)/(ydown - yup); yup = 0; } + if (ydown>=dimy()) { xdown-=(ydown - dimy())*(xdown - xup)/(ydown - yup); ydown = dimy()-1; } + T *ptrd0 = ptr(nx0,ny0); + int dx = xright - xleft, dy = ydown - yup; + const bool steep = dy>dx; + if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy); + const int + offx = (nx0=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { T *ptrd = ptrd0; const tc* col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }} + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; const tc* col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + T *ptrd = ptrd0; const tc* col = color; + cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; const tc* col = color; cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a 2D colored line. + template + CImg& draw_line(const int x0, const int y0, + const int x1, const int y1, + const CImg& color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + return draw_line(x0,y0,x1,y1,color.data,opacity,pattern,init_hatch); + } + + //! Draw a 2D colored line, with z-buffering. + template + CImg& draw_line(float *const zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (!is_empty() && z0>0 && z1>0) { + if (!color) + throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null).", + pixel_type()); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=dimx()) return *this; + if (xleft<0) { + const int D = xright - xleft; + yleft-=xleft*(yright - yleft)/D; + zleft-=xleft*(zright - zleft)/D; + xleft = 0; + } + if (xright>=dimx()) { + const int d = xright - dimx(), D = xright - xleft; + yright-=d*(yright - yleft)/D; + zright-=d*(zright - zleft)/D; + xright = dimx()-1; + } + if (ydown<0 || yup>=dimy()) return *this; + if (yup<0) { + const int D = ydown - yup; + xup-=yup*(xdown - xup)/D; + zup-=yup*(zdown - zup)/D; + yup = 0; + } + if (ydown>=dimy()) { + const int d = ydown - dimy(), D = ydown - yup; + xdown-=d*(xdown - xup)/D; + zdown-=d*(zdown - zup)/D; + ydown = dimy()-1; + } + T *ptrd0 = ptr(nx0,ny0); + float *ptrz = zbuffer + nx0 + ny0*width; + int dx = xright - xleft, dy = ydown - yup; + const bool steep = dy>dx; + if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy); + const int + offx = (nx00?dx:1; + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx; + if (z>*ptrz && pattern&hatch) { + *ptrz = z; + T *ptrd = ptrd0; const tc *col = color; + cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx; + if (z>*ptrz) { + *ptrz = z; + T *ptrd = ptrd0; const tc *col = color; + cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx; + if (z>*ptrz && pattern&hatch) { + *ptrz = z; + T *ptrd = ptrd0; const tc *col = color; + cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx; + if (z>*ptrz) { + *ptrz = z; + T *ptrd = ptrd0; const tc *col = color; + cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } + } + return *this; + } + + //! Draw a 2D colored line, with z-buffering. + template + CImg& draw_line(float *const zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const CImg& color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color.data,opacity,pattern,init_hatch); + } + + //! Draw a 3D colored line. + template + CImg& draw_line(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null)", + pixel_type()); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + int nx0 = x0, ny0 = y0, nz0 = z0, nx1 = x1, ny1 = y1, nz1 = z1; + if (nx0>nx1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1); + if (nx1<0 || nx0>=dimx()) return *this; + if (nx0<0) { const int D = 1 + nx1 - nx0; ny0-=nx0*(1 + ny1 - ny0)/D; nz0-=nx0*(1 + nz1 - nz0)/D; nx0 = 0; } + if (nx1>=dimx()) { const int d = nx1-dimx(), D = 1 + nx1 - nx0; ny1+=d*(1 + ny0 - ny1)/D; nz1+=d*(1 + nz0 - nz1)/D; nx1 = dimx()-1; } + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1); + if (ny1<0 || ny0>=dimy()) return *this; + if (ny0<0) { const int D = 1 + ny1 - ny0; nx0-=ny0*(1 + nx1 - nx0)/D; nz0-=ny0*(1 + nz1 - nz0)/D; ny0 = 0; } + if (ny1>=dimy()) { const int d = ny1-dimy(), D = 1 + ny1 - ny0; nx1+=d*(1 + nx0 - nx1)/D; nz1+=d*(1 + nz0 - nz1)/D; ny1 = dimy()-1; } + if (nz0>nz1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1); + if (nz1<0 || nz0>=dimz()) return *this; + if (nz0<0) { const int D = 1 + nz1 - nz0; nx0-=nz0*(1 + nx1 - nx0)/D; ny0-=nz0*(1 + ny1 - ny0)/D; nz0 = 0; } + if (nz1>=dimz()) { const int d = nz1-dimz(), D = 1 + nz1 - nz0; nx1+=d*(1 + nx0 - nx1)/D; ny1+=d*(1 + ny0 - ny1)/D; nz1 = dimz()-1; } + const unsigned int dmax = cimg::max(cimg::abs(nx1 - nx0),cimg::abs(ny1 - ny0),nz1 - nz0), whz = width*height*depth; + const float px = (nx1 - nx0)/(float)dmax, py = (ny1 - ny0)/(float)dmax, pz = (nz1 - nz0)/(float)dmax; + float x = (float)nx0, y = (float)ny0, z = (float)nz0; + if (opacity>=1) for (unsigned int t = 0; t<=dmax; ++t) { + if (!(~pattern) || (~pattern && pattern&hatch)) { + T* ptrd = ptr((unsigned int)x,(unsigned int)y,(unsigned int)z); + const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; } + } + x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + for (unsigned int t = 0; t<=dmax; ++t) { + if (!(~pattern) || (~pattern && pattern&hatch)) { + T* ptrd = ptr((unsigned int)x,(unsigned int)y,(unsigned int)z); + const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; } + } + x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); } + } + } + return *this; + } + + //! Draw a 3D colored line. + template + CImg& draw_line(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const CImg& color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + return draw_line(x0,y0,z0,x1,y1,z1,color.data,opacity,pattern,init_hatch); + } + + //! Draw a 2D textured line. + /** + \param x0 X-coordinate of the starting line point. + \param y0 Y-coordinate of the starting line point. + \param x1 X-coordinate of the ending line point. + \param y1 Y-coordinate of the ending line point. + \param texture Texture image defining the pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param opacity Drawing opacity (optional). + \param pattern An integer whose bits describe the line pattern (optional). + \param init_hatch Flag telling if the hash variable must be reinitialized (optional). + \note + - Clipping is supported but not for texture coordinates. + - Line routine uses the well known Bresenham's algorithm. + \par Example: + \code + CImg img(100,100,1,3,0), texture("texture256x256.ppm"); + const unsigned char color[] = { 255,128,64 }; + img.draw_line(40,40,80,70,texture,0,0,255,255); + \endcode + **/ + template + CImg& draw_line(const int x0, const int y0, + const int x1, const int y1, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty()) return *this; + if (!texture || texture.dim::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (is_overlapped(texture)) return draw_line(x0,y0,x1,y1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=dimx()) return *this; + if (xleft<0) { + const int D = xright - xleft; + yleft-=xleft*(yright - yleft)/D; + txleft-=xleft*(txright - txleft)/D; + tyleft-=xleft*(tyright - tyleft)/D; + xleft = 0; + } + if (xright>=dimx()) { + const int d = xright - dimx(), D = xright - xleft; + yright-=d*(yright - yleft)/D; + txright-=d*(txright - txleft)/D; + tyright-=d*(tyright - tyleft)/D; + xright = dimx()-1; + } + if (ydown<0 || yup>=dimy()) return *this; + if (yup<0) { + const int D = ydown - yup; + xup-=yup*(xdown - xup)/D; + txup-=yup*(txdown - txup)/D; + tyup-=yup*(tydown - tyup)/D; + yup = 0; + } + if (ydown>=dimy()) { + const int d = ydown - dimy(), D = ydown - yup; + xdown-=d*(xdown - xup)/D; + txdown-=d*(txdown - txup)/D; + tydown-=d*(tydown - tyup)/D; + ydown = dimy()-1; + } + T *ptrd0 = ptr(nx0,ny0); + int dx = xright - xleft, dy = ydown - yup; + const bool steep = dy>dx; + if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy); + const int + offx = (nx00?dx:1; + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + cimg_forV(*this,k) { *ptrd = (T)texture(tx,ty,0,k); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + cimg_forV(*this,k) { *ptrd = (T)texture(tx,ty,0,k); ptrd+=wh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + if (pattern&hatch) { + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture(tx,ty,0,k) + *ptrd*copacity); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + T *ptrd = ptrd0; + const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx; + cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture(tx,ty,0,k) + *ptrd*copacity); ptrd+=wh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a 2D textured line, with perspective correction. + template + CImg& draw_line(const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (is_empty() && z0<=0 && z1<=0) return *this; + if (!texture || texture.dim::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (is_overlapped(texture)) return draw_line(x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=dimx()) return *this; + if (xleft<0) { + const int D = xright - xleft; + yleft-=xleft*(yright - yleft)/D; + zleft-=xleft*(zright - zleft)/D; + txleft-=xleft*(txright - txleft)/D; + tyleft-=xleft*(tyright - tyleft)/D; + xleft = 0; + } + if (xright>=dimx()) { + const int d = xright - dimx(), D = xright - xleft; + yright-=d*(yright - yleft)/D; + zright-=d*(zright - zleft)/D; + txright-=d*(txright - txleft)/D; + tyright-=d*(tyright - tyleft)/D; + xright = dimx()-1; + } + if (ydown<0 || yup>=dimy()) return *this; + if (yup<0) { + const int D = ydown - yup; + xup-=yup*(xdown - xup)/D; + zup-=yup*(zdown - zup)/D; + txup-=yup*(txdown - txup)/D; + tyup-=yup*(tydown - tyup)/D; + yup = 0; + } + if (ydown>=dimy()) { + const int d = ydown - dimy(), D = ydown - yup; + xdown-=d*(xdown - xup)/D; + zdown-=d*(zdown - zup)/D; + txdown-=d*(txdown - txup)/D; + tydown-=d*(tydown - tyup)/D; + ydown = dimy()-1; + } + T *ptrd0 = ptr(nx0,ny0); + int dx = xright - xleft, dy = ydown - yup; + const bool steep = dy>dx; + if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy); + const int + offx = (nx00?dx:1; + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + T *ptrd = ptrd0; + cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; } + ptrd0+=offx; + if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } + } + } + return *this; + } + + //! Draw a 2D textured line, with z-buffering and perspective correction. + template + CImg& draw_line(float *const zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + if (!is_empty() && z0>0 && z1>0) { + if (!texture || texture.dim::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (is_overlapped(texture)) return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch); + static unsigned int hatch = ~0U - (~0U>>1); + if (init_hatch) hatch = ~0U - (~0U>>1); + const bool xdir = x0=dimx()) return *this; + if (xleft<0) { + const int D = xright - xleft; + yleft-=xleft*(yright - yleft)/D; + zleft-=xleft*(zright - zleft)/D; + txleft-=xleft*(txright - txleft)/D; + tyleft-=xleft*(tyright - tyleft)/D; + xleft = 0; + } + if (xright>=dimx()) { + const int d = xright - dimx(), D = xright - xleft; + yright-=d*(yright - yleft)/D; + zright-=d*(zright - zleft)/D; + txright-=d*(txright - txleft)/D; + tyright-=d*(tyright - tyleft)/D; + xright = dimx()-1; + } + if (ydown<0 || yup>=dimy()) return *this; + if (yup<0) { + const int D = ydown - yup; + xup-=yup*(xdown - xup)/D; + zup-=yup*(zdown - zup)/D; + txup-=yup*(txdown - txup)/D; + tyup-=yup*(tydown - tyup)/D; + yup = 0; + } + if (ydown>=dimy()) { + const int d = ydown - dimy(), D = ydown - yup; + xdown-=d*(xdown - xup)/D; + zdown-=d*(zdown - zup)/D; + txdown-=d*(txdown - txup)/D; + tydown-=d*(tydown - tyup)/D; + ydown = dimy()-1; + } + T *ptrd0 = ptr(nx0,ny0); + float *ptrz = zbuffer + nx0 + ny0*width; + int dx = xright - xleft, dy = ydown - yup; + const bool steep = dy>dx; + if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy); + const int + offx = (nx00?dx:1; + if (opacity>=1) { + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const float z = Z0 + x*dz/ndx; + if (z>*ptrz) { + *ptrz = z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; } + } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx; + if (z>*ptrz) { + *ptrz = z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } + } else { + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { + if (pattern&hatch) { + const float z = Z0 + x*dz/ndx; + if (z>*ptrz) { + *ptrz = z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; } + } + } + hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } + } else for (int error = dx>>1, x = 0; x<=dx; ++x) { + const float z = Z0 + x*dz/ndx; + if (z>*ptrz) { + *ptrz = z; + const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; + T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; } + } + ptrd0+=offx; ptrz+=offx; + if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offx; error+=dx; } + } + } + } + return *this; + } + + // Inner routine for drawing set of consecutive lines with generic type for coordinates. + template + CImg& _draw_line(const t& points, const unsigned int W, const unsigned int H, + const tc *const color, const float opacity, + const unsigned int pattern, const bool init_hatch) { + if (is_empty() || !points || W<2) return *this; + bool ninit_hatch = init_hatch; + switch (H) { + case 0 : case 1 : + throw CImgArgumentException("CImg<%s>::draw_line() : Given list of points is not valid.", + pixel_type()); + case 2 : { + const int x0 = (int)points(0,0), y0 = (int)points(0,1); + int ox = x0, oy = y0; + for (unsigned int i = 1; i img(100,100,1,3,0); + const unsigned char color[] = { 255,128,64 }; + CImgList points; + points.insert(CImg::vector(0,0)). + .insert(CImg::vector(70,10)). + .insert(CImg::vector(80,60)). + .insert(CImg::vector(10,90)); + img.draw_line(points,color); + \endcode + **/ + template + CImg& draw_line(const CImgList& points, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size())); + return _draw_line(points,points.size,H,color,opacity,pattern,init_hatch); + } + + //! Draw a set of consecutive colored lines in the instance image. + template + CImg& draw_line(const CImgList& points, + const CImg& color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + return draw_line(points,color.data,opacity,pattern,init_hatch); + } + + //! Draw a set of consecutive colored lines in the instance image. + /** + \note + - Similar to the previous function, where the N vertex coordinates are stored as a Nx2 or Nx3 image + (sequence of vectors aligned along the x-axis). + **/ + template + CImg& draw_line(const CImg& points, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + return _draw_line(points,points.width,points.height,color,opacity,pattern,init_hatch); + } + + //! Draw a set of consecutive colored lines in the instance image. + template + CImg& draw_line(const CImg& points, + const CImg& color, const float opacity=1, + const unsigned int pattern=~0U, const bool init_hatch=true) { + return draw_line(points,color.data,opacity,pattern,init_hatch); + } + + // Inner routine for a drawing filled polygon with generic type for coordinates. + template + CImg& _draw_polygon(const t& points, const unsigned int N, + const tc *const color, const float opacity) { + if (is_empty() || !points || N<3) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_polygon() : Specified color is (null).", + pixel_type()); + _draw_scanline(color,opacity); + int xmin = (int)(~0U>>1), xmax = 0, ymin = (int)(~0U>>1), ymax = 0; + { for (unsigned int p = 0; pxmax) xmax = x; + if (yymax) ymax = y; + }} + if (xmax<0 || xmin>=dimx() || ymax<0 || ymin>=dimy()) return *this; + const unsigned int + nymin = ymin<0?0:(unsigned int)ymin, + nymax = ymax>=dimy()?height-1:(unsigned int)ymax, + dy = 1 + nymax - nymin; + CImg X(1+2*N,dy,1,1,0), tmp; + int cx = (int)points(0,0), cy = (int)points(0,1); + for (unsigned int cp = 0, p = 0; pay && cy>ny))?1:0; + for (int x = cx, y = y0, _sx = 1, _sy = 1, + _dx = nx>cx?nx-cx:((_sx=-1),cx-nx), + _dy = y1>y0?y1-y0:((_sy=-1),y0-y1), + _counter = ((_dx-=_dy?_dy*(_dx/_dy):0),_dy), + _err = _dx>>1, + _rx = _dy?(nx-cx)/_dy:0; + _counter>=countermin; + --_counter, y+=_sy, x+=_rx + ((_err-=_dx)<0?_err+=_dy,_sx:0)) + if (y>=0 && y<(int)dy) X(++X(0,y),y) = x; + cp = np; cx = nx; cy = ny; + } else { + const int pp = (cp?cp-1:N-1), py = (int)points(pp,1); + if ((cy>py && ay>cy) || (cy + CImg& draw_polygon(const CImgList& points, + const tc *const color, const float opacity=1) { + if (!points.is_sameY(2)) + throw CImgArgumentException("CImg<%s>::draw_polygon() : Given list of points is not valid.", + pixel_type()); + return _draw_polygon(points,points.size,color,opacity); + } + + //! Draw a filled polygon in the instance image. + template + CImg& draw_polygon(const CImgList& points, + const CImg& color, const float opacity=1) { + return draw_polygon(points,color.data,opacity); + } + + //! Draw a filled polygon in the instance image. + template + CImg& draw_polygon(const CImg& points, + const tc *const color, const float opacity=1) { + if (points.height<2) + throw CImgArgumentException("CImg<%s>::draw_polygon() : Given list of points is not valid.", + pixel_type()); + return _draw_polygon(points,points.width,color,opacity); + } + + //! Draw a filled polygon in the instance image. + template + CImg& draw_polygon(const CImg& points, + const CImg& color, const float opacity=1) { + return draw_polygon(points,color.data,opacity); + } + + // Inner routine for drawing an outlined polygon with generic point coordinates. + template + CImg& _draw_polygon(const t& points, const unsigned int W, const unsigned int H, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (is_empty() || !points || W<3) return *this; + bool ninit_hatch = true; + switch (H) { + case 0 : case 1 : + throw CImgArgumentException("CImg<%s>::draw_polygon() : Given list of points is not valid.", + pixel_type()); + case 2 : { + const int x0 = (int)points(0,0), y0 = (int)points(0,1); + int ox = x0, oy = y0; + for (unsigned int i = 1; i + CImg& draw_polygon(const CImgList& points, + const tc *const color, const float opacity, + const unsigned int pattern) { + unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size())); + return _draw_polygon(points,points.size,H,color,opacity,pattern); + } + + //! Draw a polygon outline. + template + CImg& draw_polygon(const CImgList& points, + const CImg& color, const float opacity, + const unsigned int pattern) { + return draw_polygon(points,color.data,opacity,pattern); + } + + //! Draw a polygon outline. + template + CImg& draw_polygon(const CImg& points, + const tc *const color, const float opacity, + const unsigned int pattern) { + return _draw_polygon(points,points.width,points.height,color,opacity,pattern); + } + + //! Draw a polygon outline. + template + CImg& draw_polygon(const CImg& points, + const CImg& color, const float opacity, + const unsigned int pattern) { + return draw_polygon(points,color.data,opacity,pattern); + } + + //! Draw a cubic spline curve in the instance image. + /** + \param x0 X-coordinate of the starting curve point + \param y0 Y-coordinate of the starting curve point + \param u0 X-coordinate of the starting velocity + \param v0 Y-coordinate of the starting velocity + \param x1 X-coordinate of the ending curve point + \param y1 Y-coordinate of the ending curve point + \param u1 X-coordinate of the ending velocity + \param v1 Y-coordinate of the ending velocity + \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color. + \param precision Curve drawing precision (optional). + \param opacity Drawing opacity (optional). + \param pattern An integer whose bits describe the line pattern (optional). + \param init_hatch If \c true, init hatch motif. + \note + - The curve is a 2D cubic Bezier spline, from the set of specified starting/ending points + and corresponding velocity vectors. + - The spline is drawn as a serie of connected segments. The \p precision parameter sets the + average number of pixels in each drawn segment. + - A cubic Bezier curve is sometimes defined by a set of 4 points { (\p x0,\p y0), (\p xa,\p ya), (\p xb,\p yb), (\p x1,\p y1) } + where (\p x0,\p y0) is the starting point, (\p x1,\p y1) is the ending point and (\p xa,\p ya), (\p xb,\p yb) are two + \e control points. + The starting and ending velocities (\p u0,\p v0) and (\p u1,\p v1) can be deduced easily from the control points as + \p u0 = (\p xa - \p x0), \p v0 = (\p ya - \p y0), \p u1 = (\p x1 - \p xb) and \p v1 = (\p y1 - \p yb). + \par Example: + \code + CImg img(100,100,1,3,0); + const unsigned char color[] = { 255,255,255 }; + img.draw_spline(30,30,0,100,90,40,0,-100,color); + \endcode + **/ + template + CImg& draw_spline(const int x0, const int y0, const float u0, const float v0, + const int x1, const int y1, const float u1, const float v1, + const tc *const color, const float opacity=1, + const float precision=4, const unsigned int pattern=~0U, + const bool init_hatch=true) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_spline() : Specified color is (null)", + pixel_type()); + bool ninit_hatch = init_hatch; + const float + dx = (float)(x1 - x0), + dy = (float)(y1 - y0), + dmax = cimg::max(cimg::abs(dx),cimg::abs(dy)), + ax = -2*dx + u0 + u1, + bx = 3*dx - 2*u0 - u1, + ay = -2*dy + v0 + v1, + by = 3*dy - 2*v0 - v1, + xprecision = dmax>0?precision/dmax:1.0f, + tmax = 1 + (dmax>0?xprecision:0.0f); + int ox = x0, oy = y0; + for (float t = 0; t + CImg& draw_spline(const int x0, const int y0, const float u0, const float v0, + const int x1, const int y1, const float u1, const float v1, + const CImg& color, const float opacity=1, + const float precision=4, const unsigned int pattern=~0U, + const bool init_hatch=true) { + return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,color.data,opacity,precision,pattern,init_hatch); + } + + //! Draw a cubic spline curve in the instance image (for volumetric images). + /** + \note + - Similar to CImg::draw_spline() for a 3D spline in a volumetric image. + **/ + template + CImg& draw_spline(const int x0, const int y0, const int z0, const float u0, const float v0, const float w0, + const int x1, const int y1, const int z1, const float u1, const float v1, const float w1, + const tc *const color, const float opacity=1, + const float precision=4, const unsigned int pattern=~0U, + const bool init_hatch=true) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_spline() : Specified color is (null)", + pixel_type()); + bool ninit_hatch = init_hatch; + const float + dx = (float)(x1 - x0), + dy = (float)(y1 - y0), + dz = (float)(z1 - z0), + dmax = cimg::max(cimg::abs(dx),cimg::abs(dy),cimg::abs(dz)), + ax = -2*dx + u0 + u1, + bx = 3*dx - 2*u0 - u1, + ay = -2*dy + v0 + v1, + by = 3*dy - 2*v0 - v1, + az = -2*dz + w0 + w1, + bz = 3*dz - 2*w0 - w1, + xprecision = dmax>0?precision/dmax:1.0f, + tmax = 1 + (dmax>0?xprecision:0.0f); + int ox = x0, oy = y0, oz = z0; + for (float t = 0; t + CImg& draw_spline(const int x0, const int y0, const int z0, const float u0, const float v0, const float w0, + const int x1, const int y1, const int z1, const float u1, const float v1, const float w1, + const CImg& color, const float opacity=1, + const float precision=4, const unsigned int pattern=~0U, + const bool init_hatch=true) { + return draw_spline(x0,y0,z0,u0,v0,w0,x1,y1,z1,u1,v1,w1,color.data,opacity,precision,pattern,init_hatch); + } + + //! Draw a cubic spline curve in the instance image. + /** + \param x0 X-coordinate of the starting curve point + \param y0 Y-coordinate of the starting curve point + \param u0 X-coordinate of the starting velocity + \param v0 Y-coordinate of the starting velocity + \param x1 X-coordinate of the ending curve point + \param y1 Y-coordinate of the ending curve point + \param u1 X-coordinate of the ending velocity + \param v1 Y-coordinate of the ending velocity + \param texture Texture image defining line pixel colors. + \param tx0 X-coordinate of the starting texture point. + \param ty0 Y-coordinate of the starting texture point. + \param tx1 X-coordinate of the ending texture point. + \param ty1 Y-coordinate of the ending texture point. + \param precision Curve drawing precision (optional). + \param opacity Drawing opacity (optional). + \param pattern An integer whose bits describe the line pattern (optional). + \param init_hatch if \c true, reinit hatch motif. + **/ + template + CImg& draw_spline(const int x0, const int y0, const float u0, const float v0, + const int x1, const int y1, const float u1, const float v1, + const CImg& texture, + const int tx0, const int ty0, const int tx1, const int ty1, + const float opacity=1, + const float precision=4, const unsigned int pattern=~0U, + const bool init_hatch=true) { + if (is_empty()) return *this; + if (!texture || texture.dim::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (is_overlapped(texture)) return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,+texture,tx0,ty0,tx1,ty1,precision,opacity,pattern,init_hatch); + bool ninit_hatch = true; + const float + dx = (float)(x1 - x0), + dy = (float)(y1 - y0), + dmax = cimg::max(cimg::abs(dx),cimg::abs(dy)), + ax = -2*dx + u0 + u1, + bx = 3*dx - 2*u0 - u1, + ay = -2*dy + v0 + v1, + by = 3*dy - 2*v0 - v1, + xprecision = dmax>0?precision/dmax:1.0f, + tmax = 1 + (dmax>0?xprecision:0.0f); + int ox = x0, oy = y0, otx = tx0, oty = ty0; + for (float t1 = 0; t1 + CImg& _draw_spline(const tp& points, const tt& tangents, const unsigned int W, const unsigned int H, + const tc *const color, const float opacity, + const bool close_set, const float precision, + const unsigned int pattern, const bool init_hatch) { + if (is_empty() || !points || !tangents || W<2) return *this; + bool ninit_hatch = init_hatch; + switch (H) { + case 0 : case 1 : + throw CImgArgumentException("CImg<%s>::draw_spline() : Given list of points or tangents is not valid.", + pixel_type()); + case 2 : { + const int x0 = (int)points(0,0), y0 = (int)points(0,1); + const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1); + int ox = x0, oy = y0; + float ou = u0, ov = v0; + for (unsigned int i = 1; i + CImg& _draw_spline(const tp& points, const unsigned int W, const unsigned int H, + const tc *const color, const float opacity, + const bool close_set, const float precision, + const unsigned int pattern, const bool init_hatch) { + if (is_empty() || !points || W<2) return *this; + CImg tangents; + switch (H) { + case 0 : case 1 : + throw CImgArgumentException("CImg<%s>::draw_spline() : Given list of points or tangents is not valid.", + pixel_type()); + case 2 : { + tangents.assign(W,H); + for (unsigned int p = 0; p + CImg& draw_spline(const CImgList& points, const CImgList& tangents, + const tc *const color, const float opacity=1, + const bool close_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()),(unsigned int)(tangents[p].size())); + return _draw_spline(points,tangents,color,opacity,close_set,precision,pattern,init_hatch,points.size,H); + } + + //! Draw a set of consecutive colored splines in the instance image. + template + CImg& draw_spline(const CImgList& points, const CImgList& tangents, + const CImg& color, const float opacity=1, + const bool close_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + return draw_spline(points,tangents,color.data,opacity,close_set,precision,pattern,init_hatch); + } + + //! Draw a set of consecutive colored splines in the instance image. + template + CImg& draw_spline(const CImg& points, const CImg& tangents, + const tc *const color, const float opacity=1, + const bool close_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + return _draw_spline(points,tangents,color,opacity,close_set,precision,pattern,init_hatch,points.width,points.height); + } + + //! Draw a set of consecutive colored splines in the instance image. + template + CImg& draw_spline(const CImg& points, const CImg& tangents, + const CImg& color, const float opacity=1, + const bool close_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + return draw_spline(points,tangents,color.data,opacity,close_set,precision,pattern,init_hatch); + } + + //! Draw a set of consecutive colored splines in the instance image. + template + CImg& draw_spline(const CImgList& points, + const tc *const color, const float opacity=1, + const bool close_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + unsigned int H = ~0U; + cimglist_for(points,p) { const unsigned int s = points[p].size(); if (s + CImg& draw_spline(const CImgList& points, + CImg& color, const float opacity=1, + const bool close_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + return draw_spline(points,color.data,opacity,close_set,precision,pattern,init_hatch); + } + + //! Draw a set of consecutive colored lines in the instance image. + template + CImg& draw_spline(const CImg& points, + const tc *const color, const float opacity=1, + const bool close_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + return _draw_spline(points,color,opacity,close_set,precision,pattern,init_hatch,points.width,points.height); + } + + //! Draw a set of consecutive colored lines in the instance image. + template + CImg& draw_spline(const CImg& points, + const CImg& color, const float opacity=1, + const bool close_set=false, const float precision=4, + const unsigned int pattern=~0U, const bool init_hatch=true) { + return draw_spline(points,color.data,opacity,close_set,precision,pattern,init_hatch); + } + + //! Draw a colored arrow in the instance image. + /** + \param x0 X-coordinate of the starting arrow point (tail). + \param y0 Y-coordinate of the starting arrow point (tail). + \param x1 X-coordinate of the ending arrow point (head). + \param y1 Y-coordinate of the ending arrow point (head). + \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color. + \param angle Aperture angle of the arrow head (optional). + \param length Length of the arrow head. If negative, describes a percentage of the arrow length (optional). + \param opacity Drawing opacity (optional). + \param pattern An integer whose bits describe the line pattern (optional). + \note + - Clipping is supported. + **/ + template + CImg& draw_arrow(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1, + const float angle=30, const float length=-10, + const unsigned int pattern=~0U) { + if (is_empty()) return *this; + const float u = (float)(x0 - x1), v = (float)(y0 - y1), sq = u*u + v*v, + deg = (float)(angle*cimg::valuePI/180), ang = (sq>0)?(float)cimg_std::atan2(v,u):0.0f, + l = (length>=0)?length:-length*(float)cimg_std::sqrt(sq)/100; + if (sq>0) { + const float + cl = (float)cimg_std::cos(ang - deg), sl = (float)cimg_std::sin(ang - deg), + cr = (float)cimg_std::cos(ang + deg), sr = (float)cimg_std::sin(ang + deg); + const int + xl = x1 + (int)(l*cl), yl = y1 + (int)(l*sl), + xr = x1 + (int)(l*cr), yr = y1 + (int)(l*sr), + xc = x1 + (int)((l+1)*(cl+cr))/2, yc = y1 + (int)((l+1)*(sl+sr))/2; + draw_line(x0,y0,xc,yc,color,opacity,pattern).draw_triangle(x1,y1,xl,yl,xr,yr,color,opacity); + } else draw_point(x0,y0,color,opacity); + return *this; + } + + //! Draw a colored arrow in the instance image. + template + CImg& draw_arrow(const int x0, const int y0, + const int x1, const int y1, + const CImg& color, const float opacity=1, + const float angle=30, const float length=-10, + const unsigned int pattern=~0U) { + return draw_arrow(x0,y0,x1,y1,color.data,opacity,angle,length,pattern); + } + + //! Draw an image. + /** + \param sprite Sprite image. + \param x0 X-coordinate of the sprite position. + \param y0 Y-coordinate of the sprite position. + \param z0 Z-coordinate of the sprite position. + \param v0 V-coordinate of the sprite position. + \param opacity Drawing opacity (optional). + \note + - Clipping is supported. + **/ + template + CImg& draw_image(const int x0, const int y0, const int z0, const int v0, + const CImg& sprite, const float opacity=1) { + if (is_empty()) return *this; + if (!sprite) + throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data); + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,v0,+sprite,opacity); + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bv = (v0<0); + const int + lX = sprite.dimx() - (x0 + sprite.dimx()>dimx()?x0 + sprite.dimx() - dimx():0) + (bx?x0:0), + lY = sprite.dimy() - (y0 + sprite.dimy()>dimy()?y0 + sprite.dimy() - dimy():0) + (by?y0:0), + lZ = sprite.dimz() - (z0 + sprite.dimz()>dimz()?z0 + sprite.dimz() - dimz():0) + (bz?z0:0), + lV = sprite.dimv() - (v0 + sprite.dimv()>dimv()?v0 + sprite.dimv() - dimv():0) + (bv?v0:0); + const t + *ptrs = sprite.data - + (bx?x0:0) - + (by?y0*sprite.dimx():0) - + (bz?z0*sprite.dimx()*sprite.dimy():0) - + (bv?v0*sprite.dimx()*sprite.dimy()*sprite.dimz():0); + const unsigned int + offX = width - lX, soffX = sprite.width - lX, + offY = width*(height - lY), soffY = sprite.width*(sprite.height - lY), + offZ = width*height*(depth - lZ), soffZ = sprite.width*sprite.height*(sprite.depth - lZ); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + if (lX>0 && lY>0 && lZ>0 && lV>0) { + T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0); + for (int v = 0; v=1) for (int x = 0; x& draw_image(const int x0, const int y0, const int z0, const int v0, + const CImg& sprite, const float opacity=1) { + if (is_empty()) return *this; + if (!sprite) + throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data); + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,v0,+sprite,opacity); + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bv = (v0<0); + const int + lX = sprite.dimx() - (x0 + sprite.dimx()>dimx()?x0 + sprite.dimx() - dimx():0) + (bx?x0:0), + lY = sprite.dimy() - (y0 + sprite.dimy()>dimy()?y0 + sprite.dimy() - dimy():0) + (by?y0:0), + lZ = sprite.dimz() - (z0 + sprite.dimz()>dimz()?z0 + sprite.dimz() - dimz():0) + (bz?z0:0), + lV = sprite.dimv() - (v0 + sprite.dimv()>dimv()?v0 + sprite.dimv() - dimv():0) + (bv?v0:0); + const T + *ptrs = sprite.data - + (bx?x0:0) - + (by?y0*sprite.dimx():0) - + (bz?z0*sprite.dimx()*sprite.dimy():0) - + (bv?v0*sprite.dimx()*sprite.dimy()*sprite.dimz():0); + const unsigned int + offX = width - lX, soffX = sprite.width - lX, + offY = width*(height - lY), soffY = sprite.width*(sprite.height - lY), + offZ = width*height*(depth - lZ), soffZ = sprite.width*sprite.height*(sprite.depth - lZ), + slX = lX*sizeof(T); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + if (lX>0 && lY>0 && lZ>0 && lV>0) { + T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0); + for (int v = 0; v=1) for (int y = 0; y + CImg& draw_image(const int x0, const int y0, const int z0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,y0,z0,0,sprite,opacity); + } + + //! Draw an image. + template + CImg& draw_image(const int x0, const int y0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,y0,0,sprite,opacity); + } + + //! Draw an image. + template + CImg& draw_image(const int x0, + const CImg& sprite, const float opacity=1) { + return draw_image(x0,0,sprite,opacity); + } + + //! Draw an image. + template + CImg& draw_image(const CImg& sprite, const float opacity=1) { + return draw_image(0,sprite,opacity); + } + + //! Draw a sprite image in the instance image (masked version). + /** + \param sprite Sprite image. + \param mask Mask image. + \param x0 X-coordinate of the sprite position in the instance image. + \param y0 Y-coordinate of the sprite position in the instance image. + \param z0 Z-coordinate of the sprite position in the instance image. + \param v0 V-coordinate of the sprite position in the instance image. + \param mask_valmax Maximum pixel value of the mask image \c mask (optional). + \param opacity Drawing opacity. + \note + - Pixel values of \c mask set the opacity of the corresponding pixels in \c sprite. + - Clipping is supported. + - Dimensions along x,y and z of \p sprite and \p mask must be the same. + **/ + template + CImg& draw_image(const int x0, const int y0, const int z0, const int v0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_valmax=1) { + if (is_empty()) return *this; + if (!sprite) + throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data); + if (!mask) + throw CImgArgumentException("CImg<%s>::draw_image() : Specified mask image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data); + if (is_overlapped(sprite)) return draw_image(x0,y0,z0,v0,+sprite,mask,opacity,mask_valmax); + if (is_overlapped(mask)) return draw_image(x0,y0,z0,v0,sprite,+mask,opacity,mask_valmax); + if (mask.width!=sprite.width || mask.height!=sprite.height || mask.depth!=sprite.depth) + throw CImgArgumentException("CImg<%s>::draw_image() : Mask dimension is (%u,%u,%u,%u), while sprite is (%u,%u,%u,%u)", + pixel_type(),mask.width,mask.height,mask.depth,mask.dim,sprite.width,sprite.height,sprite.depth,sprite.dim); + const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bv = (v0<0); + const int + lX = sprite.dimx() - (x0 + sprite.dimx()>dimx()?x0 + sprite.dimx() - dimx():0) + (bx?x0:0), + lY = sprite.dimy() - (y0 + sprite.dimy()>dimy()?y0 + sprite.dimy() - dimy():0) + (by?y0:0), + lZ = sprite.dimz() - (z0 + sprite.dimz()>dimz()?z0 + sprite.dimz() - dimz():0) + (bz?z0:0), + lV = sprite.dimv() - (v0 + sprite.dimv()>dimv()?v0 + sprite.dimv() - dimv():0) + (bv?v0:0); + const int + coff = -(bx?x0:0)-(by?y0*mask.dimx():0)-(bz?z0*mask.dimx()*mask.dimy():0)-(bv?v0*mask.dimx()*mask.dimy()*mask.dimz():0), + ssize = mask.dimx()*mask.dimy()*mask.dimz(); + const ti *ptrs = sprite.data + coff; + const tm *ptrm = mask.data + coff; + const unsigned int + offX = width - lX, soffX = sprite.width - lX, + offY = width*(height - lY), soffY = sprite.width*(sprite.height - lY), + offZ = width*height*(depth - lZ), soffZ = sprite.width*sprite.height*(sprite.depth - lZ); + if (lX>0 && lY>0 && lZ>0 && lV>0) { + T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0); + for (int v = 0; v + CImg& draw_image(const int x0, const int y0, const int z0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_valmax=1) { + return draw_image(x0,y0,z0,0,sprite,mask,opacity,mask_valmax); + } + + //! Draw an image. + template + CImg& draw_image(const int x0, const int y0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_valmax=1) { + return draw_image(x0,y0,0,sprite,mask,opacity,mask_valmax); + } + + //! Draw an image. + template + CImg& draw_image(const int x0, + const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_valmax=1) { + return draw_image(x0,0,sprite,mask,opacity,mask_valmax); + } + + //! Draw an image. + template + CImg& draw_image(const CImg& sprite, const CImg& mask, const float opacity=1, + const float mask_valmax=1) { + return draw_image(0,sprite,mask,opacity,mask_valmax); + } + + //! Draw a 4D filled rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0,\c v0)-(\c x1,\c y1,\c z1,\c v1). + /** + \param x0 X-coordinate of the upper-left rectangle corner. + \param y0 Y-coordinate of the upper-left rectangle corner. + \param z0 Z-coordinate of the upper-left rectangle corner. + \param v0 V-coordinate of the upper-left rectangle corner. + \param x1 X-coordinate of the lower-right rectangle corner. + \param y1 Y-coordinate of the lower-right rectangle corner. + \param z1 Z-coordinate of the lower-right rectangle corner. + \param v1 V-coordinate of the lower-right rectangle corner. + \param val Scalar value used to fill the rectangle area. + \param opacity Drawing opacity (optional). + \note + - Clipping is supported. + **/ + CImg& draw_rectangle(const int x0, const int y0, const int z0, const int v0, + const int x1, const int y1, const int z1, const int v1, + const T val, const float opacity=1) { + if (is_empty()) return *this; + const bool bx = (x0=dimx()?dimx() - 1 - nx1:0) + (nx0<0?nx0:0), + lY = (1 + ny1 - ny0) + (ny1>=dimy()?dimy() - 1 - ny1:0) + (ny0<0?ny0:0), + lZ = (1 + nz1 - nz0) + (nz1>=dimz()?dimz() - 1 - nz1:0) + (nz0<0?nz0:0), + lV = (1 + nv1 - nv0) + (nv1>=dimv()?dimv() - 1 - nv1:0) + (nv0<0?nv0:0); + const unsigned int offX = width - lX, offY = width*(height - lY), offZ = width*height*(depth - lZ); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + T *ptrd = ptr(nx0<0?0:nx0,ny0<0?0:ny0,nz0<0?0:nz0,nv0<0?0:nv0); + if (lX>0 && lY>0 && lZ>0 && lV>0) + for (int v = 0; v=1) { + if (sizeof(T)!=1) { for (int x = 0; x + CImg& draw_rectangle(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const tc *const color, const float opacity=1) { + if (!color) + throw CImgArgumentException("CImg<%s>::draw_rectangle : specified color is (null)", + pixel_type()); + cimg_forV(*this,k) draw_rectangle(x0,y0,z0,k,x1,y1,z1,k,color[k],opacity); + return *this; + } + + //! Draw a 3D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0)-(\c x1,\c y1,\c z1). + template + CImg& draw_rectangle(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const CImg& color, const float opacity=1) { + return draw_rectangle(x0,y0,z0,x1,y1,z1,color.data,opacity); + } + + //! Draw a 3D outlined colored rectangle in the instance image. + template + CImg& draw_rectangle(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const tc *const color, const float opacity, + const unsigned int pattern) { + return draw_line(x0,y0,z0,x1,y0,z0,color,opacity,pattern,true). + draw_line(x1,y0,z0,x1,y1,z0,color,opacity,pattern,false). + draw_line(x1,y1,z0,x0,y1,z0,color,opacity,pattern,false). + draw_line(x0,y1,z0,x0,y0,z0,color,opacity,pattern,false). + draw_line(x0,y0,z1,x1,y0,z1,color,opacity,pattern,true). + draw_line(x1,y0,z1,x1,y1,z1,color,opacity,pattern,false). + draw_line(x1,y1,z1,x0,y1,z1,color,opacity,pattern,false). + draw_line(x0,y1,z1,x0,y0,z1,color,opacity,pattern,false). + draw_line(x0,y0,z0,x0,y0,z1,color,opacity,pattern,true). + draw_line(x1,y0,z0,x1,y0,z1,color,opacity,pattern,true). + draw_line(x1,y1,z0,x1,y1,z1,color,opacity,pattern,true). + draw_line(x0,y1,z0,x0,y1,z1,color,opacity,pattern,true); + } + + //! Draw a 3D outlined colored rectangle in the instance image. + template + CImg& draw_rectangle(const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1, + const CImg& color, const float opacity, + const unsigned int pattern) { + return draw_rectangle(x0,y0,z0,x1,y1,z1,color.data,opacity,pattern); + } + + //! Draw a 2D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1). + /** + \param x0 X-coordinate of the upper-left rectangle corner. + \param y0 Y-coordinate of the upper-left rectangle corner. + \param x1 X-coordinate of the lower-right rectangle corner. + \param y1 Y-coordinate of the lower-right rectangle corner. + \param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color. + \param opacity Drawing opacity (optional). + \note + - Clipping is supported. + **/ + template + CImg& draw_rectangle(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity=1) { + return draw_rectangle(x0,y0,0,x1,y1,depth-1,color,opacity); + } + + //! Draw a 2D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1). + template + CImg& draw_rectangle(const int x0, const int y0, + const int x1, const int y1, + const CImg& color, const float opacity=1) { + return draw_rectangle(x0,y0,x1,y1,color.data,opacity); + } + + //! Draw a 2D outlined colored rectangle. + template + CImg& draw_rectangle(const int x0, const int y0, + const int x1, const int y1, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (is_empty()) return *this; + if (y0==y1) return draw_line(x0,y0,x1,y0,color,opacity,pattern,true); + if (x0==x1) return draw_line(x0,y0,x0,y1,color,opacity,pattern,true); + const bool bx = (x0 + CImg& draw_rectangle(const int x0, const int y0, + const int x1, const int y1, + const CImg& color, const float opacity, + const unsigned int pattern) { + return draw_rectangle(x0,y0,x1,y1,color.data,opacity,pattern); + } + + // Inner macro for drawing triangles. +#define _cimg_for_triangle1(img,xl,xr,y,x0,y0,x1,y1,x2,y2) \ + for (int y = y0<0?0:y0, \ + xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \ + xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \ + _sxn=1, \ + _sxr=1, \ + _sxl=1, \ + _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \ + _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \ + _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \ + _dyn = y2-y1, \ + _dyr = y2-y0, \ + _dyl = y1-y0, \ + _counter = (_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \ + _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \ + _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \ + cimg::min((int)(img).height-y-1,y2-y)), \ + _errn = _dyn/2, \ + _errr = _dyr/2, \ + _errl = _dyl/2, \ + _rxn = _dyn?(x2-x1)/_dyn:0, \ + _rxr = _dyr?(x2-x0)/_dyr:0, \ + _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \ + (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn); \ + _counter>=0; --_counter, ++y, \ + xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \ + xl+=(y!=y1)?_rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0): \ + (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl)) + +#define _cimg_for_triangle2(img,xl,cl,xr,cr,y,x0,y0,c0,x1,y1,c1,x2,y2,c2) \ + for (int y = y0<0?0:y0, \ + xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \ + cr = y0>=0?c0:(c0-y0*(c2-c0)/(y2-y0)), \ + xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \ + cl = y1>=0?(y0>=0?(y0==y1?c1:c0):(c0-y0*(c1-c0)/(y1-y0))):(c1-y1*(c2-c1)/(y2-y1)), \ + _sxn=1, _scn=1, \ + _sxr=1, _scr=1, \ + _sxl=1, _scl=1, \ + _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \ + _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \ + _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \ + _dcn = c2>c1?c2-c1:(_scn=-1,c1-c2), \ + _dcr = c2>c0?c2-c0:(_scr=-1,c0-c2), \ + _dcl = c1>c0?c1-c0:(_scl=-1,c0-c1), \ + _dyn = y2-y1, \ + _dyr = y2-y0, \ + _dyl = y1-y0, \ + _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \ + _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \ + _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \ + _dcn-=_dyn?_dyn*(_dcn/_dyn):0, \ + _dcr-=_dyr?_dyr*(_dcr/_dyr):0, \ + _dcl-=_dyl?_dyl*(_dcl/_dyl):0, \ + cimg::min((int)(img).height-y-1,y2-y)), \ + _errn = _dyn/2, _errcn = _errn, \ + _errr = _dyr/2, _errcr = _errr, \ + _errl = _dyl/2, _errcl = _errl, \ + _rxn = _dyn?(x2-x1)/_dyn:0, \ + _rcn = _dyn?(c2-c1)/_dyn:0, \ + _rxr = _dyr?(x2-x0)/_dyr:0, \ + _rcr = _dyr?(c2-c0)/_dyr:0, \ + _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \ + (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \ + _rcl = (y0!=y1 && y1>0)?(_dyl?(c1-c0)/_dyl:0): \ + (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcn ); \ + _counter>=0; --_counter, ++y, \ + xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \ + cr+=_rcr+((_errcr-=_dcr)<0?_errcr+=_dyr,_scr:0), \ + xl+=(y!=y1)?(cl+=_rcl+((_errcl-=_dcl)<0?(_errcl+=_dyl,_scl):0), \ + _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \ + (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcl=_rcn, cl=c1, \ + _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl)) + +#define _cimg_for_triangle3(img,xl,txl,tyl,xr,txr,tyr,y,x0,y0,tx0,ty0,x1,y1,tx1,ty1,x2,y2,tx2,ty2) \ + for (int y = y0<0?0:y0, \ + xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \ + txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \ + tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \ + xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \ + txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \ + tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \ + _sxn=1, _stxn=1, _styn=1, \ + _sxr=1, _stxr=1, _styr=1, \ + _sxl=1, _stxl=1, _styl=1, \ + _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \ + _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \ + _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \ + _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \ + _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \ + _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \ + _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \ + _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \ + _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \ + _dyn = y2-y1, \ + _dyr = y2-y0, \ + _dyl = y1-y0, \ + _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \ + _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \ + _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \ + _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \ + _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \ + _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \ + _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \ + _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \ + _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \ + cimg::min((int)(img).height-y-1,y2-y)), \ + _errn = _dyn/2, _errtxn = _errn, _errtyn = _errn, \ + _errr = _dyr/2, _errtxr = _errr, _errtyr = _errr, \ + _errl = _dyl/2, _errtxl = _errl, _errtyl = _errl, \ + _rxn = _dyn?(x2-x1)/_dyn:0, \ + _rtxn = _dyn?(tx2-tx1)/_dyn:0, \ + _rtyn = _dyn?(ty2-ty1)/_dyn:0, \ + _rxr = _dyr?(x2-x0)/_dyr:0, \ + _rtxr = _dyr?(tx2-tx0)/_dyr:0, \ + _rtyr = _dyr?(ty2-ty0)/_dyr:0, \ + _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \ + (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \ + _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \ + (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \ + _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \ + (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ); \ + _counter>=0; --_counter, ++y, \ + xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \ + txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \ + tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \ + xl+=(y!=y1)?(txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \ + tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \ + _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \ + (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \ + _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1,\ + _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl)) + +#define _cimg_for_triangle4(img,xl,cl,txl,tyl,xr,cr,txr,tyr,y,x0,y0,c0,tx0,ty0,x1,y1,c1,tx1,ty1,x2,y2,c2,tx2,ty2) \ + for (int y = y0<0?0:y0, \ + xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \ + cr = y0>=0?c0:(c0-y0*(c2-c0)/(y2-y0)), \ + txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \ + tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \ + xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \ + cl = y1>=0?(y0>=0?(y0==y1?c1:c0):(c0-y0*(c1-c0)/(y1-y0))):(c1-y1*(c2-c1)/(y2-y1)), \ + txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \ + tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \ + _sxn=1, _scn=1, _stxn=1, _styn=1, \ + _sxr=1, _scr=1, _stxr=1, _styr=1, \ + _sxl=1, _scl=1, _stxl=1, _styl=1, \ + _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \ + _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \ + _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \ + _dcn = c2>c1?c2-c1:(_scn=-1,c1-c2), \ + _dcr = c2>c0?c2-c0:(_scr=-1,c0-c2), \ + _dcl = c1>c0?c1-c0:(_scl=-1,c0-c1), \ + _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \ + _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \ + _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \ + _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \ + _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \ + _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \ + _dyn = y2-y1, \ + _dyr = y2-y0, \ + _dyl = y1-y0, \ + _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \ + _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \ + _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \ + _dcn-=_dyn?_dyn*(_dcn/_dyn):0, \ + _dcr-=_dyr?_dyr*(_dcr/_dyr):0, \ + _dcl-=_dyl?_dyl*(_dcl/_dyl):0, \ + _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \ + _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \ + _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \ + _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \ + _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \ + _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \ + cimg::min((int)(img).height-y-1,y2-y)), \ + _errn = _dyn/2, _errcn = _errn, _errtxn = _errn, _errtyn = _errn, \ + _errr = _dyr/2, _errcr = _errr, _errtxr = _errr, _errtyr = _errr, \ + _errl = _dyl/2, _errcl = _errl, _errtxl = _errl, _errtyl = _errl, \ + _rxn = _dyn?(x2-x1)/_dyn:0, \ + _rcn = _dyn?(c2-c1)/_dyn:0, \ + _rtxn = _dyn?(tx2-tx1)/_dyn:0, \ + _rtyn = _dyn?(ty2-ty1)/_dyn:0, \ + _rxr = _dyr?(x2-x0)/_dyr:0, \ + _rcr = _dyr?(c2-c0)/_dyr:0, \ + _rtxr = _dyr?(tx2-tx0)/_dyr:0, \ + _rtyr = _dyr?(ty2-ty0)/_dyr:0, \ + _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \ + (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \ + _rcl = (y0!=y1 && y1>0)?(_dyl?(c1-c0)/_dyl:0): \ + (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcn ), \ + _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \ + (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \ + _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \ + (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ); \ + _counter>=0; --_counter, ++y, \ + xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \ + cr+=_rcr+((_errcr-=_dcr)<0?_errcr+=_dyr,_scr:0), \ + txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \ + tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \ + xl+=(y!=y1)?(cl+=_rcl+((_errcl-=_dcl)<0?(_errcl+=_dyl,_scl):0), \ + txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \ + tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \ + _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \ + (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcl=_rcn, cl=c1, \ + _errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \ + _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1, \ + _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl)) + +#define _cimg_for_triangle5(img,xl,txl,tyl,lxl,lyl,xr,txr,tyr,lxr,lyr,y,x0,y0,tx0,ty0,lx0,ly0,x1,y1,tx1,ty1,lx1,ly1,x2,y2,tx2,ty2,lx2,ly2) \ + for (int y = y0<0?0:y0, \ + xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \ + txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \ + tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \ + lxr = y0>=0?lx0:(lx0-y0*(lx2-lx0)/(y2-y0)), \ + lyr = y0>=0?ly0:(ly0-y0*(ly2-ly0)/(y2-y0)), \ + xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \ + txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \ + tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \ + lxl = y1>=0?(y0>=0?(y0==y1?lx1:lx0):(lx0-y0*(lx1-lx0)/(y1-y0))):(lx1-y1*(lx2-lx1)/(y2-y1)), \ + lyl = y1>=0?(y0>=0?(y0==y1?ly1:ly0):(ly0-y0*(ly1-ly0)/(y1-y0))):(ly1-y1*(ly2-ly1)/(y2-y1)), \ + _sxn=1, _stxn=1, _styn=1, _slxn=1, _slyn=1, \ + _sxr=1, _stxr=1, _styr=1, _slxr=1, _slyr=1, \ + _sxl=1, _stxl=1, _styl=1, _slxl=1, _slyl=1, \ + _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), _dyn = y2-y1, \ + _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), _dyr = y2-y0, \ + _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), _dyl = y1-y0, \ + _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \ + _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \ + _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \ + _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \ + _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \ + _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \ + _dlxn = lx2>lx1?lx2-lx1:(_slxn=-1,lx1-lx2), \ + _dlxr = lx2>lx0?lx2-lx0:(_slxr=-1,lx0-lx2), \ + _dlxl = lx1>lx0?lx1-lx0:(_slxl=-1,lx0-lx1), \ + _dlyn = ly2>ly1?ly2-ly1:(_slyn=-1,ly1-ly2), \ + _dlyr = ly2>ly0?ly2-ly0:(_slyr=-1,ly0-ly2), \ + _dlyl = ly1>ly0?ly1-ly0:(_slyl=-1,ly0-ly1), \ + _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \ + _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \ + _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \ + _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \ + _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \ + _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \ + _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \ + _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \ + _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \ + _dlxn-=_dyn?_dyn*(_dlxn/_dyn):0, \ + _dlxr-=_dyr?_dyr*(_dlxr/_dyr):0, \ + _dlxl-=_dyl?_dyl*(_dlxl/_dyl):0, \ + _dlyn-=_dyn?_dyn*(_dlyn/_dyn):0, \ + _dlyr-=_dyr?_dyr*(_dlyr/_dyr):0, \ + _dlyl-=_dyl?_dyl*(_dlyl/_dyl):0, \ + cimg::min((int)(img).height-y-1,y2-y)), \ + _errn = _dyn/2, _errtxn = _errn, _errtyn = _errn, _errlxn = _errn, _errlyn = _errn, \ + _errr = _dyr/2, _errtxr = _errr, _errtyr = _errr, _errlxr = _errr, _errlyr = _errr, \ + _errl = _dyl/2, _errtxl = _errl, _errtyl = _errl, _errlxl = _errl, _errlyl = _errl, \ + _rxn = _dyn?(x2-x1)/_dyn:0, \ + _rtxn = _dyn?(tx2-tx1)/_dyn:0, \ + _rtyn = _dyn?(ty2-ty1)/_dyn:0, \ + _rlxn = _dyn?(lx2-lx1)/_dyn:0, \ + _rlyn = _dyn?(ly2-ly1)/_dyn:0, \ + _rxr = _dyr?(x2-x0)/_dyr:0, \ + _rtxr = _dyr?(tx2-tx0)/_dyr:0, \ + _rtyr = _dyr?(ty2-ty0)/_dyr:0, \ + _rlxr = _dyr?(lx2-lx0)/_dyr:0, \ + _rlyr = _dyr?(ly2-ly0)/_dyr:0, \ + _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \ + (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \ + _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \ + (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \ + _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \ + (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ), \ + _rlxl = (y0!=y1 && y1>0)?(_dyl?(lx1-lx0)/_dyl:0): \ + (_errlxl=_errlxn, _dlxl=_dlxn, _dyl=_dyn, _slxl=_slxn, _rlxn ), \ + _rlyl = (y0!=y1 && y1>0)?(_dyl?(ly1-ly0)/_dyl:0): \ + (_errlyl=_errlyn, _dlyl=_dlyn, _dyl=_dyn, _slyl=_slyn, _rlyn ); \ + _counter>=0; --_counter, ++y, \ + xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \ + txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \ + tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \ + lxr+=_rlxr+((_errlxr-=_dlxr)<0?_errlxr+=_dyr,_slxr:0), \ + lyr+=_rlyr+((_errlyr-=_dlyr)<0?_errlyr+=_dyr,_slyr:0), \ + xl+=(y!=y1)?(txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \ + tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \ + lxl+=_rlxl+((_errlxl-=_dlxl)<0?(_errlxl+=_dyl,_slxl):0), \ + lyl+=_rlyl+((_errlyl-=_dlyl)<0?(_errlyl+=_dyl,_slyl):0), \ + _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \ + (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \ + _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1, \ + _errlxl=_errlxn, _dlxl=_dlxn, _dyl=_dyn, _slxl=_slxn, _rlxl=_rlxn, lxl=lx1, \ + _errlyl=_errlyn, _dlyl=_dlyn, _dyl=_dyn, _slyl=_slyn, _rlyl=_rlyn, lyl=ly1, \ + _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl)) + + // Draw a colored triangle (inner routine, uses bresenham's algorithm). + template + CImg& _draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, const float opacity, + const float brightness) { + _draw_scanline(color,opacity); + const float nbrightness = brightness<0?0:(brightness>2?2:brightness); + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2); + if (ny0=0) { + if ((nx1 - nx0)*(ny2 - ny0) - (nx2 - nx0)*(ny1 - ny0)<0) + _cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) _draw_scanline(xl,xr,y,color,opacity,nbrightness); + else + _cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) _draw_scanline(xr,xl,y,color,opacity,nbrightness); + } + return *this; + } + + //! Draw a 2D filled colored triangle. + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).", + pixel_type()); + _draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1); + return *this; + } + + //! Draw a 2D filled colored triangle. + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const CImg& color, const float opacity=1) { + return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opacity); + } + + //! Draw a 2D outlined colored triangle. + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).", + pixel_type()); + draw_line(x0,y0,x1,y1,color,opacity,pattern,true). + draw_line(x1,y1,x2,y2,color,opacity,pattern,false). + draw_line(x2,y2,x0,y0,color,opacity,pattern,false); + return *this; + } + + //! Draw a 2D outlined colored triangle. + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const CImg& color, const float opacity, + const unsigned int pattern) { + return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opacity,pattern); + } + + //! Draw a 2D filled colored triangle, with z-buffering. + template + CImg& draw_triangle(float *const zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const int x2, const int y2, const float z2, + const tc *const color, const float opacity=1, + const float brightness=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified color is (null).", + pixel_type()); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0), + nbrightness = brightness<0?0:(brightness>2?2:brightness); + const int whz = width*height*depth, offx = dim*whz; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2; + float nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nz1,nz2); + if (ny0>=dimy() || ny2<0) return *this; + float + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))); + _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) { + if (y==ny1) { zl = nz1; pzl = pzn; } + int xleft = xleft0, xright = xright0; + float zleft = zl, zright = zr; + if (xright=dimx()-1) xright = dimx()-1; + T* ptrd = ptr(xleft,y,0,0); + float *ptrz = zbuffer + xleft + y*width; + if (opacity>=1) { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; } + ptrd-=offx; + } + zleft+=pentez; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(nbrightness*(*col++)); ptrd+=whz; } + ptrd-=offx; + } + zleft+=pentez; + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval); ptrd+=whz; } + ptrd-=offx; + } + zleft+=pentez; + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=whz; } + ptrd-=offx; + } + zleft+=pentez; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(nopacity*nbrightness**(col++) + *ptrd*copacity); ptrd+=whz; } + ptrd-=offx; + } + zleft+=pentez; + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const tc *col = color; + cimg_forV(*this,k) { + const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; + } + ptrd-=offx; + } + zleft+=pentez; + } + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a 2D filled colored triangle, with z-buffering. + template + CImg& draw_triangle(float *const zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const int x2, const int y2, const float z2, + const CImg& color, const float opacity=1, + const float brightness=1) { + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,opacity,brightness); + } + + //! Draw a 2D Gouraud-shaded colored triangle. + /** + \param x0 = X-coordinate of the first corner in the instance image. + \param y0 = Y-coordinate of the first corner in the instance image. + \param x1 = X-coordinate of the second corner in the instance image. + \param y1 = Y-coordinate of the second corner in the instance image. + \param x2 = X-coordinate of the third corner in the instance image. + \param y2 = Y-coordinate of the third corner in the instance image. + \param color = array of dimv() values of type \c T, defining the global drawing color. + \param brightness0 = brightness of the first corner (in [0,2]). + \param brightness1 = brightness of the second corner (in [0,2]). + \param brightness2 = brightness of the third corner (in [0,2]). + \param opacity = opacity of the drawing. + \note Clipping is supported. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, + const float brightness0, + const float brightness1, + const float brightness2, + const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).", + pixel_type()); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + const int whz = width*height*depth, offx = dim*whz-1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256), + nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256), + nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256); + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nc1,nc2); + if (ny0>=dimy() || ny2<0) return *this; + _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) { + int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0; + if (xrightcleft?cright - cleft:cleft - cright, + rc = dx?(cright - cleft)/dx:0, + sc = cright>cleft?1:-1, + ndc = dc-(dx?dx*(dc/dx):0); + int errc = dx>>1; + if (xleft<0 && dx) cleft-=xleft*(cright - cleft)/dx; + if (xleft<0) xleft = 0; + if (xright>=dimx()-1) xright = dimx()-1; + T* ptrd = ptr(xleft,y); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const tc *col = color; + cimg_forV(*this,k) { + *ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256); + ptrd+=whz; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = color; + cimg_forV(*this,k) { + const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + } + return *this; + } + + //! Draw a 2D Gouraud-shaded colored triangle. + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const CImg& color, + const float brightness0, + const float brightness1, + const float brightness2, + const float opacity=1) { + return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,brightness0,brightness1,brightness2,opacity); + } + + //! Draw a 2D Gouraud-shaded colored triangle, with z-buffering. + template + CImg& draw_triangle(float *const zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const int x2, const int y2, const float z2, + const tc *const color, + const float brightness0, + const float brightness1, + const float brightness2, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified color is (null).", + pixel_type()); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + const int whz = width*height*depth, offx = dim*whz; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256), + nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256), + nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256); + float nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nz0,nz2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nz1,nz2,nc1,nc2); + if (ny0>=dimy() || ny2<0) return *this; + float + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))); + _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) { + if (y==ny1) { zl = nz1; pzl = pzn; } + int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0; + float zleft = zl, zright = zr; + if (xrightcleft?cright - cleft:cleft - cright, + rc = dx?(cright-cleft)/dx:0, + sc = cright>cleft?1:-1, + ndc = dc-(dx?dx*(dc/dx):0); + const float pentez = (zright - zleft)/dx; + int errc = dx>>1; + if (xleft<0 && dx) { + cleft-=xleft*(cright - cleft)/dx; + zleft-=xleft*(zright - zleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=dimx()-1) xright = dimx()-1; + T *ptrd = ptr(xleft,y); + float *ptrz = zbuffer + xleft + y*width; + if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { + if (zleft>*ptrz) { + *ptrz = zleft; + const tc *col = color; + cimg_forV(*this,k) { + *ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256); + ptrd+=whz; + } + ptrd-=offx; + } + zleft+=pentez; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { + if (zleft>*ptrz) { + *ptrz = zleft; + const tc *col = color; + cimg_forV(*this,k) { + const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; + } + ptrd-=offx; + } + zleft+=pentez; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a Gouraud triangle with z-buffer consideration. + template + CImg& draw_triangle(float *const zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const int x2, const int y2, const float z2, + const CImg& color, + const float brightness0, + const float brightness1, + const float brightness2, + const float opacity=1) { + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,brightness0,brightness1,brightness2,opacity); + } + + //! Draw a 2D textured triangle. + /** + \param x0 = X-coordinate of the first corner in the instance image. + \param y0 = Y-coordinate of the first corner in the instance image. + \param x1 = X-coordinate of the second corner in the instance image. + \param y1 = Y-coordinate of the second corner in the instance image. + \param x2 = X-coordinate of the third corner in the instance image. + \param y2 = Y-coordinate of the third corner in the instance image. + \param texture = texture image used to fill the triangle. + \param tx0 = X-coordinate of the first corner in the texture image. + \param ty0 = Y-coordinate of the first corner in the texture image. + \param tx1 = X-coordinate of the second corner in the texture image. + \param ty1 = Y-coordinate of the second corner in the texture image. + \param tx2 = X-coordinate of the third corner in the texture image. + \param ty2 = Y-coordinate of the third corner in the texture image. + \param opacity = opacity of the drawing. + \param brightness = brightness of the drawing (in [0,2]). + \note Clipping is supported, but texture coordinates do not support clipping. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float opacity=1, + const float brightness=1) { + if (is_empty()) return *this; + if (!texture || texture.dim::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (is_overlapped(texture)) return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0), + nbrightness = brightness<0?0:(brightness>2?2:brightness); + const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2); + if (ny0>=dimy() || ny2<0) return *this; + _cimg_for_triangle3(*this,xleft0,txleft0,tyleft0,xright0,txright0,tyright0,y, + nx0,ny0,ntx0,nty0,nx1,ny1,ntx1,nty1,nx2,ny2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (xrighttxleft?txright - txleft:txleft - txright, + dty = tyright>tyleft?tyright - tyleft:tyleft - tyright, + rtx = dx?(txright - txleft)/dx:0, + rty = dx?(tyright - tyleft)/dx:0, + stx = txright>txleft?1:-1, + sty = tyright>tyleft?1:-1, + ndtx = dtx - (dx?dx*(dtx/dx):0), + ndty = dty - (dx?dx*(dty/dx):0); + int errtx = dx>>1, errty = errtx; + if (xleft<0 && dx) { + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=dimx()-1) xright = dimx()-1; + T* ptrd = ptr(xleft,y,0,0); + if (opacity>=1) { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const tc *col = texture.ptr(txleft,tyleft); + cimg_forV(*this,k) { + *ptrd = (T)*col; + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const tc *col = texture.ptr(txleft,tyleft); + cimg_forV(*this,k) { + *ptrd = (T)(nbrightness**col); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = texture.ptr(txleft,tyleft); + cimg_forV(*this,k) { + *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const tc *col = texture.ptr(txleft,tyleft); + cimg_forV(*this,k) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const tc *col = texture.ptr(txleft,tyleft); + cimg_forV(*this,k) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = texture.ptr(txleft,tyleft); + cimg_forV(*this,k) { + const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + } + return *this; + } + + //! Draw a 2D textured triangle, with perspective correction. + template + CImg& draw_triangle(const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const int x2, const int y2, const float z2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float opacity=1, + const float brightness=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!texture || texture.dim::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0), + nbrightness = brightness<0?0:(brightness>2?2:brightness); + const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2); + if (ny0>=dimy() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int xleft = xleft0, xright = xright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xright=dimx()-1) xright = dimx()-1; + T* ptrd = ptr(xleft,y,0,0); + if (opacity>=1) { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)*col; + ptrd+=whz; col+=twhz; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)(nbrightness**col); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)((2-nbrightness)**col + (nbrightness-1)*maxval); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + const T val = (T)((2-nbrightness)**col + (nbrightness-1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a 2D textured triangle, with z-buffering and perspective correction. + template + CImg& draw_triangle(float *const zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const int x2, const int y2, const float z2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float opacity=1, + const float brightness=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!texture || texture.dim::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float + nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0), + nbrightness = brightness<0?0:(brightness>2?2:brightness); + const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2); + if (ny0>=dimy() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int xleft = xleft0, xright = xright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xright=dimx()-1) xright = dimx()-1; + T *ptrd = ptr(xleft,y,0,0); + float *ptrz = zbuffer + xleft + y*width; + if (opacity>=1) { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)*col; + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)(nbrightness**col); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)((2-nbrightness)**col + (nbrightness-1)*maxval); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } else { + if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)(nopacity**col + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + const T val = (T)((2-nbrightness)**col + (nbrightness-1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + } + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a 2D Pseudo-Phong-shaded triangle. + /** + \param x0 = X-coordinate of the first corner in the instance image. + \param y0 = Y-coordinate of the first corner in the instance image. + \param x1 = X-coordinate of the second corner in the instance image. + \param y1 = Y-coordinate of the second corner in the instance image. + \param x2 = X-coordinate of the third corner in the instance image. + \param y2 = Y-coordinate of the third corner in the instance image. + \param color = array of dimv() values of type \c T, defining the global drawing color. + \param light = light image. + \param lx0 = X-coordinate of the first corner in the light image. + \param ly0 = Y-coordinate of the first corner in the light image. + \param lx1 = X-coordinate of the second corner in the light image. + \param ly1 = Y-coordinate of the second corner in the light image. + \param lx2 = X-coordinate of the third corner in the light image. + \param ly2 = Y-coordinate of the third corner in the light image. + \param opacity = opacity of the drawing. + \note Clipping is supported, but texture coordinates do not support clipping. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const tc *const color, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).", + pixel_type()); + if (!light) + throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.", + pixel_type(),light.width,light.height,light.depth,light.dim,light.data); + if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,color,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + const int whz = width*height*depth, offx = dim*whz-1; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2); + if (ny0>=dimy() || ny2<0) return *this; + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + if (xrightlxleft?lxright - lxleft:lxleft - lxright, + dly = lyright>lyleft?lyright - lyleft:lyleft - lyright, + rlx = dx?(lxright - lxleft)/dx:0, + rly = dx?(lyright - lyleft)/dx:0, + slx = lxright>lxleft?1:-1, + sly = lyright>lyleft?1:-1, + ndlx = dlx - (dx?dx*(dlx/dx):0), + ndly = dly - (dx?dx*(dly/dx):0); + int errlx = dx>>1, errly = errlx; + if (xleft<0 && dx) { + lxleft-=xleft*(lxright - lxleft)/dx; + lyleft-=xleft*(lyright - lyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=dimx()-1) xright = dimx()-1; + T* ptrd = ptr(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const tl l = light(lxleft,lyleft); + const tc *col = color; + cimg_forV(*this,k) { + *ptrd = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval)); + ptrd+=whz; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x) { + const tl l = light(lxleft,lyleft); + const tc *col = color; + cimg_forV(*this,k) { + const T val = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval)); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + } + return *this; + } + + //! Draw a 2D Pseudo-Phong-shaded triangle. + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const CImg& color, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + } + + //! Draw a 2D Pseudo-Phong-shaded triangle, with z-buffering. + template + CImg& draw_triangle(float *const zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const int x2, const int y2, const float z2, + const tc *const color, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified color is (null).", + pixel_type()); + if (!light) + throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.", + pixel_type(),light.width,light.height,light.depth,light.dim,light.data); + if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color, + +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + const int whz = width*height*depth, offx = dim*whz; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + float nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=dimy() || ny2<0) return *this; + float + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))); + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; pzl = pzn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + float zleft = zl, zright = zr; + if (xrightlxleft?lxright - lxleft:lxleft - lxright, + dly = lyright>lyleft?lyright - lyleft:lyleft - lyright, + rlx = dx?(lxright - lxleft)/dx:0, + rly = dx?(lyright - lyleft)/dx:0, + slx = lxright>lxleft?1:-1, + sly = lyright>lyleft?1:-1, + ndlx = dlx - (dx?dx*(dlx/dx):0), + ndly = dly - (dx?dx*(dly/dx):0); + const float pentez = (zright - zleft)/dx; + int errlx = dx>>1, errly = errlx; + if (xleft<0 && dx) { + zleft-=xleft*(zright - zleft)/dx; + lxleft-=xleft*(lxright - lxleft)/dx; + lyleft-=xleft*(lyright - lyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=dimx()-1) xright = dimx()-1; + T *ptrd = ptr(xleft,y,0,0); + float *ptrz = zbuffer + xleft + y*width; + if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const tl l = light(lxleft,lyleft); + const tc *col = color; + cimg_forV(*this,k) { + const tc cval = *(col++); + *ptrd = (T)(l<1?l*cval:(2-l)*cval+(l-1)*maxval); + ptrd+=whz; + } + ptrd-=offx; + } + zleft+=pentez; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const tl l = light(lxleft,lyleft); + const tc *col = color; + cimg_forV(*this,k) { + const tc cval = *(col++); + const T val = (T)(l<1?l*cval:(2-l)*cval+(l-1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; + } + ptrd-=offx; + } + zleft+=pentez; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; zl+=pzl; + } + return *this; + } + + //! Draw a 2D Pseudo-Phong-shaded triangle, with z-buffering. + template + CImg& draw_triangle(float *const zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const int x2, const int y2, const float z2, + const CImg& color, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + } + + //! Draw a 2D Gouraud-shaded textured triangle. + /** + \param x0 = X-coordinate of the first corner in the instance image. + \param y0 = Y-coordinate of the first corner in the instance image. + \param x1 = X-coordinate of the second corner in the instance image. + \param y1 = Y-coordinate of the second corner in the instance image. + \param x2 = X-coordinate of the third corner in the instance image. + \param y2 = Y-coordinate of the third corner in the instance image. + \param texture = texture image used to fill the triangle. + \param tx0 = X-coordinate of the first corner in the texture image. + \param ty0 = Y-coordinate of the first corner in the texture image. + \param tx1 = X-coordinate of the second corner in the texture image. + \param ty1 = Y-coordinate of the second corner in the texture image. + \param tx2 = X-coordinate of the third corner in the texture image. + \param ty2 = Y-coordinate of the third corner in the texture image. + \param brightness0 = brightness value of the first corner. + \param brightness1 = brightness value of the second corner. + \param brightness2 = brightness value of the third corner. + \param opacity = opacity of the drawing. + \note Clipping is supported, but texture coordinates do not support clipping. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float brightness0, + const float brightness1, + const float brightness2, + const float opacity=1) { + if (is_empty()) return *this; + if (!texture || texture.dim::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (is_overlapped(texture)) + return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2, + nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256), + nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256), + nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256); + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nc1,nc2); + if (ny0>=dimy() || ny2<0) return *this; + _cimg_for_triangle4(*this,xleft0,cleft0,txleft0,tyleft0,xright0,cright0,txright0,tyright0,y, + nx0,ny0,nc0,ntx0,nty0,nx1,ny1,nc1,ntx1,nty1,nx2,ny2,nc2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + cleft = cleft0, cright = cright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (xrightcleft?cright - cleft:cleft - cright, + dtx = txright>txleft?txright - txleft:txleft - txright, + dty = tyright>tyleft?tyright - tyleft:tyleft - tyright, + rc = dx?(cright - cleft)/dx:0, + rtx = dx?(txright - txleft)/dx:0, + rty = dx?(tyright - tyleft)/dx:0, + sc = cright>cleft?1:-1, + stx = txright>txleft?1:-1, + sty = tyright>tyleft?1:-1, + ndc = dc - (dx?dx*(dc/dx):0), + ndtx = dtx - (dx?dx*(dtx/dx):0), + ndty = dty - (dx?dx*(dty/dx):0); + int errc = dx>>1, errtx = errc, errty = errc; + if (xleft<0 && dx) { + cleft-=xleft*(cright - cleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=dimx()-1) xright = dimx()-1; + T* ptrd = ptr(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const tc *col = texture.ptr(txleft,tyleft); + cimg_forV(*this,k) { + *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tc *col = texture.ptr(txleft,tyleft); + cimg_forV(*this,k) { + const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + return *this; + } + + //! Draw a 2D Gouraud-shaded textured triangle, with perspective correction. + template + CImg& draw_triangle(const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const int x2, const int y2, const float z2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float brightness0, + const float brightness1, + const float brightness2, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!texture || texture.dim::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256), + nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256), + nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256); + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2); + if (ny0>=dimy() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + cleft = cleft0, cright = cright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xrightcleft?cright - cleft:cleft - cright, + rc = dx?(cright - cleft)/dx:0, + sc = cright>cleft?1:-1, + ndc = dc - (dx?dx*(dc/dx):0); + const float + pentez = (zright - zleft)/dx, + pentetx = (txright - txleft)/dx, + pentety = (tyright - tyleft)/dx; + int errc = dx>>1; + if (xleft<0 && dx) { + cleft-=xleft*(cright - cleft)/dx; + zleft-=xleft*(zright - zleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=dimx()-1) xright = dimx()-1; + T* ptrd = ptr(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a 2D Gouraud-shaded textured triangle, with z-buffering and perspective correction. + template + CImg& draw_triangle(float *const zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const int x2, const int y2, const float z2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const float brightness0, + const float brightness1, + const float brightness2, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!texture || texture.dim::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2, + brightness0,brightness1,brightness2,opacity); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256), + nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256), + nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256); + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2); + if (ny0>=dimy() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + cleft = cleft0, cright = cright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xrightcleft?cright - cleft:cleft - cright, + rc = dx?(cright - cleft)/dx:0, + sc = cright>cleft?1:-1, + ndc = dc - (dx?dx*(dc/dx):0); + const float + pentez = (zright - zleft)/dx, + pentetx = (txright - txleft)/dx, + pentety = (tyright - tyleft)/dx; + int errc = dx>>1; + if (xleft<0 && dx) { + cleft-=xleft*(cright - cleft)/dx; + zleft-=xleft*(zright - zleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=dimx()-1) xright = dimx()-1; + T* ptrd = ptr(xleft,y); + float *ptrz = zbuffer + xleft + y*width; + if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { + if (zleft>*ptrz) { + *ptrz = zleft; + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { + if (zleft>*ptrz) { + *ptrz = zleft; + const float invz = 1/zleft; + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a 2D Pseudo-Phong-shaded textured triangle. + /** + \param x0 = X-coordinate of the first corner in the instance image. + \param y0 = Y-coordinate of the first corner in the instance image. + \param x1 = X-coordinate of the second corner in the instance image. + \param y1 = Y-coordinate of the second corner in the instance image. + \param x2 = X-coordinate of the third corner in the instance image. + \param y2 = Y-coordinate of the third corner in the instance image. + \param texture = texture image used to fill the triangle. + \param tx0 = X-coordinate of the first corner in the texture image. + \param ty0 = Y-coordinate of the first corner in the texture image. + \param tx1 = X-coordinate of the second corner in the texture image. + \param ty1 = Y-coordinate of the second corner in the texture image. + \param tx2 = X-coordinate of the third corner in the texture image. + \param ty2 = Y-coordinate of the third corner in the texture image. + \param light = light image. + \param lx0 = X-coordinate of the first corner in the light image. + \param ly0 = Y-coordinate of the first corner in the light image. + \param lx1 = X-coordinate of the second corner in the light image. + \param ly1 = Y-coordinate of the second corner in the light image. + \param lx2 = X-coordinate of the third corner in the light image. + \param ly2 = Y-coordinate of the third corner in the light image. + \param opacity = opacity of the drawing. + \note Clipping is supported, but texture coordinates do not support clipping. + **/ + template + CImg& draw_triangle(const int x0, const int y0, + const int x1, const int y1, + const int x2, const int y2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty()) return *this; + if (!texture || texture.dim::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (!light) + throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.", + pixel_type(),light.width,light.height,light.depth,light.dim,light.data); + if (is_overlapped(texture)) return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2); + if (ny0>=dimy() || ny2<0) return *this; + _cimg_for_triangle5(*this,xleft0,lxleft0,lyleft0,txleft0,tyleft0,xright0,lxright0,lyright0,txright0,tyright0,y, + nx0,ny0,nlx0,nly0,ntx0,nty0,nx1,ny1,nlx1,nly1,ntx1,nty1,nx2,ny2,nlx2,nly2,ntx2,nty2) { + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0, + txleft = txleft0, txright = txright0, + tyleft = tyleft0, tyright = tyright0; + if (xrightlxleft?lxright - lxleft:lxleft - lxright, + dly = lyright>lyleft?lyright - lyleft:lyleft - lyright, + dtx = txright>txleft?txright - txleft:txleft - txright, + dty = tyright>tyleft?tyright - tyleft:tyleft - tyright, + rlx = dx?(lxright - lxleft)/dx:0, + rly = dx?(lyright - lyleft)/dx:0, + rtx = dx?(txright - txleft)/dx:0, + rty = dx?(tyright - tyleft)/dx:0, + slx = lxright>lxleft?1:-1, + sly = lyright>lyleft?1:-1, + stx = txright>txleft?1:-1, + sty = tyright>tyleft?1:-1, + ndlx = dlx - (dx?dx*(dlx/dx):0), + ndly = dly - (dx?dx*(dly/dx):0), + ndtx = dtx - (dx?dx*(dtx/dx):0), + ndty = dty - (dx?dx*(dty/dx):0); + int errlx = dx>>1, errly = errlx, errtx = errlx, errty = errlx; + if (xleft<0 && dx) { + lxleft-=xleft*(lxright - lxleft)/dx; + lyleft-=xleft*(lyright - lyleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=dimx()-1) xright = dimx()-1; + T* ptrd = ptr(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const tl l = light(lxleft,lyleft); + const tc *col = texture.ptr(txleft,tyleft); + cimg_forV(*this,k) { + *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } else for (int x = xleft; x<=xright; ++x) { + const tl l = light(lxleft,lyleft); + const tc *col = texture.ptr(txleft,tyleft); + cimg_forV(*this,k) { + const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0); + tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0); + } + } + return *this; + } + + //! Draw a 2D Pseudo-Phong-shaded textured triangle, with perspective correction. + template + CImg& draw_triangle(const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const int x2, const int y2, const float z2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!texture || texture.dim::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (!light) + throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.", + pixel_type(),light.width,light.height,light.depth,light.dim,light.data); + if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=dimy() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xrightlxleft?lxright - lxleft:lxleft - lxright, + dly = lyright>lyleft?lyright - lyleft:lyleft - lyright, + rlx = dx?(lxright - lxleft)/dx:0, + rly = dx?(lyright - lyleft)/dx:0, + slx = lxright>lxleft?1:-1, + sly = lyright>lyleft?1:-1, + ndlx = dlx - (dx?dx*(dlx/dx):0), + ndly = dly - (dx?dx*(dly/dx):0); + const float + pentez = (zright - zleft)/dx, + pentetx = (txright - txleft)/dx, + pentety = (tyright - tyleft)/dx; + int errlx = dx>>1, errly = errlx; + if (xleft<0 && dx) { + zleft-=xleft*(zright - zleft)/dx; + lxleft-=xleft*(lxright - lxleft)/dx; + lyleft-=xleft*(lyright - lyleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=dimx()-1) xright = dimx()-1; + T* ptrd = ptr(xleft,y,0,0); + if (opacity>=1) for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tl l = light(lxleft,lyleft); + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x) { + const float invz = 1/zleft; + const tl l = light(lxleft,lyleft); + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + //! Draw a 2D Pseudo-Phong-shaded textured triangle, with z-buffering and perspective correction. + template + CImg& draw_triangle(float *const zbuffer, + const int x0, const int y0, const float z0, + const int x1, const int y1, const float z1, + const int x2, const int y2, const float z2, + const CImg& texture, + const int tx0, const int ty0, + const int tx1, const int ty1, + const int tx2, const int ty2, + const CImg& light, + const int lx0, const int ly0, + const int lx1, const int ly1, + const int lx2, const int ly2, + const float opacity=1) { + if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; + if (!texture || texture.dim::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.", + pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data); + if (!light) + throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.", + pixel_type(),light.width,light.height,light.depth,light.dim,light.data); + if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2, + +texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2, + texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity); + static const T maxval = (T)cimg::min(cimg::type::max(),cimg::type::max()); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz; + int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, + nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2; + float + ntx0 = tx0/z0, nty0 = ty0/z0, + ntx1 = tx1/z1, nty1 = ty1/z1, + ntx2 = tx2/z2, nty2 = ty2/z2, + nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2; + if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1); + if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2); + if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2); + if (ny0>=dimy() || ny2<0) return *this; + float + ptxl = (ntx1 - ntx0)/(ny1 - ny0), + ptxr = (ntx2 - ntx0)/(ny2 - ny0), + ptxn = (ntx2 - ntx1)/(ny2 - ny1), + ptyl = (nty1 - nty0)/(ny1 - ny0), + ptyr = (nty2 - nty0)/(ny2 - ny0), + ptyn = (nty2 - nty1)/(ny2 - ny1), + pzl = (nz1 - nz0)/(ny1 - ny0), + pzr = (nz2 - nz0)/(ny2 - ny0), + pzn = (nz2 - nz1)/(ny2 - ny1), + zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)), + txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), + tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), + zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))), + txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))), + tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1))); + _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y, + nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) { + if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } + int + xleft = xleft0, xright = xright0, + lxleft = lxleft0, lxright = lxright0, + lyleft = lyleft0, lyright = lyright0; + float + zleft = zl, zright = zr, + txleft = txl, txright = txr, + tyleft = tyl, tyright = tyr; + if (xrightlxleft?lxright - lxleft:lxleft - lxright, + dly = lyright>lyleft?lyright - lyleft:lyleft - lyright, + rlx = dx?(lxright - lxleft)/dx:0, + rly = dx?(lyright - lyleft)/dx:0, + slx = lxright>lxleft?1:-1, + sly = lyright>lyleft?1:-1, + ndlx = dlx - (dx?dx*(dlx/dx):0), + ndly = dly - (dx?dx*(dly/dx):0); + const float + pentez = (zright - zleft)/dx, + pentetx = (txright - txleft)/dx, + pentety = (tyright - tyleft)/dx; + int errlx = dx>>1, errly = errlx; + if (xleft<0 && dx) { + zleft-=xleft*(zright - zleft)/dx; + lxleft-=xleft*(lxright - lxleft)/dx; + lyleft-=xleft*(lyright - lyleft)/dx; + txleft-=xleft*(txright - txleft)/dx; + tyleft-=xleft*(tyright - tyleft)/dx; + } + if (xleft<0) xleft = 0; + if (xright>=dimx()-1) xright = dimx()-1; + T* ptrd = ptr(xleft,y); + float *ptrz = zbuffer + xleft + y*width; + if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const float invz = 1/zleft; + const tl l = light(lxleft,lyleft); + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { + if (zleft>*ptrz) { + *ptrz = zleft; + const float invz = 1/zleft; + const tl l = light(lxleft,lyleft); + const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz)); + cimg_forV(*this,k) { + const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval); + *ptrd = (T)(nopacity*val + *ptrd*copacity); + ptrd+=whz; col+=twhz; + } + ptrd-=offx; + } + zleft+=pentez; txleft+=pentetx; tyleft+=pentety; + lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); + lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); + } + zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } + return *this; + } + + // Draw a 2D ellipse (inner routine). + template + CImg& _draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_ellipse : Specified color is (null).", + pixel_type()); + _draw_scanline(color,opacity); + const float + nr1 = cimg::abs(r1), nr2 = cimg::abs(r2), + norm = (float)cimg_std::sqrt(ru*ru+rv*rv), + u = norm>0?ru/norm:1, + v = norm>0?rv/norm:0, + rmax = cimg::max(nr1,nr2), + l1 = (float)cimg_std::pow(rmax/(nr1>0?nr1:1e-6),2), + l2 = (float)cimg_std::pow(rmax/(nr2>0?nr2:1e-6),2), + a = l1*u*u + l2*v*v, + b = u*v*(l1-l2), + c = l1*v*v + l2*u*u; + const int + yb = (int)cimg_std::sqrt(a*rmax*rmax/(a*c-b*b)), + tymin = y0 - yb - 1, + tymax = y0 + yb + 1, + ymin = tymin<0?0:tymin, + ymax = tymax>=dimy()?height-1:tymax; + int oxmin = 0, oxmax = 0; + bool first_line = true; + for (int y = ymin; y<=ymax; ++y) { + const float + Y = y-y0 + (y0?(float)cimg_std::sqrt(delta)/a:0.0f, + bY = b*Y/a, + fxmin = x0-0.5f-bY-sdelta, + fxmax = x0+0.5f-bY+sdelta; + const int xmin = (int)fxmin, xmax = (int)fxmax; + if (!pattern) _draw_scanline(xmin,xmax,y,color,opacity); + else { + if (first_line) { + if (y0-yb>=0) _draw_scanline(xmin,xmax,y,color,opacity); + else draw_point(xmin,y,color,opacity).draw_point(xmax,y,color,opacity); + first_line = false; + } else { + if (xmin + CImg& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv, + const tc *const color, const float opacity=1) { + return _draw_ellipse(x0,y0,r1,r2,ru,rv,color,opacity,0U); + } + + //! Draw a filled ellipse. + template + CImg& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv, + const CImg& color, const float opacity=1) { + return draw_ellipse(x0,y0,r1,r2,ru,rv,color.data,opacity); + } + + //! Draw a filled ellipse. + /** + \param x0 = X-coordinate of the ellipse center. + \param y0 = Y-coordinate of the ellipse center. + \param tensor = Diffusion tensor describing the ellipse. + \param color = array of dimv() values of type \c T, defining the drawing color. + \param opacity = opacity of the drawing. + **/ + template + CImg& draw_ellipse(const int x0, const int y0, const CImg &tensor, + const tc *const color, const float opacity=1) { + CImgList eig = tensor.get_symmetric_eigen(); + const CImg &val = eig[0], &vec = eig[1]; + return draw_ellipse(x0,y0,val(0),val(1),vec(0,0),vec(0,1),color,opacity); + } + + //! Draw a filled ellipse. + template + CImg& draw_ellipse(const int x0, const int y0, const CImg &tensor, + const CImg& color, const float opacity=1) { + return draw_ellipse(x0,y0,tensor,color.data,opacity); + } + + //! Draw an outlined ellipse. + /** + \param x0 = X-coordinate of the ellipse center. + \param y0 = Y-coordinate of the ellipse center. + \param r1 = First radius of the ellipse. + \param r2 = Second radius of the ellipse. + \param ru = X-coordinate of the orientation vector related to the first radius. + \param rv = Y-coordinate of the orientation vector related to the first radius. + \param color = array of dimv() values of type \c T, defining the drawing color. + \param pattern = If zero, the ellipse is filled, else pattern is an integer whose bits describe the outline pattern. + \param opacity = opacity of the drawing. + **/ + template + CImg& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv, + const tc *const color, const float opacity, + const unsigned int pattern) { + if (pattern) _draw_ellipse(x0,y0,r1,r2,ru,rv,color,opacity,pattern); + return *this; + } + + //! Draw an outlined ellipse. + template + CImg& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv, + const CImg& color, const float opacity, + const unsigned int pattern) { + return draw_ellipse(x0,y0,r1,r2,ru,rv,color.data,opacity,pattern); + } + + //! Draw an outlined ellipse. + /** + \param x0 = X-coordinate of the ellipse center. + \param y0 = Y-coordinate of the ellipse center. + \param tensor = Diffusion tensor describing the ellipse. + \param color = array of dimv() values of type \c T, defining the drawing color. + \param pattern = If zero, the ellipse is filled, else pattern is an integer whose bits describe the outline pattern. + \param opacity = opacity of the drawing. + **/ + template + CImg& draw_ellipse(const int x0, const int y0, const CImg &tensor, + const tc *const color, const float opacity, + const unsigned int pattern) { + CImgList eig = tensor.get_symmetric_eigen(); + const CImg &val = eig[0], &vec = eig[1]; + return draw_ellipse(x0,y0,val(0),val(1),vec(0,0),vec(0,1),color,opacity,pattern); + } + + //! Draw an outlined ellipse. + template + CImg& draw_ellipse(const int x0, const int y0, const CImg &tensor, + const CImg& color, const float opacity, + const unsigned int pattern) { + return draw_ellipse(x0,y0,tensor,color.data,opacity,pattern); + } + + //! Draw a filled circle. + /** + \param x0 X-coordinate of the circle center. + \param y0 Y-coordinate of the circle center. + \param radius Circle radius. + \param color Array of dimv() values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \note + - Circle version of the Bresenham's algorithm is used. + **/ + template + CImg& draw_circle(const int x0, const int y0, int radius, + const tc *const color, const float opacity=1) { + if (!is_empty()) { + if (!color) + throw CImgArgumentException("CImg<%s>::draw_circle : Specified color is (null).", + pixel_type()); + _draw_scanline(color,opacity); + if (radius<0 || x0-radius>=dimx() || y0+radius<0 || y0-radius>=dimy()) return *this; + if (y0>=0 && y0=0) { + const int x1 = x0-x, x2 = x0+x, y1 = y0-y, y2 = y0+y; + if (y1>=0 && y1=0 && y2=0 && y1=0 && y2 + CImg& draw_circle(const int x0, const int y0, int radius, + const CImg& color, const float opacity=1) { + return draw_circle(x0,y0,radius,color.data,opacity); + } + + //! Draw an outlined circle. + /** + \param x0 X-coordinate of the circle center. + \param y0 Y-coordinate of the circle center. + \param radius Circle radius. + \param color Array of dimv() values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + **/ + template + CImg& draw_circle(const int x0, const int y0, int radius, + const tc *const color, const float opacity, + const unsigned int) { + if (!is_empty()) { + if (!color) + throw CImgArgumentException("CImg<%s>::draw_circle : Specified color is (null).", + pixel_type()); + if (radius<0 || x0-radius>=dimx() || y0+radius<0 || y0-radius>=dimy()) return *this; + if (!radius) return draw_point(x0,y0,color,opacity); + draw_point(x0-radius,y0,color,opacity).draw_point(x0+radius,y0,color,opacity). + draw_point(x0,y0-radius,color,opacity).draw_point(x0,y0+radius,color,opacity); + if (radius==1) return *this; + for (int f=1-radius, ddFx=0, ddFy=-(radius<<1), x=0, y=radius; x=0) { f+=(ddFy+=2); --y; } + ++x; ++(f+=(ddFx+=2)); + if (x!=y+1) { + const int x1 = x0-y, x2 = x0+y, y1 = y0-x, y2 = y0+x, x3 = x0-x, x4 = x0+x, y3 = y0-y, y4 = y0+y; + draw_point(x1,y1,color,opacity).draw_point(x1,y2,color,opacity). + draw_point(x2,y1,color,opacity).draw_point(x2,y2,color,opacity); + if (x!=y) + draw_point(x3,y3,color,opacity).draw_point(x4,y4,color,opacity). + draw_point(x4,y3,color,opacity).draw_point(x3,y4,color,opacity); + } + } + } + return *this; + } + + //! Draw an outlined circle. + template + CImg& draw_circle(const int x0, const int y0, int radius, const CImg& color, + const float opacity, + const unsigned int pattern) { + return draw_circle(x0,y0,radius,color.data,opacity,pattern); + } + + // Draw a text (internal). + template + CImg& _draw_text(const int x0, const int y0, const char *const text, + const tc1 *const foreground_color, const tc2 *const background_color, + const float opacity, const CImgList& font) { + if (!text) return *this; + if (!font) + throw CImgArgumentException("CImg<%s>::draw_text() : Specified font (%u,%p) is empty.", + pixel_type(),font.size,font.data); + const int text_length = cimg::strlen(text); + + if (is_empty()) { + // If needed, pre-compute necessary size of the image + int x = 0, y = 0, w = 0; + unsigned char c = 0; + for (int i = 0; iw) w = x; x = 0; break; + case '\t' : x+=4*font[' '].width; break; + default : if (cw) w=x; + y+=font[' '].height; + } + assign(x0+w,y0+y,1,font[' '].dim,0); + if (background_color) cimg_forV(*this,k) get_shared_channel(k).fill((T)background_color[k]); + } + + int x = x0, y = y0; + CImg letter; + for (int i = 0; i& mask = (c+256)<(int)font.size?font[c+256]:font[c]; + if (foreground_color) for (unsigned int p = 0; p=512) draw_image(x,y,letter,mask,opacity,(T)1); + else draw_image(x,y,letter,opacity); + x+=letter.width; + } + } + } + return *this; + } + + //! Draw a text. + /** + \param x0 X-coordinate of the text in the instance image. + \param y0 Y-coordinate of the text in the instance image. + \param foreground_color Array of dimv() values of type \c T, defining the foreground color (0 means 'transparent'). + \param background_color Array of dimv() values of type \c T, defining the background color (0 means 'transparent'). + \param font Font used for drawing text. + \param opacity Drawing opacity. + \param format 'printf'-style format string, followed by arguments. + \note Clipping is supported. + **/ + template + CImg& draw_text(const int x0, const int y0, const char *const text, + const tc1 *const foreground_color, const tc2 *const background_color, + const float opacity, const CImgList& font, ...) { + char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font); + cimg_std::vsprintf(tmp,text,ap); va_end(ap); + return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font); + } + + //! Draw a text. + template + CImg& draw_text(const int x0, const int y0, const char *const text, + const CImg& foreground_color, const CImg& background_color, + const float opacity, const CImgList& font, ...) { + char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font); + cimg_std::vsprintf(tmp,text,ap); va_end(ap); + return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font); + } + + //! Draw a text. + template + CImg& draw_text(const int x0, const int y0, const char *const text, + const tc *const foreground_color, const int background_color, + const float opacity, const CImgList& font, ...) { + char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font); + cimg_std::vsprintf(tmp,text,ap); va_end(ap); + return _draw_text(x0,y0,tmp,foreground_color,(tc*)background_color,opacity,font); + } + + //! Draw a text. + template + CImg& draw_text(const int x0, const int y0, const char *const text, + const int foreground_color, const tc *const background_color, + const float opacity, const CImgList& font, ...) { + char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font); + cimg_std::vsprintf(tmp,text,ap); va_end(ap); + return _draw_text(x0,y0,tmp,(tc*)foreground_color,background_color,opacity,font); + } + + //! Draw a text. + /** + \param x0 X-coordinate of the text in the instance image. + \param y0 Y-coordinate of the text in the instance image. + \param foreground_color Array of dimv() values of type \c T, defining the foreground color (0 means 'transparent'). + \param background_color Array of dimv() values of type \c T, defining the background color (0 means 'transparent'). + \param font_size Size of the font (nearest match). + \param opacity Drawing opacity. + \param format 'printf'-style format string, followed by arguments. + \note Clipping is supported. + **/ + template + CImg& draw_text(const int x0, const int y0, const char *const text, + const tc1 *const foreground_color, const tc2 *const background_color, + const float opacity=1, const unsigned int font_size=11, ...) { + static CImgList font; + static unsigned int fsize = 0; + if (fsize!=font_size) { font = CImgList::font(font_size); fsize = font_size; } + char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap); + return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font); + } + + //! Draw a text. + template + CImg& draw_text(const int x0, const int y0, const char *const text, + const CImg& foreground_color, const CImg& background_color, + const float opacity=1, const unsigned int font_size=11, ...) { + static CImgList font; + static unsigned int fsize = 0; + if (fsize!=font_size) { font = CImgList::font(font_size); fsize = font_size; } + char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap); + return _draw_text(x0,y0,tmp,foreground_color.data,background_color.data,opacity,font); + } + + //! Draw a text. + template + CImg& draw_text(const int x0, const int y0, const char *const text, + const tc *const foreground_color, const int background_color=0, + const float opacity=1, const unsigned int font_size=11, ...) { + static CImgList font; + static unsigned int fsize = 0; + if (fsize!=font_size) { font = CImgList::font(font_size); fsize = font_size; } + char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap); + return _draw_text(x0,y0,tmp,foreground_color,(const tc*)background_color,opacity,font); + } + + //! Draw a text. + template + CImg& draw_text(const int x0, const int y0, const char *const text, + const int foreground_color, const tc *const background_color, + const float opacity=1, const unsigned int font_size=11, ...) { + static CImgList font; + static unsigned int fsize = 0; + if (fsize!=font_size) { font = CImgList::font(font_size); fsize = font_size; } + char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap); + return _draw_text(x0,y0,tmp,(tc*)foreground_color,background_color,opacity,font); + } + + //! Draw a vector field in the instance image, using a colormap. + /** + \param flow Image of 2d vectors used as input data. + \param color Image of dimv()-D vectors corresponding to the color of each arrow. + \param sampling Length (in pixels) between each arrow. + \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length). + \param quiver_type Type of plot. Can be 0 (arrows) or 1 (segments). + \param opacity Opacity of the drawing. + \param pattern Used pattern to draw lines. + \note Clipping is supported. + **/ + template + CImg& draw_quiver(const CImg& flow, + const t2 *const color, const float opacity=1, + const unsigned int sampling=25, const float factor=-20, + const int quiver_type=0, const unsigned int pattern=~0U) { + return draw_quiver(flow,CImg(color,dim,1,1,1,true),opacity,sampling,factor,quiver_type,pattern); + } + + //! Draw a vector field in the instance image, using a colormap. + /** + \param flow Image of 2d vectors used as input data. + \param color Image of dimv()-D vectors corresponding to the color of each arrow. + \param sampling Length (in pixels) between each arrow. + \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length). + \param quiver_type Type of plot. Can be 0 (arrows) or 1 (segments). + \param opacity Opacity of the drawing. + \param pattern Used pattern to draw lines. + \note Clipping is supported. + **/ + template + CImg& draw_quiver(const CImg& flow, + const CImg& color, const float opacity=1, + const unsigned int sampling=25, const float factor=-20, + const int quiver_type=0, const unsigned int pattern=~0U) { + if (!is_empty()) { + if (!flow || flow.dim!=2) + throw CImgArgumentException("CImg<%s>::draw_quiver() : Specified flow (%u,%u,%u,%u,%p) has wrong dimensions.", + pixel_type(),flow.width,flow.height,flow.depth,flow.dim,flow.data); + if (sampling<=0) + throw CImgArgumentException("CImg<%s>::draw_quiver() : Incorrect sampling value = %g", + pixel_type(),sampling); + const bool colorfield = (color.width==flow.width && color.height==flow.height && color.depth==1 && color.dim==dim); + if (is_overlapped(flow)) return draw_quiver(+flow,color,opacity,sampling,factor,quiver_type,pattern); + + float vmax,fact; + if (factor<=0) { + float m, M = (float)flow.get_pointwise_norm(2).maxmin(m); + vmax = (float)cimg::max(cimg::abs(m),cimg::abs(M)); + fact = -factor; + } else { fact = factor; vmax = 1; } + + for (unsigned int y=sampling/2; y + CImg& draw_graph(const CImg& data, + const tc *const color, const float opacity=1, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const double ymin=0, const double ymax=0, + const unsigned int pattern=~0U) { + if (is_empty() || height<=1) return *this;; + const unsigned long siz = data.size(); + if (!color) + throw CImgArgumentException("CImg<%s>::draw_graph() : Specified color is (null)", + pixel_type()); + tc *color1 = 0, *color2 = 0; + if (plot_type==3) { + color1 = new tc[dim]; color2 = new tc[dim]; + cimg_forV(*this,k) { color1[k] = (tc)(color[k]*0.6f); color2[k] = (tc)(color[k]*0.3f); } + } + + double m = ymin, M = ymax; + if (ymin==ymax) m = (double)data.maxmin(M); + if (m==M) { --m; ++M; } + const float ca = (float)(M-m)/(height-1); + bool init_hatch = true; + + // Draw graph edges + switch (plot_type%4) { + case 1 : { // Segments + int oX = 0, oY = (int)((data[0]-m)/ca); + for (unsigned long off = 1; off ndata = data.get_shared_points(0,siz-1); + int oY = (int)((data[0]-m)/ca); + cimg_forX(*this,x) { + const int Y = (int)((ndata._cubic_atX((float)x*ndata.width/width)-m)/ca); + if (x>0) draw_line(x,oY,x+1,Y,color,opacity,pattern,init_hatch); + init_hatch = false; + oY = Y; + } + } break; + case 3 : { // Bars + const int Y0 = (int)(-m/ca); + int oX = 0; + cimg_foroff(data,off) { + const int + X = (off+1)*width/siz-1, + Y = (int)((data[off]-m)/ca); + draw_rectangle(oX,Y0,X,Y,color1,opacity). + draw_line(oX,Y,oX,Y0,color2,opacity). + draw_line(oX,Y0,X,Y0,Y<=Y0?color2:color,opacity). + draw_line(X,Y,X,Y0,color,opacity). + draw_line(oX,Y,X,Y,Y<=Y0?color:color2,opacity); + oX = X+1; + } + } break; + default : break; // No edges + } + + // Draw graph points + switch (vertex_type%8) { + case 1 : { // Point + cimg_foroff(data,off) { + const int X = off*width/siz, Y = (int)((data[off]-m)/ca); + draw_point(X,Y,color,opacity); + } + } break; + case 2 : { // Standard Cross + cimg_foroff(data,off) { + const int X = off*width/siz, Y = (int)((data[off]-m)/ca); + draw_line(X-3,Y,X+3,Y,color,opacity).draw_line(X,Y-3,X,Y+3,color,opacity); + } + } break; + case 3 : { // Rotated Cross + cimg_foroff(data,off) { + const int X = off*width/siz, Y = (int)((data[off]-m)/ca); + draw_line(X-3,Y-3,X+3,Y+3,color,opacity).draw_line(X-3,Y+3,X+3,Y-3,color,opacity); + } + } break; + case 4 : { // Filled Circle + cimg_foroff(data,off) { + const int X = off*width/siz, Y = (int)((data[off]-m)/ca); + draw_circle(X,Y,3,color,opacity); + } + } break; + case 5 : { // Outlined circle + cimg_foroff(data,off) { + const int X = off*width/siz, Y = (int)((data[off]-m)/ca); + draw_circle(X,Y,3,color,opacity,0U); + } + } break; + case 6 : { // Square + cimg_foroff(data,off) { + const int X = off*width/siz, Y = (int)((data[off]-m)/ca); + draw_rectangle(X-3,Y-3,X+3,Y+3,color,opacity,~0U); + } + } break; + case 7 : { // Diamond + cimg_foroff(data,off) { + const int X = off*width/siz, Y = (int)((data[off]-m)/ca); + draw_line(X,Y-4,X+4,Y,color,opacity). + draw_line(X+4,Y,X,Y+4,color,opacity). + draw_line(X,Y+4,X-4,Y,color,opacity). + draw_line(X-4,Y,X,Y-4,color,opacity); + } + } break; + default : break; // No vertices + } + + if (color1) delete[] color1; if (color2) delete[] color2; + return *this; + } + + //! Draw a 1D graph on the instance image. + template + CImg& draw_graph(const CImg& data, + const CImg& color, const float opacity=1, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const double ymin=0, const double ymax=0, + const unsigned int pattern=~0U) { + return draw_graph(data,color.data,opacity,plot_type,vertex_type,ymin,ymax,pattern); + } + + //! Draw a labeled horizontal axis on the instance image. + /** + \param xvalues Lower bound of the x-range. + \param y Y-coordinate of the horizontal axis in the instance image. + \param color Array of dimv() values of type \c T, defining the drawing color. + \param opacity Drawing opacity. + \param pattern Drawing pattern. + \param opacity_out Drawing opacity of 'outside' axes. + \note if \c precision==0, precision of the labels is automatically computed. + **/ + template + CImg& draw_axis(const CImg& xvalues, const int y, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U) { + if (!is_empty()) { + int siz = (int)xvalues.size()-1; + if (siz<=0) draw_line(0,y,width-1,y,color,opacity,pattern); + else { + if (xvalues[0] + CImg& draw_axis(const CImg& xvalues, const int y, + const CImg& color, const float opacity=1, + const unsigned int pattern=~0U) { + return draw_axis(xvalues,y,color.data,opacity,pattern); + } + + //! Draw a labeled vertical axis on the instance image. + template + CImg& draw_axis(const int x, const CImg& yvalues, + const tc *const color, const float opacity=1, + const unsigned int pattern=~0U) { + if (!is_empty()) { + int siz = (int)yvalues.size()-1; + if (siz<=0) draw_line(x,0,x,height-1,color,opacity,pattern); + else { + if (yvalues[0]=dimy()-11?dimy()-11:tmp), + xt = x-(int)cimg::strlen(txt)*7; + draw_point(x-1,yi,color,opacity).draw_point(x+1,yi,color,opacity); + if (xt>0) draw_text(xt,nyi,txt,color,(tc*)0,opacity,11); + else draw_text(x+3,nyi,txt,color,(tc*)0,opacity,11); + } + } + } + return *this; + } + + //! Draw a labeled vertical axis on the instance image. + template + CImg& draw_axis(const int x, const CImg& yvalues, + const CImg& color, const float opacity=1, + const unsigned int pattern=~0U) { + return draw_axis(x,yvalues,color.data,opacity,pattern); + } + + //! Draw a labeled horizontal+vertical axis on the instance image. + template + CImg& draw_axis(const CImg& xvalues, const CImg& yvalues, + const tc *const color, const float opacity=1, + const unsigned int patternx=~0U, const unsigned int patterny=~0U) { + if (!is_empty()) { + const CImg nxvalues(xvalues.data,xvalues.size(),1,1,1,true); + const int sizx = (int)xvalues.size()-1, wm1 = (int)(width)-1; + if (sizx>0) { + float ox = (float)nxvalues[0]; + for (unsigned int x = 1; x nyvalues(yvalues.data,yvalues.size(),1,1,1,true); + const int sizy = (int)yvalues.size()-1, hm1 = (int)(height)-1; + if (sizy>0) { + float oy = (float)nyvalues[0]; + for (unsigned int y = 1; y + CImg& draw_axis(const CImg& xvalues, const CImg& yvalues, + const CImg& color, const float opacity=1, + const unsigned int patternx=~0U, const unsigned int patterny=~0U) { + return draw_axis(xvalues,yvalues,color.data,opacity,patternx,patterny); + } + + //! Draw a labeled horizontal+vertical axis on the instance image. + template + CImg& draw_axis(const float x0, const float x1, const float y0, const float y1, + const tc *const color, const float opacity=1, + const int subdivisionx=-60, const int subdivisiony=-60, + const float precisionx=0, const float precisiony=0, + const unsigned int patternx=~0U, const unsigned int patterny=~0U) { + if (!is_empty()) { + const float + dx = cimg::abs(x1-x0), dy = cimg::abs(y1-y0), + px = (precisionx==0)?(float)cimg_std::pow(10.0,(int)cimg_std::log10(dx)-2.0):precisionx, + py = (precisiony==0)?(float)cimg_std::pow(10.0,(int)cimg_std::log10(dy)-2.0):precisiony; + draw_axis(CImg::sequence(subdivisionx>0?subdivisionx:1-dimx()/subdivisionx,x0,x1).round(px), + CImg::sequence(subdivisiony>0?subdivisiony:1-dimy()/subdivisiony,y0,y1).round(py), + color,opacity,patternx,patterny); + } + return *this; + } + + //! Draw a labeled horizontal+vertical axis on the instance image. + template + CImg& draw_axis(const float x0, const float x1, const float y0, const float y1, + const CImg& color, const float opacity=1, + const int subdivisionx=-60, const int subdivisiony=-60, + const float precisionx=0, const float precisiony=0, + const unsigned int patternx=~0U, const unsigned int patterny=~0U) { + return draw_axis(x0,x1,y0,y1,color.data,opacity,subdivisionx,subdivisiony,precisionx,precisiony,patternx,patterny); + } + + //! Draw grid. + template + CImg& draw_grid(const CImg& xvalues, const CImg& yvalues, + const tc *const color, const float opacity=1, + const unsigned int patternx=~0U, const unsigned int patterny=~0U) { + if (!is_empty()) { + if (xvalues) cimg_foroff(xvalues,x) { + const int xi = (int)xvalues[x]; + if (xi>=0 && xi=0 && yi + CImg& draw_grid(const CImg& xvalues, const CImg& yvalues, + const CImg& color, const float opacity=1, + const unsigned int patternx=~0U, const unsigned int patterny=~0U) { + return draw_grid(xvalues,yvalues,color.data,opacity,patternx,patterny); + } + + //! Draw grid. + template + CImg& draw_grid(const float deltax, const float deltay, + const float offsetx, const float offsety, + const bool invertx, const bool inverty, + const tc *const color, const float opacity=1, + const unsigned int patternx=~0U, const unsigned int patterny=~0U) { + CImg seqx, seqy; + if (deltax!=0) { + const float dx = deltax>0?deltax:width*-deltax/100; + const unsigned int nx = (unsigned int)(width/dx); + seqx = CImg::sequence(1+nx,0,(unsigned int)(dx*nx)); + if (offsetx) cimg_foroff(seqx,x) seqx(x) = (unsigned int)cimg::mod(seqx(x)+offsetx,(float)width); + if (invertx) cimg_foroff(seqx,x) seqx(x) = width-1-seqx(x); + } + + if (deltay!=0) { + const float dy = deltay>0?deltay:height*-deltay/100; + const unsigned int ny = (unsigned int)(height/dy); + seqy = CImg::sequence(1+ny,0,(unsigned int)(dy*ny)); + if (offsety) cimg_foroff(seqy,y) seqy(y) = (unsigned int)cimg::mod(seqy(y)+offsety,(float)height); + if (inverty) cimg_foroff(seqy,y) seqy(y) = height-1-seqy(y); + } + return draw_grid(seqx,seqy,color,opacity,patternx,patterny); + } + + //! Draw grid. + template + CImg& draw_grid(const float deltax, const float deltay, + const float offsetx, const float offsety, + const bool invertx, const bool inverty, + const CImg& color, const float opacity=1, + const unsigned int patternx=~0U, const unsigned int patterny=~0U) { + return draw_grid(deltax,deltay,offsetx,offsety,invertx,inverty,color.data,opacity,patternx,patterny); + } + + //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image. + /** + \param x X-coordinate of the starting point of the region to fill. + \param y Y-coordinate of the starting point of the region to fill. + \param z Z-coordinate of the starting point of the region to fill. + \param color An array of dimv() values of type \c T, defining the drawing color. + \param region Image that will contain the mask of the filled region mask, as an output. + \param sigma Tolerance concerning neighborhood values. + \param opacity Opacity of the drawing. + \param high_connexity Tells if 8-connexity must be used (only for 2D images). + \return \p region is initialized with the binary mask of the filled region. + **/ + template + CImg& draw_fill(const int x, const int y, const int z, + const tc *const color, const float opacity, + CImg& region, const float sigma=0, + const bool high_connexity=false) { + +#define _cimg_draw_fill_test(x,y,z,res) if (region(x,y,z)) res = false; else { \ + res = true; \ + const T *reference_col = reference_color.ptr() + dim, *ptrs = ptr(x,y,z) + siz; \ + for (unsigned int i = dim; res && i; --i) { ptrs-=whz; res = (cimg::abs(*ptrs - *(--reference_col))<=sigma); } \ + region(x,y,z) = (t)(res?1:noregion); \ +} + +#define _cimg_draw_fill_set(x,y,z) { \ + const tc *col = color; \ + T *ptrd = ptr(x,y,z); \ + if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; } \ + else cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; } \ +} + +#define _cimg_draw_fill_insert(x,y,z) { \ + if (posr1>=remaining.height) remaining.resize(3,remaining.height<<1,1,1,0); \ + unsigned int *ptrr = remaining.ptr(0,posr1); \ + *(ptrr++) = x; *(ptrr++) = y; *(ptrr++) = z; ++posr1; \ +} + +#define _cimg_draw_fill_test_neighbor(x,y,z,cond) if (cond) { \ + const unsigned int tx = x, ty = y, tz = z; \ + _cimg_draw_fill_test(tx,ty,tz,res); if (res) _cimg_draw_fill_insert(tx,ty,tz); \ +} + + if (!color) + throw CImgArgumentException("CImg<%s>::draw_fill() : Specified color is (null).", + pixel_type()); + region.assign(width,height,depth,1,(t)0); + if (x>=0 && x=0 && y=0 && z1; + const CImg reference_color = get_vector_at(x,y,z); + CImg remaining(3,512,1,1,0); + remaining(0,0) = x; remaining(1,0) = y; remaining(2,0) = z; + unsigned int posr0 = 0, posr1 = 1; + region(x,y,z) = (t)1; + const t noregion = ((t)1==(t)2)?(t)0:(t)(-1); + if (threed) do { // 3D version of the filling algorithm + const unsigned int *pcurr = remaining.ptr(0,posr0++), xc = *(pcurr++), yc = *(pcurr++), zc = *(pcurr++); + if (posr0>=512) { remaining.translate(0,posr0); posr1-=posr0; posr0 = 0; } + bool cont, res; + unsigned int nxc = xc; + do { // X-backward + _cimg_draw_fill_set(nxc,yc,zc); + _cimg_draw_fill_test_neighbor(nxc,yc-1,zc,yc!=0); + _cimg_draw_fill_test_neighbor(nxc,yc+1,zc,ycposr0); + else do { // 2D version of the filling algorithm + const unsigned int *pcurr = remaining.ptr(0,posr0++), xc = *(pcurr++), yc = *(pcurr++); + if (posr0>=512) { remaining.translate(0,posr0); posr1-=posr0; posr0 = 0; } + bool cont, res; + unsigned int nxc = xc; + do { // X-backward + _cimg_draw_fill_set(nxc,yc,0); + _cimg_draw_fill_test_neighbor(nxc,yc-1,0,yc!=0); + _cimg_draw_fill_test_neighbor(nxc,yc+1,0,ycposr0); + if (noregion) cimg_for(region,ptr,t) if (*ptr==noregion) *ptr = (t)0; + } + return *this; + } + + //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image. + template + CImg& draw_fill(const int x, const int y, const int z, + const CImg& color, const float opacity, + CImg& region, const float sigma=0, const bool high_connexity=false) { + return draw_fill(x,y,z,color.data,opacity,region,sigma,high_connexity); + } + + //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image. + /** + \param x = X-coordinate of the starting point of the region to fill. + \param y = Y-coordinate of the starting point of the region to fill. + \param z = Z-coordinate of the starting point of the region to fill. + \param color = an array of dimv() values of type \c T, defining the drawing color. + \param sigma = tolerance concerning neighborhood values. + \param opacity = opacity of the drawing. + **/ + template + CImg& draw_fill(const int x, const int y, const int z, + const tc *const color, const float opacity=1, + const float sigma=0, const bool high_connexity=false) { + CImg tmp; + return draw_fill(x,y,z,color,opacity,tmp,sigma,high_connexity); + } + + //! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image. + template + CImg& draw_fill(const int x, const int y, const int z, + const CImg& color, const float opacity=1, + const float sigma=0, const bool high_connexity=false) { + return draw_fill(x,y,z,color.data,opacity,sigma,high_connexity); + } + + //! Draw a 2D filled region starting from a point (\c x,\c y) in the instance image. + /** + \param x = X-coordinate of the starting point of the region to fill. + \param y = Y-coordinate of the starting point of the region to fill. + \param color = an array of dimv() values of type \c T, defining the drawing color. + \param sigma = tolerance concerning neighborhood values. + \param opacity = opacity of the drawing. + **/ + template + CImg& draw_fill(const int x, const int y, + const tc *const color, const float opacity=1, + const float sigma=0, const bool high_connexity=false) { + CImg tmp; + return draw_fill(x,y,0,color,opacity,tmp,sigma,high_connexity); + } + + //! Draw a 2D filled region starting from a point (\c x,\c y) in the instance image. + template + CImg& draw_fill(const int x, const int y, + const CImg& color, const float opacity=1, + const float sigma=0, const bool high_connexity=false) { + return draw_fill(x,y,color.data,opacity,sigma,high_connexity); + } + + //! Draw a plasma random texture. + /** + \param x0 = X-coordinate of the upper-left corner of the plasma. + \param y0 = Y-coordinate of the upper-left corner of the plasma. + \param x1 = X-coordinate of the lower-right corner of the plasma. + \param y1 = Y-coordinate of the lower-right corner of the plasma. + \param alpha = Alpha-parameter of the plasma. + \param beta = Beta-parameter of the plasma. + \param opacity = opacity of the drawing. + **/ + CImg& draw_plasma(const int x0, const int y0, const int x1, const int y1, + const float alpha=1, const float beta=1, + const float opacity=1) { + if (!is_empty()) { + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + int nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1; + if (nx1=dimx()) nx1 = width-1; + if (ny0<0) ny0 = 0; + if (ny1>=dimy()) ny1 = height-1; + const int xc = (nx0+nx1)/2, yc = (ny0+ny1)/2, dx = (xc-nx0), dy = (yc-ny0); + const Tfloat dc = (Tfloat)(cimg_std::sqrt((float)(dx*dx+dy*dy))*alpha + beta); + Tfloat val = 0; + cimg_forV(*this,k) { + if (opacity>=1) { + const Tfloat + val0 = (Tfloat)((*this)(nx0,ny0,0,k)), val1 = (Tfloat)((*this)(nx1,ny0,0,k)), + val2 = (Tfloat)((*this)(nx0,ny1,0,k)), val3 = (Tfloat)((*this)(nx1,ny1,0,k)); + (*this)(xc,ny0,0,k) = (T)((val0+val1)/2); + (*this)(xc,ny1,0,k) = (T)((val2+val3)/2); + (*this)(nx0,yc,0,k) = (T)((val0+val2)/2); + (*this)(nx1,yc,0,k) = (T)((val1+val3)/2); + do { + val = (Tfloat)(0.25f*((Tfloat)((*this)(nx0,ny0,0,k)) + + (Tfloat)((*this)(nx1,ny0,0,k)) + + (Tfloat)((*this)(nx1,ny1,0,k)) + + (Tfloat)((*this)(nx0,ny1,0,k))) + + dc*cimg::grand()); + } while (val<(Tfloat)cimg::type::min() || val>(Tfloat)cimg::type::max()); + (*this)(xc,yc,0,k) = (T)val; + } else { + const Tfloat + val0 = (Tfloat)((*this)(nx0,ny0,0,k)), val1 = (Tfloat)((*this)(nx1,ny0,0,k)), + val2 = (Tfloat)((*this)(nx0,ny1,0,k)), val3 = (Tfloat)((*this)(nx1,ny1,0,k)); + (*this)(xc,ny0,0,k) = (T)(((val0+val1)*nopacity + copacity*(*this)(xc,ny0,0,k))/2); + (*this)(xc,ny1,0,k) = (T)(((val2+val3)*nopacity + copacity*(*this)(xc,ny1,0,k))/2); + (*this)(nx0,yc,0,k) = (T)(((val0+val2)*nopacity + copacity*(*this)(nx0,yc,0,k))/2); + (*this)(nx1,yc,0,k) = (T)(((val1+val3)*nopacity + copacity*(*this)(nx1,yc,0,k))/2); + do { + val = (Tfloat)(0.25f*(((Tfloat)((*this)(nx0,ny0,0,k)) + + (Tfloat)((*this)(nx1,ny0,0,k)) + + (Tfloat)((*this)(nx1,ny1,0,k)) + + (Tfloat)((*this)(nx0,ny1,0,k))) + + dc*cimg::grand())*nopacity + copacity*(*this)(xc,yc,0,k)); + } while (val<(Tfloat)cimg::type::min() || val>(Tfloat)cimg::type::max()); + (*this)(xc,yc,0,k) = (T)val; + } + } + if (xc!=nx0 || yc!=ny0) { + draw_plasma(nx0,ny0,xc,yc,alpha,beta,opacity); + draw_plasma(xc,ny0,nx1,yc,alpha,beta,opacity); + draw_plasma(nx0,yc,xc,ny1,alpha,beta,opacity); + draw_plasma(xc,yc,nx1,ny1,alpha,beta,opacity); + } + } + return *this; + } + + //! Draw a plasma random texture. + /** + \param alpha = Alpha-parameter of the plasma. + \param beta = Beta-parameter of the plasma. + \param opacity = opacity of the drawing. + **/ + CImg& draw_plasma(const float alpha=1, const float beta=1, + const float opacity=1) { + return draw_plasma(0,0,width-1,height-1,alpha,beta,opacity); + } + + //! Draw a quadratic Mandelbrot or Julia fractal set, computed using the Escape Time Algorithm. + template + CImg& draw_mandelbrot(const int x0, const int y0, const int x1, const int y1, + const CImg& color_palette, const float opacity=1, + const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2, + const unsigned int itermax=255, + const bool normalized_iteration=false, + const bool julia_set=false, + const double paramr=0, const double parami=0) { + if (is_empty()) return *this; + CImg palette; + if (color_palette) palette.assign(color_palette.data,color_palette.size()/color_palette.dim,1,1,color_palette.dim,true); + if (palette && palette.dim!=dim) + throw CImgArgumentException("CImg<%s>::draw_mandelbrot() : Specified color palette (%u,%u,%u,%u,%p) is not \n" + "compatible with instance image (%u,%u,%u,%u,%p).", + pixel_type(),color_palette.width,color_palette.height,color_palette.depth,color_palette.dim, + color_palette.data,width,height,depth,dim,data); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0), ln2 = (float)cimg_std::log(2.0); + unsigned int iter = 0; + cimg_for_inXY(*this,x0,y0,x1,y1,p,q) { + const double x = z0r + p*(z1r-z0r)/width, y = z0i + q*(z1i-z0i)/height; + double zr, zi, cr, ci; + if (julia_set) { zr = x; zi = y; cr = paramr; ci = parami; } + else { zr = paramr; zi = parami; cr = x; ci = y; } + for (iter=1; zr*zr + zi*zi<=4 && iter<=itermax; ++iter) { + const double temp = zr*zr - zi*zi + cr; + zi = 2*zr*zi + ci; + zr = temp; + } + if (iter>itermax) { + if (palette) { + if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette(0,k); + else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette(0,k)*nopacity + (*this)(p,q,0,k)*copacity); + } else { + if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)0; + else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)((*this)(p,q,0,k)*copacity); + } + } else if (normalized_iteration) { + const float + normz = (float)cimg::abs(zr*zr+zi*zi), + niter = (float)(iter + 1 - cimg_std::log(cimg_std::log(normz))/ln2); + if (palette) { + if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette._linear_atX(niter,k); + else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette._linear_atX(niter,k)*nopacity + (*this)(p,q,0,k)*copacity); + } else { + if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)niter; + else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(niter*nopacity + (*this)(p,q,0,k)*copacity); + } + } else { + if (palette) { + if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette._atX(iter,k); + else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette(iter,k)*nopacity + (*this)(p,q,0,k)*copacity); + } else { + if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)iter; + else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(iter*nopacity + (*this)(p,q,0,k)*copacity); + } + } + } + return *this; + } + + //! Draw a quadratic Mandelbrot or Julia fractal set, computed using the Escape Time Algorithm. + template + CImg& draw_mandelbrot(const CImg& color_palette, const float opacity=1, + const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2, + const unsigned int itermax=255, + const bool normalized_iteration=false, + const bool julia_set=false, + const double paramr=0, const double parami=0) { + return draw_mandelbrot(0,0,width-1,height-1,color_palette,opacity,z0r,z0i,z1r,z1i,itermax,normalized_iteration,julia_set,paramr,parami); + } + + //! Draw a 1D gaussian function in the instance image. + /** + \param xc = X-coordinate of the gaussian center. + \param sigma = Standard variation of the gaussian distribution. + \param color = array of dimv() values of type \c T, defining the drawing color. + \param opacity = opacity of the drawing. + **/ + template + CImg& draw_gaussian(const float xc, const float sigma, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException("CImg<%s>::draw_gaussian() : Specified color is (null)", + pixel_type()); + const float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + const unsigned int whz = width*height*depth; + const tc *col = color; + cimg_forX(*this,x) { + const float dx = (x - xc), val = (float)cimg_std::exp(-dx*dx/sigma2); + T *ptrd = ptr(x,0,0,0); + if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; } + else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; } + col-=dim; + } + return *this; + } + + //! Draw a 1D gaussian function in the instance image. + template + CImg& draw_gaussian(const float xc, const float sigma, + const CImg& color, const float opacity=1) { + return draw_gaussian(xc,sigma,color.data,opacity); + } + + //! Draw an anisotropic 2D gaussian function. + /** + \param xc = X-coordinate of the gaussian center. + \param yc = Y-coordinate of the gaussian center. + \param tensor = 2x2 covariance matrix. + \param color = array of dimv() values of type \c T, defining the drawing color. + \param opacity = opacity of the drawing. + **/ + template + CImg& draw_gaussian(const float xc, const float yc, const CImg& tensor, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + typedef typename cimg::superset::type tfloat; + if (tensor.width!=2 || tensor.height!=2 || tensor.depth!=1 || tensor.dim!=1) + throw CImgArgumentException("CImg<%s>::draw_gaussian() : Tensor parameter (%u,%u,%u,%u,%p) is not a 2x2 matrix.", + pixel_type(),tensor.width,tensor.height,tensor.depth,tensor.dim,tensor.data); + if (!color) + throw CImgArgumentException("CImg<%s>::draw_gaussian() : Specified color is (null)", + pixel_type()); + const CImg invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0); + const tfloat a = invT2(0,0), b = 2*invT2(1,0), c = invT2(1,1); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + const unsigned int whz = width*height*depth; + const tc *col = color; + float dy = -yc; + cimg_forY(*this,y) { + float dx = -xc; + cimg_forX(*this,x) { + const float val = (float)cimg_std::exp(a*dx*dx + b*dx*dy + c*dy*dy); + T *ptrd = ptr(x,y,0,0); + if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; } + else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; } + col-=dim; + ++dx; + } + ++dy; + } + return *this; + } + + //! Draw an anisotropic 2D gaussian function. + template + CImg& draw_gaussian(const float xc, const float yc, const CImg& tensor, + const CImg& color, const float opacity=1) { + return draw_gaussian(xc,yc,tensor,color.data,opacity); + } + + //! Draw an anisotropic 2D gaussian function. + template + CImg& draw_gaussian(const int xc, const int yc, const float r1, const float r2, const float ru, const float rv, + const tc *const color, const float opacity=1) { + const double + a = r1*ru*ru + r2*rv*rv, + b = (r1-r2)*ru*rv, + c = r1*rv*rv + r2*ru*ru; + const CImg tensor(2,2,1,1, a,b,b,c); + return draw_gaussian(xc,yc,tensor,color,opacity); + } + + //! Draw an anisotropic 2D gaussian function. + template + CImg& draw_gaussian(const int xc, const int yc, const float r1, const float r2, const float ru, const float rv, + const CImg& color, const float opacity=1) { + return draw_gaussian(xc,yc,r1,r2,ru,rv,color.data,opacity); + } + + //! Draw an isotropic 2D gaussian function. + /** + \param xc = X-coordinate of the gaussian center. + \param yc = Y-coordinate of the gaussian center. + \param sigma = standard variation of the gaussian distribution. + \param color = array of dimv() values of type \c T, defining the drawing color. + \param opacity = opacity of the drawing. + **/ + template + CImg& draw_gaussian(const float xc, const float yc, const float sigma, + const tc *const color, const float opacity=1) { + return draw_gaussian(xc,yc,CImg::diagonal(sigma,sigma),color,opacity); + } + + //! Draw an isotropic 2D gaussian function. + template + CImg& draw_gaussian(const float xc, const float yc, const float sigma, + const CImg& color, const float opacity=1) { + return draw_gaussian(xc,yc,sigma,color.data,opacity); + } + + //! Draw an anisotropic 3D gaussian function. + /** + \param xc = X-coordinate of the gaussian center. + \param yc = Y-coordinate of the gaussian center. + \param zc = Z-coordinate of the gaussian center. + \param tensor = 3x3 covariance matrix. + \param color = array of dimv() values of type \c T, defining the drawing color. + \param opacity = opacity of the drawing. + **/ + template + CImg& draw_gaussian(const float xc, const float yc, const float zc, const CImg& tensor, + const tc *const color, const float opacity=1) { + if (is_empty()) return *this; + typedef typename cimg::superset::type tfloat; + if (tensor.width!=3 || tensor.height!=3 || tensor.depth!=1 || tensor.dim!=1) + throw CImgArgumentException("CImg<%s>::draw_gaussian() : Tensor parameter (%u,%u,%u,%u,%p) is not a 3x3 matrix.", + pixel_type(),tensor.width,tensor.height,tensor.depth,tensor.dim,tensor.data); + const CImg invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0); + const tfloat a = invT(0,0), b = 2*invT(1,0), c = 2*invT(2,0), d = invT(1,1), e = 2*invT(2,1), f = invT(2,2); + const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0); + const unsigned int whz = width*height*depth; + const tc *col = color; + cimg_forXYZ(*this,x,y,z) { + const float + dx = (x - xc), dy = (y - yc), dz = (z - zc), + val = (float)cimg_std::exp(a*dx*dx + b*dx*dy + c*dx*dz + d*dy*dy + e*dy*dz + f*dz*dz); + T *ptrd = ptr(x,y,z,0); + if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; } + else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; } + col-=dim; + } + return *this; + } + + //! Draw an anisotropic 3D gaussian function. + template + CImg& draw_gaussian(const float xc, const float yc, const float zc, const CImg& tensor, + const CImg& color, const float opacity=1) { + return draw_gaussian(xc,yc,zc,tensor,color.data,opacity); + } + + //! Draw an isotropic 3D gaussian function. + /** + \param xc = X-coordinate of the gaussian center. + \param yc = Y-coordinate of the gaussian center. + \param zc = Z-coordinate of the gaussian center. + \param sigma = standard variation of the gaussian distribution. + \param color = array of dimv() values of type \c T, defining the drawing color. + \param opacity = opacity of the drawing. + **/ + template + CImg& draw_gaussian(const float xc, const float yc, const float zc, const float sigma, + const tc *const color, const float opacity=1) { + return draw_gaussian(xc,yc,zc,CImg::diagonal(sigma,sigma,sigma),color,opacity); + } + + //! Draw an isotropic 3D gaussian function. + template + CImg& draw_gaussian(const float xc, const float yc, const float zc, const float sigma, + const CImg& color, const float opacity=1) { + return draw_gaussian(xc,yc,zc,sigma,color.data,opacity); + } + + // Draw a 3D object (internal) + template + void _draw_object3d_sprite(const int x, const int y, + const CImg& color, const CImg& opacity, const CImg& sprite) { + if (opacity.width==color.width && opacity.height==color.height) + draw_image(x,y,sprite,opacity.get_resize(sprite.width,sprite.height,1,sprite.dim,1)); + else + draw_image(x,y,sprite,opacity(0)); + } + + template + void _draw_object3d_sprite(const int x, const int y, + const CImg& color, const float opacity, const CImg& sprite) { + if (color) draw_image(x,y,sprite,opacity); + } + + template + CImg& _draw_object3d(void *const pboard, float *const zbuffer, + const float X, const float Y, const float Z, + const tp& points, const unsigned int nb_points, + const CImgList& primitives, + const CImgList& colors, + const to& opacities, const unsigned int nb_opacities, + const unsigned int render_type, + const bool double_sided, const float focale, + const float lightx, const float lighty, const float lightz, + const float specular_light, const float specular_shine) { + if (is_empty()) return *this; +#ifndef cimg_use_board + if (pboard) return *this; +#endif + const float + nspec = 1-(specular_light<0?0:(specular_light>1?1:specular_light)), + nspec2 = 1+(specular_shine<0?0:specular_shine), + nsl1 = (nspec2-1)/cimg::sqr(nspec-1), + nsl2 = (1-2*nsl1*nspec), + nsl3 = nspec2-nsl1-nsl2; + + // Create light texture for phong-like rendering + static CImg light_texture; + if (render_type==5) { + if (colors.size>primitives.size) light_texture.assign(colors[primitives.size])/=255; + else { + static float olightx = 0, olighty = 0, olightz = 0, ospecular_shine = 0; + if (!light_texture || lightx!=olightx || lighty!=olighty || lightz!=olightz || specular_shine!=ospecular_shine) { + light_texture.assign(512,512); + const float white[] = { 1 }, + dlx = lightx-X, dly = lighty-Y, dlz = lightz-Z, + nl = (float)cimg_std::sqrt(dlx*dlx+dly*dly+dlz*dlz), + nlx = light_texture.width/2*(1+dlx/nl), + nly = light_texture.height/2*(1+dly/nl); + light_texture.draw_gaussian(nlx,nly,light_texture.width/3.0f,white); + cimg_forXY(light_texture,x,y) { + const float factor = light_texture(x,y); + if (factor>nspec) light_texture(x,y) = cimg::min(2,nsl1*factor*factor+nsl2*factor+nsl3); + } + olightx = lightx; olighty = lighty; olightz = lightz; ospecular_shine = specular_shine; + } + } + } + + // Compute 3D to 2D projection + CImg projections(nb_points,2); + cimg_forX(projections,l) { + const float + x = (float)points(l,0), + y = (float)points(l,1), + z = (float)points(l,2); + const float projectedz = z + Z + focale; + projections(l,1) = Y + focale*y/projectedz; + projections(l,0) = X + focale*x/projectedz; + } + + // Compute and sort visible primitives + CImg visibles(primitives.size); + CImg zrange(primitives.size); + unsigned int nb_visibles = 0; + const float zmin = -focale+1.5f; + { cimglist_for(primitives,l) { + const CImg& primitive = primitives[l]; + switch (primitive.size()) { + + case 1 : { // Point + const unsigned int i0 = (unsigned int)primitive(0); + const float x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)); + if (z0>zmin && x0>=0 && x0=0 && y0zmin && x0+radius>=0 && x0-radius=0 && y0-radiuszmin && z1>zmin && xM>=0 && xm=0 && ymxM) xM = x2; + if (y0yM) yM = y2; + if (z0>zmin && z1>zmin && z2>zmin && xM>=0 && xm=0 && ymxM) xM = x2; + if (x3xM) xM = x3; + if (y0yM) yM = y2; + if (y3yM) yM = y3; + if (z0>zmin && z1>zmin && z2>zmin && z3>zmin && xM>=0 && xm=0 && ym::draw_object3d() : Primitive %u is invalid (size = %u, can be 1,2,3,4,5,6,9 or 12)", + pixel_type(),l,primitive.size()); + }} + } + if (nb_visibles<=0) return *this; + CImg permutations; + CImg(zrange.data,nb_visibles,1,1,1,true).sort(permutations,false); + + // Compute light properties + CImg lightprops; + switch (render_type) { + case 3 : { // Flat Shading + lightprops.assign(nb_visibles); + cimg_forX(lightprops,l) { + const CImg& primitive = primitives(visibles(permutations(l))); + const unsigned int psize = primitive.size(); + if (psize==3 || psize==4 || psize==9 || psize==12) { + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2); + const float + x0 = (float)points(i0,0), y0 = (float)points(i0,1), z0 = (float)points(i0,2), + x1 = (float)points(i1,0), y1 = (float)points(i1,1), z1 = (float)points(i1,2), + x2 = (float)points(i2,0), y2 = (float)points(i2,1), z2 = (float)points(i2,2), + dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0, + dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0, + nx = dy1*dz2 - dz1*dy2, + ny = dz1*dx2 - dx1*dz2, + nz = dx1*dy2 - dy1*dx2, + norm = (float)cimg_std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz), + lx = X + (x0 + x1 + x2)/3 - lightx, + ly = Y + (y0 + y1 + y2)/3 - lighty, + lz = Z + (z0 + z1 + z2)/3 - lightz, + nl = (float)cimg_std::sqrt(1e-5f + lx*lx + ly*ly + lz*lz), + factor = cimg::max(cimg::abs(-lx*nx-ly*ny-lz*nz)/(norm*nl),0); + lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3); + } else lightprops[l] = 1; + } + } break; + + case 4 : // Gouraud Shading + case 5 : { // Phong-Shading + CImg points_normals(nb_points,3,1,1,0); + for (unsigned int l=0; l& primitive = primitives[visibles(l)]; + const unsigned int psize = primitive.size(); + const bool + triangle_flag = (psize==3) || (psize==9), + rectangle_flag = (psize==4) || (psize==12); + if (triangle_flag || rectangle_flag) { + const unsigned int + i0 = (unsigned int)primitive(0), + i1 = (unsigned int)primitive(1), + i2 = (unsigned int)primitive(2), + i3 = rectangle_flag?(unsigned int)primitive(3):0; + const float + x0 = (float)points(i0,0), y0 = (float)points(i0,1), z0 = (float)points(i0,2), + x1 = (float)points(i1,0), y1 = (float)points(i1,1), z1 = (float)points(i1,2), + x2 = (float)points(i2,0), y2 = (float)points(i2,1), z2 = (float)points(i2,2), + dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0, + dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0, + nnx = dy1*dz2 - dz1*dy2, + nny = dz1*dx2 - dx1*dz2, + nnz = dx1*dy2 - dy1*dx2, + norm = 1e-5f + (float)cimg_std::sqrt(nnx*nnx + nny*nny + nnz*nnz), + nx = nnx/norm, + ny = nny/norm, + nz = nnz/norm; + points_normals(i0,0)+=nx; points_normals(i0,1)+=ny; points_normals(i0,2)+=nz; + points_normals(i1,0)+=nx; points_normals(i1,1)+=ny; points_normals(i1,2)+=nz; + points_normals(i2,0)+=nx; points_normals(i2,1)+=ny; points_normals(i2,2)+=nz; + if (rectangle_flag) { points_normals(i3,0)+=nx; points_normals(i3,1)+=ny; points_normals(i3,2)+=nz; } + } + } + + if (double_sided) cimg_forX(points_normals,p) if (points_normals(p,2)>0) { + points_normals(p,0) = -points_normals(p,0); + points_normals(p,1) = -points_normals(p,1); + points_normals(p,2) = -points_normals(p,2); + } + + if (render_type==4) { + lightprops.assign(nb_points); + cimg_forX(lightprops,ll) { + const float + nx = points_normals(ll,0), + ny = points_normals(ll,1), + nz = points_normals(ll,2), + norm = (float)cimg_std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz), + lx = (float)(X + points(ll,0) - lightx), + ly = (float)(Y + points(ll,1) - lighty), + lz = (float)(Z + points(ll,2) - lightz), + nl = (float)cimg_std::sqrt(1e-5f + lx*lx + ly*ly + lz*lz), + factor = cimg::max((-lx*nx-ly*ny-lz*nz)/(norm*nl),0); + lightprops[ll] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3); + } + } else { + const unsigned int + lw2 = light_texture.width/2 - 1, + lh2 = light_texture.height/2 - 1; + lightprops.assign(nb_points,2); + cimg_forX(lightprops,ll) { + const float + nx = points_normals(ll,0), + ny = points_normals(ll,1), + nz = points_normals(ll,2), + norm = (float)cimg_std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz), + nnx = nx/norm, + nny = ny/norm; + lightprops(ll,0) = lw2*(1 + nnx); + lightprops(ll,1) = lh2*(1 + nny); + } + } + } break; + } + + // Draw visible primitives + const CImg default_color(1,dim,1,1,(tc)200); + { for (unsigned int l = 0; l& primitive = primitives[n_primitive]; + const CImg& color = n_primitive=0 && x0-sw=0 && y0-sh sprite = color.get_resize(-factor,-factor,1,-100,render_type<=3?1:3); + _draw_object3d_sprite(x0-sw,y0-sh,color,opacities[n_primitive%nb_opacities],sprite); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128); + board.setFillColor(BoardLib::Color::none); + board.drawRectangle((float)x0-sw,dimy()-(float)y0+sh,sw,sh); + } +#endif + } + } + } break; + case 2 : { // Colored line + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1); + const float + z0 = points(n0,2) + Z + focale, + z1 = points(n1,2) + Z + focale; + if (render_type) { + if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,opac); + else draw_line(x0,y0,x1,y1,color,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255)); + board.drawLine((float)x0,dimy()-(float)y0,x1,dimy()-(float)y1); + } +#endif + } else { + draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255)); + board.drawCircle((float)x0,dimy()-(float)y0,0); + board.drawCircle((float)x1,dimy()-(float)y1,0); + } +#endif + } + } break; + case 5 : { // Colored sphere + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1); + int radius; + if (n2) radius = (int)(n2*focale/(Z+points(n0,2)+focale)); + else { + const int + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + deltax = x1-x0, deltay = y1-y0; + radius = (int)cimg_std::sqrt((float)(deltax*deltax + deltay*deltay)); + } + switch (render_type) { + case 0 : + draw_point(x0,y0,color,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255)); + board.fillCircle((float)x0,dimy()-(float)y0,0); + } +#endif + break; + case 1 : + draw_circle(x0,y0,radius,color,opac,~0U); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255)); + board.setFillColor(BoardLib::Color::none); + board.drawCircle((float)x0,dimy()-(float)y0,(float)radius); + } +#endif + break; + default : + draw_circle(x0,y0,radius,color,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255)); + board.fillCircle((float)x0,dimy()-(float)y0,(float)radius); + } +#endif + break; + } + } break; + case 6 : { // Textured line + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + tx0 = (unsigned int)primitive[2], + ty0 = (unsigned int)primitive[3], + tx1 = (unsigned int)primitive[4], + ty1 = (unsigned int)primitive[5]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1); + const float + z0 = points(n0,2) + Z + focale, + z1 = points(n1,2) + Z + focale; + if (render_type) { + if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac); + else draw_line(x0,y0,x1,y1,color,tx0,ty0,tx1,ty1,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255)); + board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1); + } +#endif + } else { + draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac). + draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255)); + board.drawCircle((float)x0,dimy()-(float)y0,0); + board.drawCircle((float)x1,dimy()-(float)y1,0); + } +#endif + } + } break; + case 3 : { // Colored triangle + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1); + const float + z0 = points(n0,2) + Z + focale, + z1 = points(n1,2) + Z + focale, + z2 = points(n2,2) + Z + focale; + switch (render_type) { + case 0 : + draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac).draw_point(x2,y2,color,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255)); + board.drawCircle((float)x0,dimy()-(float)y0,0); + board.drawCircle((float)x1,dimy()-(float)y1,0); + board.drawCircle((float)x2,dimy()-(float)y2,0); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,opac).draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,opac). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,opac); + else + draw_line(x0,y0,x1,y1,color,opac).draw_line(x0,y0,x2,y2,color,opac). + draw_line(x1,y1,x2,y2,color,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255)); + board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1); + board.drawLine((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2); + board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2); + } +#endif + break; + case 2 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,opac); + else draw_triangle(x0,y0,x1,y1,x2,y2,color,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255)); + board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2); + } +#endif + break; + case 3 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,opac,lightprops(l)); + else _draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opac,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = cimg::min(lightprops(l),1); + board.setPenColorRGBi((unsigned char)(color[0]*lp), + (unsigned char)(color[1]*lp), + (unsigned char)(color[2]*lp), + (unsigned char)(opac*255)); + board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2); + } +#endif + break; + case 4 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,lightprops(n0),lightprops(n1),lightprops(n2),opac); + else draw_triangle(x0,y0,x1,y1,x2,y2,color,lightprops(n0),lightprops(n1),lightprops(n2),opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opac*255)); + board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprops(n0), + (float)x1,dimy()-(float)y1,lightprops(n1), + (float)x2,dimy()-(float)y2,lightprops(n2)); + } +#endif + break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1); + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac); + else draw_triangle(x0,y0,x1,y1,x2,y2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n0,0))), (int)(light_texture.dimy()/2*(1+lightprops(n0,1)))), + l1 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n1,0))), (int)(light_texture.dimy()/2*(1+lightprops(n1,1)))), + l2 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n2,0))), (int)(light_texture.dimy()/2*(1+lightprops(n2,1)))); + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opac*255)); + board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0, + (float)x1,dimy()-(float)y1,l1, + (float)x2,dimy()-(float)y2,l2); + } +#endif + } break; + } + } break; + case 4 : { // Colored rectangle + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2], + n3 = (unsigned int)primitive[3]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1), + x3 = (int)projections(n3,0), y3 = (int)projections(n3,1); + const float + z0 = points(n0,2) + Z + focale, + z1 = points(n1,2) + Z + focale, + z2 = points(n2,2) + Z + focale, + z3 = points(n3,2) + Z + focale; + switch (render_type) { + case 0 : + draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac). + draw_point(x2,y2,color,opac).draw_point(x3,y3,color,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255)); + board.drawCircle((float)x0,dimy()-(float)y0,0); + board.drawCircle((float)x1,dimy()-(float)y1,0); + board.drawCircle((float)x2,dimy()-(float)y2,0); + board.drawCircle((float)x3,dimy()-(float)y3,0); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,opac).draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,opac). + draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,opac).draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,opac); + else + draw_line(x0,y0,x1,y1,color,opac).draw_line(x1,y1,x2,y2,color,opac). + draw_line(x2,y2,x3,y3,color,opac).draw_line(x3,y3,x0,y0,color,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255)); + board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1); + board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2); + board.drawLine((float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3); + board.drawLine((float)x3,dimy()-(float)y3,(float)x0,dimy()-(float)y0); + } +#endif + break; + case 2 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,opac).draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,opac); + else + draw_triangle(x0,y0,x1,y1,x2,y2,color,opac).draw_triangle(x0,y0,x2,y2,x3,y3,color,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255)); + board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2); + board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,opac,lightprops(l)). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color.data,opac,lightprops(l)); + else + _draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opac,lightprops(l)). + _draw_triangle(x0,y0,x2,y2,x3,y3,color.data,opac,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = cimg::min(lightprops(l),1); + board.setPenColorRGBi((unsigned char)(color[0]*lp), + (unsigned char)(color[1]*lp), + (unsigned char)(color[2]*lp),(unsigned char)(opac*255)); + board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2); + board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3); + } +#endif + break; + case 4 : { + const float + lightprop0 = lightprops(n0), lightprop1 = lightprops(n1), + lightprop2 = lightprops(n2), lightprop3 = lightprops(n3); + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,lightprop0,lightprop1,lightprop2,opac). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,lightprop0,lightprop2,lightprop3,opac); + else + draw_triangle(x0,y0,x1,y1,x2,y2,color,lightprop0,lightprop1,lightprop2,opac). + draw_triangle(x0,y0,x2,y2,x3,y3,color,lightprop0,lightprop2,lightprop3,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opac*255)); + board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0, + (float)x1,dimy()-(float)y1,lightprop1, + (float)x2,dimy()-(float)y2,lightprop2); + board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0, + (float)x2,dimy()-(float)y2,lightprop2, + (float)x3,dimy()-(float)y3,lightprop3); + } +#endif + } break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1), + lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1); + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac); + else + draw_triangle(x0,y0,x1,y1,x2,y2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac). + draw_triangle(x0,y0,x2,y2,x3,y3,color,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.dimx()/2*(1+lx0)), (int)(light_texture.dimy()/2*(1+ly0))), + l1 = light_texture((int)(light_texture.dimx()/2*(1+lx1)), (int)(light_texture.dimy()/2*(1+ly1))), + l2 = light_texture((int)(light_texture.dimx()/2*(1+lx2)), (int)(light_texture.dimy()/2*(1+ly2))), + l3 = light_texture((int)(light_texture.dimx()/2*(1+lx3)), (int)(light_texture.dimy()/2*(1+ly3))); + board.setPenColorRGBi((unsigned char)(color[0]), + (unsigned char)(color[1]), + (unsigned char)(color[2]), + (unsigned char)(opac*255)); + board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0, + (float)x1,dimy()-(float)y1,l1, + (float)x2,dimy()-(float)y2,l2); + board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0, + (float)x2,dimy()-(float)y2,l2, + (float)x3,dimy()-(float)y3,l3); + } +#endif + } break; + } + } break; + case 9 : { // Textured triangle + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2], + tx0 = (unsigned int)primitive[3], + ty0 = (unsigned int)primitive[4], + tx1 = (unsigned int)primitive[5], + ty1 = (unsigned int)primitive[6], + tx2 = (unsigned int)primitive[7], + ty2 = (unsigned int)primitive[8]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1); + const float + z0 = points(n0,2) + Z + focale, + z1 = points(n1,2) + Z + focale, + z2 = points(n2,2) + Z + focale; + switch (render_type) { + case 0 : + draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac). + draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac). + draw_point(x2,y2,color.get_vector_at(tx2,ty2),opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255)); + board.drawCircle((float)x0,dimy()-(float)y0,0); + board.drawCircle((float)x1,dimy()-(float)y1,0); + board.drawCircle((float)x2,dimy()-(float)y2,0); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac). + draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opac). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac); + else + draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac). + draw_line(x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opac). + draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255)); + board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1); + board.drawLine((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2); + board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2); + } +#endif + break; + case 2 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac); + else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255)); + board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2); + } +#endif + break; + case 3 : + if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l)); + else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = cimg::min(lightprops(l),1); + board.setPenColorRGBi((unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(opac*255)); + board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2); + } +#endif + break; + case 4 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprops(n0),lightprops(n1),lightprops(n2),opac); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprops(n0),lightprops(n1),lightprops(n2),opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255)); + board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprops(n0), + (float)x1,dimy()-(float)y1,lightprops(n1), + (float)x2,dimy()-(float)y2,lightprops(n2)); + } +#endif + break; + case 5 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture, + (unsigned int)lightprops(n0,0), (unsigned int)lightprops(n0,1), + (unsigned int)lightprops(n1,0), (unsigned int)lightprops(n1,1), + (unsigned int)lightprops(n2,0), (unsigned int)lightprops(n2,1), + opac); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture, + (unsigned int)lightprops(n0,0), (unsigned int)lightprops(n0,1), + (unsigned int)lightprops(n1,0), (unsigned int)lightprops(n1,1), + (unsigned int)lightprops(n2,0), (unsigned int)lightprops(n2,1), + opac); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n0,0))), (int)(light_texture.dimy()/2*(1+lightprops(n0,1)))), + l1 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n1,0))), (int)(light_texture.dimy()/2*(1+lightprops(n1,1)))), + l2 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n2,0))), (int)(light_texture.dimy()/2*(1+lightprops(n2,1)))); + board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255)); + board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,(float)x1,dimy()-(float)y1,l1,(float)x2,dimy()-(float)y2,l2); + } +#endif + break; + } + } break; + case 12 : { // Textured rectangle + const unsigned int + n0 = (unsigned int)primitive[0], + n1 = (unsigned int)primitive[1], + n2 = (unsigned int)primitive[2], + n3 = (unsigned int)primitive[3], + tx0 = (unsigned int)primitive[4], + ty0 = (unsigned int)primitive[5], + tx1 = (unsigned int)primitive[6], + ty1 = (unsigned int)primitive[7], + tx2 = (unsigned int)primitive[8], + ty2 = (unsigned int)primitive[9], + tx3 = (unsigned int)primitive[10], + ty3 = (unsigned int)primitive[11]; + const int + x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), + x1 = (int)projections(n1,0), y1 = (int)projections(n1,1), + x2 = (int)projections(n2,0), y2 = (int)projections(n2,1), + x3 = (int)projections(n3,0), y3 = (int)projections(n3,1); + const float + z0 = points(n0,2) + Z + focale, + z1 = points(n1,2) + Z + focale, + z2 = points(n2,2) + Z + focale, + z3 = points(n3,2) + Z + focale; + switch (render_type) { + case 0 : + draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac). + draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac). + draw_point(x2,y2,color.get_vector_at(tx2,ty2),opac). + draw_point(x3,y3,color.get_vector_at(tx3,ty3),opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255)); + board.drawCircle((float)x0,dimy()-(float)y0,0); + board.drawCircle((float)x1,dimy()-(float)y1,0); + board.drawCircle((float)x2,dimy()-(float)y2,0); + board.drawCircle((float)x3,dimy()-(float)y3,0); + } +#endif + break; + case 1 : + if (zbuffer) + draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac). + draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac). + draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opac). + draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opac); + else + draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac). + draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac). + draw_line(x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opac). + draw_line(x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255)); + board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1); + board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2); + board.drawLine((float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3); + board.drawLine((float)x3,dimy()-(float)y3,(float)x0,dimy()-(float)y0); + } +#endif + break; + case 2 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac). + draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255)); + board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2); + board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3); + } +#endif + break; + case 3 : + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l)). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac,lightprops(l)); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l)). + draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac,lightprops(l)); +#ifdef cimg_use_board + if (pboard) { + const float lp = cimg::min(lightprops(l),1); + board.setPenColorRGBi((unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(128*lp), + (unsigned char)(opac*255)); + board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2); + board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3); + } +#endif + break; + case 4 : { + const float + lightprop0 = lightprops(n0), lightprop1 = lightprops(n1), + lightprop2 = lightprops(n2), lightprop3 = lightprops(n3); + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprop0,lightprop1,lightprop2,opac). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,lightprop0,lightprop2,lightprop3,opac); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprop0,lightprop1,lightprop2,opac). + draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,lightprop0,lightprop2,lightprop3,opac); +#ifdef cimg_use_board + if (pboard) { + board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255)); + board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0, + (float)x1,dimy()-(float)y1,lightprop1, + (float)x2,dimy()-(float)y2,lightprop2); + board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0, + (float)x2,dimy()-(float)y2,lightprop2, + (float)x3,dimy()-(float)y3,lightprop3); + } +#endif + } break; + case 5 : { + const unsigned int + lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1), + lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1), + lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1), + lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1); + if (zbuffer) + draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac). + draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac); + else + draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac). + draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac); +#ifdef cimg_use_board + if (pboard) { + const float + l0 = light_texture((int)(light_texture.dimx()/2*(1+lx0)), (int)(light_texture.dimy()/2*(1+ly0))), + l1 = light_texture((int)(light_texture.dimx()/2*(1+lx1)), (int)(light_texture.dimy()/2*(1+ly1))), + l2 = light_texture((int)(light_texture.dimx()/2*(1+lx2)), (int)(light_texture.dimy()/2*(1+ly2))), + l3 = light_texture((int)(light_texture.dimx()/2*(1+lx3)), (int)(light_texture.dimy()/2*(1+ly3))); + board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255)); + board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0, + (float)x1,dimy()-(float)y1,l1, + (float)x2,dimy()-(float)y2,l2); + board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0, + (float)x2,dimy()-(float)y2,l2, + (float)x3,dimy()-(float)y3,l3); + } +#endif + } break; + } + } break; + } + } + } + return *this; + } + + //! Draw a 3D object. + /** + \param X = X-coordinate of the 3d object position + \param Y = Y-coordinate of the 3d object position + \param Z = Z-coordinate of the 3d object position + \param points = Image N*3 describing 3D point coordinates + \param primitives = List of P primitives + \param colors = List of P color (or textures) + \param opacities = Image of P opacities + \param render_type = Render type (0=Points, 1=Lines, 2=Faces (no light), 3=Faces (flat), 4=Faces(Gouraud) + \param double_sided = Tell if object faces have two sides or are oriented. + \param focale = length of the focale + \param lightx = X-coordinate of the light + \param lighty = Y-coordinate of the light + \param lightz = Z-coordinate of the light + \param specular_shine = Shininess of the object + **/ + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& points, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type=4, + const bool double_sided=false, const float focale=500, + const float lightx=0, const float lighty=0, const float lightz=-5000, + const float specular_light=0.2f, const float specular_shine=0.1f, + float *const zbuffer=0) { + if (!points) return *this; + return _draw_object3d(0,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width, + primitives,colors,opacities,opacities.size, + render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(BoardLib::Board& board, + const float x0, const float y0, const float z0, + const CImg& points, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type=4, + const bool double_sided=false, const float focale=500, + const float lightx=0, const float lighty=0, const float lightz=-5000, + const float specular_light=0.2f, const float specular_shine=0.1f, + float *const zbuffer=0) { + if (!points) return *this; + return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width, + primitives,colors,opacities,opacities.size, + render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine); + } +#endif + + //! Draw a 3D object. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImgList& points, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type=4, + const bool double_sided=false, const float focale=500, + const float lightx=0, const float lighty=0, const float lightz=-5000, + const float specular_light=0.2f, const float specular_shine=0.1f, + float *const zbuffer=0) { + if (!points) return *this; + return _draw_object3d(0,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size, + render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(BoardLib::Board& board, + const float x0, const float y0, const float z0, + const CImgList& points, const CImgList& primitives, + const CImgList& colors, const CImgList& opacities, + const unsigned int render_type=4, + const bool double_sided=false, const float focale=500, + const float lightx=0, const float lighty=0, const float lightz=-5000, + const float specular_light=0.2f, const float specular_shine=0.1f, + float *const zbuffer=0) { + if (!points) return *this; + return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size, + render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine); + } +#endif + + //! Draw a 3D object. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImg& points, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type=4, + const bool double_sided=false, const float focale=500, + const float lightx=0, const float lighty=0, const float lightz=-5000, + const float specular_light=0.2f, const float specular_shine=0.1f, + float *const zbuffer=0) { + if (!points) return *this; + return _draw_object3d(0,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width, + primitives,colors,opacities,opacities.size(), + render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(BoardLib::Board& board, + const float x0, const float y0, const float z0, + const CImg& points, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type=4, + const bool double_sided=false, const float focale=500, + const float lightx=0, const float lighty=0, const float lightz=-5000, + const float specular_light=0.2f, const float specular_shine=0.1f, + float *const zbuffer=0) { + if (!points) return *this; + return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width + ,primitives,colors,opacities,opacities.size(), + render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine); + } +#endif + + //! Draw a 3D object. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const CImgList& points, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type=4, + const bool double_sided=false, const float focale=500, + const float lightx=0, const float lighty=0, const float lightz=-5000, + const float specular_light=0.2f, const float specular_shine=0.1f, + float *const zbuffer=0) { + if (!points) return *this; + return _draw_object3d(0,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size(), + render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(BoardLib::Board& board, + const float x0, const float y0, const float z0, + const CImgList& points, const CImgList& primitives, + const CImgList& colors, const CImg& opacities, + const unsigned int render_type=4, + const bool double_sided=false, const float focale=500, + const float lightx=0, const float lighty=0, const float lightz=-5000, + const float specular_light=0.2f, const float specular_shine=0.1f, + float *const zbuffer=0) { + if (!points) return *this; + return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size(), + render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine); + } +#endif + + //! Draw a 3D object. + template + CImg& draw_object3d(const float x0, const float y0, const float z0, + const tp& points, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type=4, + const bool double_sided=false, const float focale=500, + const float lightx=0, const float lighty=0, const float lightz=-5000, + const float specular_light=0.2f, const float specular_shine=0.1f, + float *const zbuffer=0) { + static const CImg opacities; + return draw_object3d(x0,y0,z0,points,primitives,colors,opacities, + render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,zbuffer); + } + +#ifdef cimg_use_board + template + CImg& draw_object3d(BoardLib::Board& board, + const float x0, const float y0, const float z0, + const tp& points, const CImgList& primitives, + const CImgList& colors, + const unsigned int render_type=4, + const bool double_sided=false, const float focale=500, + const float lightx=0, const float lighty=0, const float lightz=-5000, + const float specular_light=0.2f, const float specular_shine=0.1f, + float *const zbuffer=0) { + static const CImg opacities; + return draw_object3d(x0,y0,z0,points,primitives,colors,opacities, + render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,zbuffer); + } +#endif + + //@} + //---------------------------- + // + //! \name Image Filtering + //@{ + //---------------------------- + + //! Compute the correlation of the instance image by a mask. + /** + The correlation of the instance image \p *this by the mask \p mask is defined to be : + + res(x,y,z) = sum_{i,j,k} (*this)(x+i,y+j,z+k)*mask(i,j,k) + + \param mask = the correlation kernel. + \param cond = the border condition type (0=zero, 1=dirichlet) + \param weighted_correl = enable local normalization. + **/ + template + CImg& correlate(const CImg& mask, const unsigned int cond=1, const bool weighted_correl=false) { + return get_correlate(mask,cond,weighted_correl).transfer_to(*this); + } + + template + CImg::type> get_correlate(const CImg& mask, const unsigned int cond=1, + const bool weighted_correl=false) const { + typedef typename cimg::superset2::type Ttfloat; + if (is_empty()) return *this; + if (!mask || mask.dim!=1) + throw CImgArgumentException("CImg<%s>::correlate() : Specified mask (%u,%u,%u,%u,%p) is not scalar.", + pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data); + CImg dest(width,height,depth,dim); + if (cond && mask.width==mask.height && ((mask.depth==1 && mask.width<=5) || (mask.depth==mask.width && mask.width<=3))) { + // A special optimization is done for 2x2, 3x3, 4x4, 5x5, 2x2x2 and 3x3x3 mask (with cond=1) + switch (mask.depth) { + case 3 : { + T I[27] = { 0 }; + cimg_forZV(*this,z,v) cimg_for3x3x3(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat) + (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] + + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] + + I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + + I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] + + I[18]*mask[18] + I[19]*mask[19] + I[20]*mask[20] + + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] + + I[24]*mask[24] + I[25]*mask[25] + I[26]*mask[26]); + if (weighted_correl) cimg_forZV(*this,z,v) cimg_for3x3x3(*this,x,y,z,v,I) { + const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + + I[ 3]*I[ 3] + I[ 4]*I[ 4] + I[ 5]*I[ 5] + + I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + + I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] + + I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + + I[15]*I[15] + I[16]*I[16] + I[17]*I[17] + + I[18]*I[18] + I[19]*I[19] + I[20]*I[20] + + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] + + I[24]*I[24] + I[25]*I[25] + I[26]*I[26]); + if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight); + } + } break; + case 2 : { + T I[8] = { 0 }; + cimg_forZV(*this,z,v) cimg_for2x2x2(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat) + (I[0]*mask[0] + I[1]*mask[1] + + I[2]*mask[2] + I[3]*mask[3] + + I[4]*mask[4] + I[5]*mask[5] + + I[6]*mask[6] + I[7]*mask[7]); + if (weighted_correl) cimg_forZV(*this,z,v) cimg_for2x2x2(*this,x,y,z,v,I) { + const double weight = (double)(I[0]*I[0] + I[1]*I[1] + + I[2]*I[2] + I[3]*I[3] + + I[4]*I[4] + I[5]*I[5] + + I[6]*I[6] + I[7]*I[7]); + if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight); + } + } break; + default : + case 1 : + switch (mask.width) { + case 6 : { + T I[36] = { 0 }; + cimg_forZV(*this,z,v) cimg_for6x6(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat) + (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] + + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] + + I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] + + I[18]*mask[18] + I[19]*mask[19] + I[20]*mask[20] + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] + + I[24]*mask[24] + I[25]*mask[25] + I[26]*mask[26] + I[27]*mask[27] + I[28]*mask[28] + I[29]*mask[29] + + I[30]*mask[30] + I[31]*mask[31] + I[32]*mask[32] + I[33]*mask[33] + I[34]*mask[34] + I[35]*mask[35]); + if (weighted_correl) cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) { + const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + I[ 4]*I[ 4] + I[ 5]*I[ 5] + + I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] + + I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + I[15]*I[15] + I[16]*I[16] + I[17]*I[17] + + I[18]*I[18] + I[19]*I[19] + I[20]*I[20] + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] + + I[24]*I[24] + I[25]*I[25] + I[26]*I[26] + I[27]*I[27] + I[28]*I[28] + I[29]*I[29] + + I[30]*I[30] + I[31]*I[31] + I[32]*I[32] + I[33]*I[33] + I[34]*I[34] + I[35]*I[35]); + if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight); + } + } break; + case 5 : { + T I[25] = { 0 }; + cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat) + (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + + I[ 5]*mask[ 5] + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + + I[10]*mask[10] + I[11]*mask[11] + I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + + I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] + I[18]*mask[18] + I[19]*mask[19] + + I[20]*mask[20] + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] + I[24]*mask[24]); + if (weighted_correl) cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) { + const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + I[ 4]*I[ 4] + + I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + I[ 9]*I[ 9] + + I[10]*I[10] + I[11]*I[11] + I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + + I[15]*I[15] + I[16]*I[16] + I[17]*I[17] + I[18]*I[18] + I[19]*I[19] + + I[20]*I[20] + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] + I[24]*I[24]); + if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight); + } + } break; + case 4 : { + T I[16] = { 0 }; + cimg_forZV(*this,z,v) cimg_for4x4(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat) + (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] + + I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + I[15]*mask[15]); + if (weighted_correl) cimg_forZV(*this,z,v) cimg_for4x4(*this,x,y,z,v,I) { + const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + + I[ 4]*I[ 4] + I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] + + I[ 8]*I[ 8] + I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] + + I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + I[15]*I[15]); + if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight); + } + } break; + case 3 : { + T I[9] = { 0 }; + cimg_forZV(*this,z,v) cimg_for3x3(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat) + (I[0]*mask[0] + I[1]*mask[1] + I[2]*mask[2] + + I[3]*mask[3] + I[4]*mask[4] + I[5]*mask[5] + + I[6]*mask[6] + I[7]*mask[7] + I[8]*mask[8]); + if (weighted_correl) cimg_forZV(*this,z,v) cimg_for3x3(*this,x,y,z,v,I) { + const double weight = (double)(I[0]*I[0] + I[1]*I[1] + I[2]*I[2] + + I[3]*I[3] + I[4]*I[4] + I[5]*I[5] + + I[6]*I[6] + I[7]*I[7] + I[8]*I[8]); + if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight); + } + } break; + case 2 : { + T I[4] = { 0 }; + cimg_forZV(*this,z,v) cimg_for2x2(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat) + (I[0]*mask[0] + I[1]*mask[1] + + I[2]*mask[2] + I[3]*mask[3]); + if (weighted_correl) cimg_forZV(*this,z,v) cimg_for2x2(*this,x,y,z,v,I) { + const double weight = (double)(I[0]*I[0] + I[1]*I[1] + + I[2]*I[2] + I[3]*I[3]); + if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight); + } + } break; + case 1 : (dest.assign(*this))*=mask(0); break; + } + } + } else { // Generic version for other masks + const int + mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2, + mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2), + mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2; + cimg_forV(*this,v) + if (!weighted_correl) { // Classical correlation + for (int z = mz1; z=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Ttfloat val = 0; + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) + val+=_atXYZ(x+xm,y+ym,z+zm,v)*mask(mx1+xm,my1+ym,mz1+zm); + dest(x,y,z,v) = (Ttfloat)val; + } + else + cimg_forYZV(*this,y,z,v) + for (int x = 0; x=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Ttfloat val = 0; + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) + val+=atXYZ(x+xm,y+ym,z+zm,v,0)*mask(mx1+xm,my1+ym,mz1+zm); + dest(x,y,z,v) = (Ttfloat)val; + } + } else { // Weighted correlation + for (int z = mz1; z(Ttfloat)0)?(Ttfloat)(val/cimg_std::sqrt((double)weight)):(Ttfloat)0; + } + if (cond) + cimg_forYZV(*this,y,z,v) + for (int x = 0; x=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Ttfloat val = 0, weight = 0; + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const Ttfloat cval = (Ttfloat)_atXYZ(x+xm,y+ym,z+zm,v); + val+=cval*mask(mx1+xm,my1+ym,mz1+zm); + weight+=cval*cval; + } + dest(x,y,z,v) = (weight>(Ttfloat)0)?(Ttfloat)(val/cimg_std::sqrt((double)weight)):(Ttfloat)0; + } + else + cimg_forYZV(*this,y,z,v) + for (int x = 0; x=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Ttfloat val = 0, weight = 0; + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const Ttfloat cval = (Ttfloat)atXYZ(x+xm,y+ym,z+zm,v,0); + val+=cval*mask(mx1+xm,my1+ym,mz1+zm); + weight+=cval*cval; + } + dest(x,y,z,v) = (weight>(Ttfloat)0)?(Ttfloat)(val/cimg_std::sqrt((double)weight)):(Ttfloat)0; + } + } + } + return dest; + } + + //! Compute the convolution of the image by a mask. + /** + The result \p res of the convolution of an image \p img by a mask \p mask is defined to be : + + res(x,y,z) = sum_{i,j,k} img(x-i,y-j,z-k)*mask(i,j,k) + + \param mask = the correlation kernel. + \param cond = the border condition type (0=zero, 1=dirichlet) + \param weighted_convol = enable local normalization. + **/ + template + CImg& convolve(const CImg& mask, const unsigned int cond=1, const bool weighted_convol=false) { + return get_convolve(mask,cond,weighted_convol).transfer_to(*this); + } + + template + CImg::type> get_convolve(const CImg& mask, const unsigned int cond=1, + const bool weighted_convol=false) const { + typedef typename cimg::superset2::type Ttfloat; + if (is_empty()) return *this; + if (!mask || mask.dim!=1) + throw CImgArgumentException("CImg<%s>::convolve() : Specified mask (%u,%u,%u,%u,%p) is not scalar.", + pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data); + return get_correlate(CImg(mask.ptr(),mask.size(),1,1,1,true).get_mirror('x').resize(mask,-1),cond,weighted_convol); + } + + //! Return the erosion of the image by a structuring element. + template + CImg& erode(const CImg& mask, const unsigned int cond=1, const bool weighted_erosion=false) { + return get_erode(mask,cond,weighted_erosion).transfer_to(*this); + } + + template + CImg::type> get_erode(const CImg& mask, const unsigned int cond=1, + const bool weighted_erosion=false) const { + typedef typename cimg::superset::type Tt; + if (is_empty()) return *this; + if (!mask || mask.dim!=1) + throw CImgArgumentException("CImg<%s>::erode() : Specified mask (%u,%u,%u,%u,%p) is not a scalar image.", + pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data); + CImg dest(width,height,depth,dim); + const int + mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2, + mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2), + mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2; + cimg_forV(*this,v) + if (!weighted_erosion) { // Classical erosion + for (int z = mz1; z::max(); + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const Tt cval = (Tt)(*this)(x+xm,y+ym,z+zm,v); + if (mask(mx1+xm,my1+ym,mz1+zm) && cval=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Tt min_val = cimg::type::max(); + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const T cval = (Tt)_atXYZ(x+xm,y+ym,z+zm,v); + if (mask(mx1+xm,my1+ym,mz1+zm) && cval=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Tt min_val = cimg::type::max(); + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const T cval = (Tt)atXYZ(x+xm,y+ym,z+zm,v,0); + if (mask(mx1+xm,my1+ym,mz1+zm) && cval::max(); + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = mask(mx1+xm,my1+ym,mz1+zm); + const Tt cval = (Tt)((*this)(x+xm,y+ym,z+zm,v) + mval); + if (mval && cval=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Tt min_val = cimg::type::max(); + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = mask(mx1+xm,my1+ym,mz1+zm); + const Tt cval = (Tt)(_atXYZ(x+xm,y+ym,z+zm,v) + mval); + if (mval && cval=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Tt min_val = cimg::type::max(); + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = mask(mx1+xm,my1+ym,mz1+zm); + const Tt cval = (Tt)(atXYZ(x+xm,y+ym,z+zm,v,0) + mval); + if (mval && cval& erode(const unsigned int n, const unsigned int cond=1) { + if (n<2) return *this; + return get_erode(n,cond).transfer_to(*this); + } + + CImg get_erode(const unsigned int n, const unsigned int cond=1) const { + static CImg mask; + if (n<2) return *this; + if (mask.width!=n) mask.assign(n,n,1,1,1); + const CImg res = get_erode(mask,cond,false); + if (n>20) mask.assign(); + return res; + } + + //! Dilate the image by a structuring element. + template + CImg& dilate(const CImg& mask, const unsigned int cond=1, const bool weighted_dilatation=false) { + return get_dilate(mask,cond,weighted_dilatation).transfer_to(*this); + } + + template + CImg::type> get_dilate(const CImg& mask, const unsigned int cond=1, + const bool weighted_dilatation=false) const { + typedef typename cimg::superset::type Tt; + if (is_empty()) return *this; + if (!mask || mask.dim!=1) + throw CImgArgumentException("CImg<%s>::dilate() : Specified mask (%u,%u,%u,%u,%p) is not a scalar image.", + pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data); + CImg dest(width,height,depth,dim); + const int + mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2, + mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2), + mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2; + cimg_forV(*this,v) + if (!weighted_dilatation) { // Classical dilatation + for (int z = mz1; z::min(); + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const Tt cval = (Tt)(*this)(x+xm,y+ym,z+zm,v); + if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval; + } + dest(x,y,z,v) = max_val; + } + if (cond) + cimg_forYZV(*this,y,z,v) + for (int x = 0; x=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Tt max_val = cimg::type::min(); + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const T cval = (Tt)_atXYZ(x+xm,y+ym,z+zm,v); + if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval; + } + dest(x,y,z,v) = max_val; + } + else + cimg_forYZV(*this,y,z,v) + for (int x = 0; x=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Tt max_val = cimg::type::min(); + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const T cval = (Tt)atXYZ(x+xm,y+ym,z+zm,v,0); + if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval; + } + dest(x,y,z,v) = max_val; + } + } else { // Weighted dilatation + for (int z = mz1; z::min(); + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = mask(mx1+xm,my1+ym,mz1+zm); + const Tt cval = (Tt)((*this)(x+xm,y+ym,z+zm,v) - mval); + if (mval && cval>max_val) max_val = cval; + } + dest(x,y,z,v) = max_val; + } + if (cond) + cimg_forYZV(*this,y,z,v) + for (int x = 0; x=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Tt max_val = cimg::type::min(); + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = mask(mx1+xm,my1+ym,mz1+zm); + const Tt cval = (Tt)(_atXYZ(x+xm,y+ym,z+zm,v) - mval); + if (mval && cval>max_val) max_val = cval; + } + dest(x,y,z,v) = max_val; + } + else + cimg_forYZV(*this,y,z,v) + for (int x = 0; x=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Tt max_val = cimg::type::min(); + for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) { + const t mval = mask(mx1+xm,my1+ym,mz1+zm); + const Tt cval = (Tt)(atXYZ(x+xm,y+ym,z+zm,v,0) - mval); + if (mval && cval>max_val) max_val = cval; + } + dest(x,y,z,v) = max_val; + } + } + return dest; + } + + //! Dilate the image by a square structuring element of size n. + CImg& dilate(const unsigned int n, const unsigned int cond=1) { + if (n<2) return *this; + return get_dilate(n,cond).transfer_to(*this); + } + + CImg get_dilate(const unsigned int n, const unsigned int cond=1) const { + static CImg mask; + if (n<2) return *this; + if (mask.width!=n) mask.assign(n,n,1,1,1); + const CImg res = get_dilate(mask,cond,false); + if (n>20) mask.assign(); + return res; + } + + //! Add noise to the image. + /** + \param sigma = power of the noise. if sigma<0, it corresponds to the percentage of the maximum image value. + \param ntype = noise type. can be 0=gaussian, 1=uniform or 2=Salt and Pepper, 3=Poisson, 4=Rician. + \return A noisy version of the instance image. + **/ + CImg& noise(const double sigma, const unsigned int noise_type=0) { + if (!is_empty()) { + double nsigma = sigma, max = (double)cimg::type::max(), min = (double)cimg::type::min(); + Tfloat m = 0, M = 0; + if (nsigma==0 && noise_type!=3) return *this; + if (nsigma<0 || noise_type==2) m = (Tfloat)minmax(M); + if (nsigma<0) nsigma = -nsigma*(M-m)/100.0; + switch (noise_type) { + case 0 : { // Gaussian noise + cimg_for(*this,ptr,T) { + double val = *ptr + nsigma*cimg::grand(); + if (val>max) val = max; + if (valmax) val = max; + if (val::is_float()?1:cimg::type::max()); } + cimg_for(*this,ptr,T) if (cimg::rand()*100max) val = max; + if (val::noise() : Invalid noise type %d " + "(should be {0=Gaussian, 1=Uniform, 2=Salt&Pepper, 3=Poisson}).",pixel_type(),noise_type); + } + } + return *this; + } + + CImg get_noise(const double sigma, const unsigned int noise_type=0) const { + return (+*this).noise(sigma,noise_type); + } + + //! Compute the result of the Deriche filter. + /** + The Canny-Deriche filter is a recursive algorithm allowing to compute blurred derivatives of + order 0,1 or 2 of an image. + **/ + CImg& deriche(const float sigma, const int order=0, const char axis='x', const bool cond=true) { +#define _cimg_deriche2_apply \ + Tfloat *ptrY = Y.data, yb = 0, yp = 0; \ + T xp = (T)0; \ + if (cond) { xp = *ptrX; yb = yp = (Tfloat)(coefp*xp); } \ + for (int m=0; m=0; --n) { \ + const T xc = *(ptrX-=off); \ + const Tfloat yc = (Tfloat)(a2*xn + a3*xa - b1*yn - b2*ya); \ + xa = xn; xn = xc; ya = yn; yn = yc; \ + *ptrX = (T)(*(--ptrY)+yc); \ + } + if (sigma<0) + throw CImgArgumentException("CImg<%s>::deriche() : Given filter variance (sigma = %g) is negative", + pixel_type(),sigma); + if (is_empty() || (sigma<0.1 && !order)) return *this; + const float + nsigma = sigma<0.1f?0.1f:sigma, + alpha = 1.695f/nsigma, + ema = (float)cimg_std::exp(-alpha), + ema2 = (float)cimg_std::exp(-2*alpha), + b1 = -2*ema, + b2 = ema2; + float a0 = 0, a1 = 0, a2 = 0, a3 = 0, coefp = 0, coefn = 0; + switch (order) { + case 0 : { + const float k = (1-ema)*(1-ema)/(1+2*alpha*ema-ema2); + a0 = k; + a1 = k*(alpha-1)*ema; + a2 = k*(alpha+1)*ema; + a3 = -k*ema2; + } break; + case 1 : { + const float k = (1-ema)*(1-ema)/ema; + a0 = k*ema; + a1 = a3 = 0; + a2 = -a0; + } break; + case 2 : { + const float + ea = (float)cimg_std::exp(-alpha), + k = -(ema2-1)/(2*alpha*ema), + kn = (-2*(-1+3*ea-3*ea*ea+ea*ea*ea)/(3*ea+1+3*ea*ea+ea*ea*ea)); + a0 = kn; + a1 = -kn*(1+k*alpha)*ema; + a2 = kn*(1-k*alpha)*ema; + a3 = -kn*ema2; + } break; + default : + throw CImgArgumentException("CImg<%s>::deriche() : Given filter order (order = %u) must be 0,1 or 2", + pixel_type(),order); + } + coefp = (a0+a1)/(1+b1+b2); + coefn = (a2+a3)/(1+b1+b2); + switch (cimg::uncase(axis)) { + case 'x' : { + const int N = width, off = 1; + CImg Y(N); + cimg_forYZV(*this,y,z,v) { T *ptrX = ptr(0,y,z,v); _cimg_deriche2_apply; } + } break; + case 'y' : { + const int N = height, off = width; + CImg Y(N); + cimg_forXZV(*this,x,z,v) { T *ptrX = ptr(x,0,z,v); _cimg_deriche2_apply; } + } break; + case 'z' : { + const int N = depth, off = width*height; + CImg Y(N); + cimg_forXYV(*this,x,y,v) { T *ptrX = ptr(x,y,0,v); _cimg_deriche2_apply; } + } break; + case 'v' : { + const int N = dim, off = width*height*depth; + CImg Y(N); + cimg_forXYZ(*this,x,y,z) { T *ptrX = ptr(x,y,z,0); _cimg_deriche2_apply; } + } break; + } + return *this; + } + + CImg get_deriche(const float sigma, const int order=0, const char axis='x', const bool cond=true) const { + return CImg(*this,false).deriche(sigma,order,axis,cond); + } + + //! Return a blurred version of the image, using a Canny-Deriche filter. + /** + Blur the image with an anisotropic exponential filter (Deriche filter of order 0). + **/ + CImg& blur(const float sigmax, const float sigmay, const float sigmaz, const bool cond=true) { + if (!is_empty()) { + if (width>1 && sigmax>0) deriche(sigmax,0,'x',cond); + if (height>1 && sigmay>0) deriche(sigmay,0,'y',cond); + if (depth>1 && sigmaz>0) deriche(sigmaz,0,'z',cond); + } + return *this; + } + + CImg get_blur(const float sigmax, const float sigmay, const float sigmaz, + const bool cond=true) const { + return CImg(*this,false).blur(sigmax,sigmay,sigmaz,cond); + } + + //! Return a blurred version of the image, using a Canny-Deriche filter. + CImg& blur(const float sigma, const bool cond=true) { + return blur(sigma,sigma,sigma,cond); + } + + CImg get_blur(const float sigma, const bool cond=true) const { + return CImg(*this,false).blur(sigma,cond); + } + + //! Blur the image anisotropically following a field of diffusion tensors. + /** + \param G = Field of square roots of diffusion tensors used to drive the smoothing. + \param amplitude = amplitude of the smoothing. + \param dl = spatial discretization. + \param da = angular discretization. + \param gauss_prec = precision of the gaussian function. + \param interpolation Used interpolation scheme (0 = nearest-neighbor, 1 = linear, 2 = Runge-Kutta) + \param fast_approx = Tell to use the fast approximation or not. + **/ + template + CImg& blur_anisotropic(const CImg& G, const float amplitude=60, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true) { +#define _cimg_valign2d(i,j) \ + { Tfloat &u = W(i,j,0,0), &v = W(i,j,0,1); \ + if (u*curru + v*currv<0) { u=-u; v=-v; }} +#define _cimg_valign3d(i,j,k) \ + { Tfloat &u = W(i,j,k,0), &v = W(i,j,k,1), &w = W(i,j,k,2); \ + if (u*curru + v*currv + w*currw<0) { u=-u; v=-v; w=-w; }} + + // Check arguments and init variables + if (!is_empty() && amplitude>0) { + if (!G || (G.dim!=3 && G.dim!=6) || G.width!=width || G.height!=height || G.depth!=depth) + throw CImgArgumentException("CImg<%s>::blur_anisotropic() : Specified tensor field (%u,%u,%u,%u) is not valid.", + pixel_type(),G.width,G.height,G.depth,G.dim); + + const float sqrt2amplitude = (float)cimg_std::sqrt(2*amplitude); + const bool threed = (G.dim>=6); + const int + dx1 = dimx()-1, + dy1 = dimy()-1, + dz1 = dimz()-1; + CImg + dest(width,height,depth,dim,0), + W(width,height,depth,threed?4:3), + tmp(dim); + int N = 0; + + if (threed) + // 3D version of the algorithm + for (float phi=(180%(int)da)/2.0f; phi<=180; phi+=da) { + const float + phir = (float)(phi*cimg::valuePI/180), + datmp = (float)(da/cimg_std::cos(phir)), + da2 = datmp<1?360.0f:datmp; + + for (float theta=0; theta<360; (theta+=da2),++N) { + const float + thetar = (float)(theta*cimg::valuePI/180), + vx = (float)(cimg_std::cos(thetar)*cimg_std::cos(phir)), + vy = (float)(cimg_std::sin(thetar)*cimg_std::cos(phir)), + vz = (float)cimg_std::sin(phir); + const t + *pa = G.ptr(0,0,0,0), + *pb = G.ptr(0,0,0,1), + *pc = G.ptr(0,0,0,2), + *pd = G.ptr(0,0,0,3), + *pe = G.ptr(0,0,0,4), + *pf = G.ptr(0,0,0,5); + Tfloat + *pd0 = W.ptr(0,0,0,0), + *pd1 = W.ptr(0,0,0,1), + *pd2 = W.ptr(0,0,0,2), + *pd3 = W.ptr(0,0,0,3); + cimg_forXYZ(G,xg,yg,zg) { + const t + a = *(pa++), b = *(pb++), c = *(pc++), + d = *(pd++), e = *(pe++), f = *(pf++); + const float + u = (float)(a*vx + b*vy + c*vz), + v = (float)(b*vx + d*vy + e*vz), + w = (float)(c*vx + e*vy + f*vz), + n = (float)cimg_std::sqrt(1e-5+u*u+v*v+w*w), + dln = dl/n; + *(pd0++) = (Tfloat)(u*dln); + *(pd1++) = (Tfloat)(v*dln); + *(pd2++) = (Tfloat)(w*dln); + *(pd3++) = (Tfloat)n; + } + + cimg_forXYZ(*this,x,y,z) { + tmp.fill(0); + const float + cu = (float)W(x,y,z,0), + cv = (float)W(x,y,z,1), + cw = (float)W(x,y,z,2), + n = (float)W(x,y,z,3), + fsigma = (float)(n*sqrt2amplitude), + length = gauss_prec*fsigma, + fsigma2 = 2*fsigma*fsigma; + float + S = 0, + pu = cu, + pv = cv, + pw = cw, + X = (float)x, + Y = (float)y, + Z = (float)z; + + switch (interpolation_type) { + case 0 : { + // Nearest neighbor + for (float l=0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const int + cx = (int)(X+0.5f), + cy = (int)(Y+0.5f), + cz = (int)(Z+0.5f); + float + u = (float)W(cx,cy,cz,0), + v = (float)W(cx,cy,cz,1), + w = (float)W(cx,cy,cz,2); + if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; } + if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)(*this)(cx,cy,cz,k); ++S; } + else { + const float coef = (float)cimg_std::exp(-l*l/fsigma2); + cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*(*this)(cx,cy,cz,k)); + S+=coef; + } + X+=(pu=u); Y+=(pv=v); Z+=(pw=w); + } + } break; + + case 1 : { + // Linear interpolation + for (float l=0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const int + cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1, + cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1, + cz = (int)Z, pz = (cz-1<0)?0:cz-1, nz = (cz+1>dz1)?dz1:cz+1; + const float + curru = (float)W(cx,cy,cz,0), + currv = (float)W(cx,cy,cz,1), + currw = (float)W(cx,cy,cz,2); + _cimg_valign3d(px,py,pz); _cimg_valign3d(cx,py,pz); _cimg_valign3d(nx,py,pz); + _cimg_valign3d(px,cy,pz); _cimg_valign3d(cx,cy,pz); _cimg_valign3d(nx,cy,pz); + _cimg_valign3d(px,ny,pz); _cimg_valign3d(cx,ny,pz); _cimg_valign3d(nx,ny,pz); + _cimg_valign3d(px,py,cz); _cimg_valign3d(cx,py,cz); _cimg_valign3d(nx,py,cz); + _cimg_valign3d(px,cy,cz); _cimg_valign3d(nx,cy,cz); + _cimg_valign3d(px,ny,cz); _cimg_valign3d(cx,ny,cz); _cimg_valign3d(nx,ny,cz); + _cimg_valign3d(px,py,nz); _cimg_valign3d(cx,py,nz); _cimg_valign3d(nx,py,nz); + _cimg_valign3d(px,cy,nz); _cimg_valign3d(cx,cy,nz); _cimg_valign3d(nx,cy,nz); + _cimg_valign3d(px,ny,nz); _cimg_valign3d(cx,ny,nz); _cimg_valign3d(nx,ny,nz); + float + u = (float)(W._linear_atXYZ(X,Y,Z,0)), + v = (float)(W._linear_atXYZ(X,Y,Z,1)), + w = (float)(W._linear_atXYZ(X,Y,Z,2)); + if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; } + if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXYZ(X,Y,Z,k); ++S; } + else { + const float coef = (float)cimg_std::exp(-l*l/fsigma2); + cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,k)); + S+=coef; + } + X+=(pu=u); Y+=(pv=v); Z+=(pw=w); + } + } break; + + default : { + // 2nd order Runge Kutta + for (float l=0; l=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) { + const int + cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1, + cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1, + cz = (int)Z, pz = (cz-1<0)?0:cz-1, nz = (cz+1>dz1)?dz1:cz+1; + const float + curru = (float)W(cx,cy,cz,0), + currv = (float)W(cx,cy,cz,1), + currw = (float)W(cx,cy,cz,2); + _cimg_valign3d(px,py,pz); _cimg_valign3d(cx,py,pz); _cimg_valign3d(nx,py,pz); + _cimg_valign3d(px,cy,pz); _cimg_valign3d(cx,cy,pz); _cimg_valign3d(nx,cy,pz); + _cimg_valign3d(px,ny,pz); _cimg_valign3d(cx,ny,pz); _cimg_valign3d(nx,ny,pz); + _cimg_valign3d(px,py,cz); _cimg_valign3d(cx,py,cz); _cimg_valign3d(nx,py,cz); + _cimg_valign3d(px,cy,cz); _cimg_valign3d(nx,cy,cz); + _cimg_valign3d(px,ny,cz); _cimg_valign3d(cx,ny,cz); _cimg_valign3d(nx,ny,cz); + _cimg_valign3d(px,py,nz); _cimg_valign3d(cx,py,nz); _cimg_valign3d(nx,py,nz); + _cimg_valign3d(px,cy,nz); _cimg_valign3d(cx,cy,nz); _cimg_valign3d(nx,cy,nz); + _cimg_valign3d(px,ny,nz); _cimg_valign3d(cx,ny,nz); _cimg_valign3d(nx,ny,nz); + const float + u0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,0)), + v0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,1)), + w0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,2)); + float + u = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,0)), + v = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,1)), + w = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,2)); + if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; } + if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXYZ(X,Y,Z,k); ++S; } + else { + const float coef = (float)cimg_std::exp(-l*l/fsigma2); + cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,k)); + S+=coef; + } + X+=(pu=u); Y+=(pv=v); Z+=(pw=w); + } + } break; + } + if (S>0) cimg_forV(dest,k) dest(x,y,z,k)+=tmp[k]/S; + else cimg_forV(dest,k) dest(x,y,z,k)+=(Tfloat)((*this)(x,y,z,k)); + cimg_plugin_greycstoration_count; + } + } + } else + // 2D version of the algorithm + for (float theta=(360%(int)da)/2.0f; theta<360; (theta+=da),++N) { + const float + thetar = (float)(theta*cimg::valuePI/180), + vx = (float)(cimg_std::cos(thetar)), + vy = (float)(cimg_std::sin(thetar)); + const t + *pa = G.ptr(0,0,0,0), + *pb = G.ptr(0,0,0,1), + *pc = G.ptr(0,0,0,2); + Tfloat + *pd0 = W.ptr(0,0,0,0), + *pd1 = W.ptr(0,0,0,1), + *pd2 = W.ptr(0,0,0,2); + cimg_forXY(G,xg,yg) { + const t a = *(pa++), b = *(pb++), c = *(pc++); + const float + u = (float)(a*vx + b*vy), + v = (float)(b*vx + c*vy), + n = (float)cimg_std::sqrt(1e-5+u*u+v*v), + dln = dl/n; + *(pd0++) = (Tfloat)(u*dln); + *(pd1++) = (Tfloat)(v*dln); + *(pd2++) = (Tfloat)n; + } + + cimg_forXY(*this,x,y) { + tmp.fill(0); + const float + cu = (float)W(x,y,0,0), + cv = (float)W(x,y,0,1), + n = (float)W(x,y,0,2), + fsigma = (float)(n*sqrt2amplitude), + length = gauss_prec*fsigma, + fsigma2 = 2*fsigma*fsigma; + float + S = 0, + pu = cu, + pv = cv, + X = (float)x, + Y = (float)y; + + switch (interpolation_type) { + + case 0 : { + // Nearest-neighbor interpolation for 2D images + for (float l=0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const int + cx = (int)(X+0.5f), + cy = (int)(Y+0.5f); + float + u = (float)W(cx,cy,0,0), + v = (float)W(cx,cy,0,1); + if ((pu*u + pv*v)<0) { u=-u; v=-v; } + if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)(*this)(cx,cy,0,k); ++S; } + else { + const float coef = (float)cimg_std::exp(-l*l/fsigma2); + cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*(*this)(cx,cy,0,k)); + S+=coef; + } + X+=(pu=u); Y+=(pv=v); + } + } break; + + case 1 : { + // Linear interpolation for 2D images + for (float l=0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const int + cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1, + cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1; + const float + curru = (float)W(cx,cy,0,0), + currv = (float)W(cx,cy,0,1); + _cimg_valign2d(px,py); _cimg_valign2d(cx,py); _cimg_valign2d(nx,py); + _cimg_valign2d(px,cy); _cimg_valign2d(nx,cy); + _cimg_valign2d(px,ny); _cimg_valign2d(cx,ny); _cimg_valign2d(nx,ny); + float + u = (float)(W._linear_atXY(X,Y,0,0)), + v = (float)(W._linear_atXY(X,Y,0,1)); + if ((pu*u + pv*v)<0) { u=-u; v=-v; } + if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXY(X,Y,0,k); ++S; } + else { + const float coef = (float)cimg_std::exp(-l*l/fsigma2); + cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXY(X,Y,0,k)); + S+=coef; + } + X+=(pu=u); Y+=(pv=v); + } + } break; + + default : { + // 2nd-order Runge-kutta interpolation for 2D images + for (float l=0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { + const int + cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1, + cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1; + const float + curru = (float)W(cx,cy,0,0), + currv = (float)W(cx,cy,0,1); + _cimg_valign2d(px,py); _cimg_valign2d(cx,py); _cimg_valign2d(nx,py); + _cimg_valign2d(px,cy); _cimg_valign2d(nx,cy); + _cimg_valign2d(px,ny); _cimg_valign2d(cx,ny); _cimg_valign2d(nx,ny); + const float + u0 = (float)(0.5f*W._linear_atXY(X,Y,0,0)), + v0 = (float)(0.5f*W._linear_atXY(X,Y,0,1)); + float + u = (float)(W._linear_atXY(X+u0,Y+v0,0,0)), + v = (float)(W._linear_atXY(X+u0,Y+v0,0,1)); + if ((pu*u + pv*v)<0) { u=-u; v=-v; } + if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXY(X,Y,0,k); ++S; } + else { + const float coef = (float)cimg_std::exp(-l*l/fsigma2); + cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXY(X,Y,0,k)); + S+=coef; + } + X+=(pu=u); Y+=(pv=v); + } + } + } + if (S>0) cimg_forV(dest,k) dest(x,y,0,k)+=tmp[k]/S; + else cimg_forV(dest,k) dest(x,y,0,k)+=(Tfloat)((*this)(x,y,0,k)); + cimg_plugin_greycstoration_count; + } + } + const Tfloat *ptrs = dest.data+dest.size(); + const T m = cimg::type::min(), M = cimg::type::max(); + cimg_for(*this,ptrd,T) { const Tfloat val = *(--ptrs)/N; *ptrd = valM?M:(T)val); } + } + return *this; + } + + template + CImg get_blur_anisotropic(const CImg& G, const float amplitude=60, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true) const { + return (+*this).blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx); + } + + //! Blur an image in an anisotropic way. + /** + \param mask Binary mask. + \param amplitude Amplitude of the anisotropic blur. + \param sharpness Contour preservation. + \param anisotropy Smoothing anisotropy. + \param alpha Image pre-blurring (gaussian). + \param sigma Regularity of the tensor-valued geometry. + \param dl Spatial discretization. + \param da Angular discretization. + \param gauss_prec Precision of the gaussian function. + \param interpolation_type Used interpolation scheme (0 = nearest-neighbor, 1 = linear, 2 = Runge-Kutta) + \param fast_approx Tell to use the fast approximation or not + \param geom_factor Geometry factor. + **/ + template + CImg& blur_anisotropic(const CImg& mask, const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f, + const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true, + const float geom_factor=1) { + if (!is_empty() && amplitude>0) { + if (amplitude==0) return *this; + if (amplitude<0 || sharpness<0 || anisotropy<0 || anisotropy>1 || alpha<0 || sigma<0 || dl<0 || da<0 || gauss_prec<0) + throw CImgArgumentException("CImg<%s>::blur_anisotropic() : Given parameters are amplitude(%g), sharpness(%g), " + "anisotropy(%g), alpha(%g), sigma(%g), dl(%g), da(%g), gauss_prec(%g).\n" + "Admissible parameters are in the range : amplitude>0, sharpness>0, anisotropy in [0,1], " + "alpha>0, sigma>0, dl>0, da>0, gauss_prec>0.", + pixel_type(),amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec); + const bool threed = (depth>1), no_mask = mask.is_empty(); + const float nsharpness = cimg::max(sharpness,1e-5f), power1 = 0.5f*nsharpness, power2 = power1/(1e-7f+1-anisotropy); + CImg blurred = CImg(*this,false).blur(alpha); + if (geom_factor>0) blurred*=geom_factor; + else blurred.normalize(0,-geom_factor); + + if (threed) { // Field for 3D volumes + cimg_plugin_greycstoration_lock; + CImg val(3), vec(3,3), G(blurred.get_structure_tensor()); + if (sigma>0) G.blur(sigma); + cimg_forXYZ(*this,x,y,z) { + if (no_mask || mask(x,y,z)) { + G.get_tensor_at(x,y,z).symmetric_eigen(val,vec); + const float l1 = val[2], l2 = val[1], l3 = val[0], + ux = vec(0,0), uy = vec(0,1), uz = vec(0,2), + vx = vec(1,0), vy = vec(1,1), vz = vec(1,2), + wx = vec(2,0), wy = vec(2,1), wz = vec(2,2), + n1 = (float)cimg_std::pow(1+l1+l2+l3,-power1), + n2 = (float)cimg_std::pow(1+l1+l2+l3,-power2); + G(x,y,z,0) = n1*(ux*ux + vx*vx) + n2*wx*wx; + G(x,y,z,1) = n1*(ux*uy + vx*vy) + n2*wx*wy; + G(x,y,z,2) = n1*(ux*uz + vx*vz) + n2*wx*wz; + G(x,y,z,3) = n1*(uy*uy + vy*vy) + n2*wy*wy; + G(x,y,z,4) = n1*(uy*uz + vy*vz) + n2*wy*wz; + G(x,y,z,5) = n1*(uz*uz + vz*vz) + n2*wz*wz; + } else G(x,y,z,0) = G(x,y,z,1) = G(x,y,z,2) = G(x,y,z,3) = G(x,y,z,4) = G(x,y,z,5) = 0; + cimg_plugin_greycstoration_count; + } + cimg_plugin_greycstoration_unlock; + blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx); + } else { // Field for 2D images + cimg_plugin_greycstoration_lock; + CImg val(2), vec(2,2), G(blurred.get_structure_tensor()); + if (sigma>0) G.blur(sigma); + cimg_forXY(*this,x,y) { + if (no_mask || mask(x,y)) { + G.get_tensor_at(x,y).symmetric_eigen(val,vec); + const float l1 = val[1], l2 = val[0], + ux = vec(1,0), uy = vec(1,1), + vx = vec(0,0), vy = vec(0,1), + n1 = (float)cimg_std::pow(1+l1+l2,-power1), + n2 = (float)cimg_std::pow(1+l1+l2,-power2); + G(x,y,0,0) = n1*ux*ux + n2*vx*vx; + G(x,y,0,1) = n1*ux*uy + n2*vx*vy; + G(x,y,0,2) = n1*uy*uy + n2*vy*vy; + } else G(x,y,0,0) = G(x,y,0,1) = G(x,y,0,2) = 0; + cimg_plugin_greycstoration_count; + } + cimg_plugin_greycstoration_unlock; + blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx); + } + } + return *this; + } + + template + CImg get_blur_anisotropic(const CImg& mask, const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f, + const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, + const float da=30, const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool fast_approx=true, const float geom_factor=1) const { + return (+*this).blur_anisotropic(mask,amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx,geom_factor); + } + + //! Blur an image following in an anisotropic way. + CImg& blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f, + const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30, + const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true, + const float geom_factor=1) { + return blur_anisotropic(CImg(),amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx,geom_factor); + } + + CImg get_blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f, + const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, + const float da=30, const float gauss_prec=2, const unsigned int interpolation_type=0, + const bool fast_approx=true, const float geom_factor=1) const { + return (+*this).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx,geom_factor); + } + + //! Blur an image using the bilateral filter. + /** + \param sigmax Amount of blur along the X-axis. + \param sigmay Amount of blur along the Y-axis. + \param sigmaz Amount of blur along the Z-axis. + \param sigmar Amount of blur along the range axis. + \param bgridx Size of the bilateral grid along the X-axis. + \param bgridy Size of the bilateral grid along the Y-axis. + \param bgridz Size of the bilateral grid along the Z-axis. + \param bgridr Size of the bilateral grid along the range axis. + \param interpolation_type Use interpolation for image slicing. + \note This algorithm uses the optimisation technique proposed by S. Paris and F. Durand, in ECCV'2006 + (extended for 3D volumetric images). + **/ + CImg& blur_bilateral(const float sigmax, const float sigmay, const float sigmaz, const float sigmar, + const int bgridx, const int bgridy, const int bgridz, const int bgridr, + const bool interpolation_type=true) { + T m, M = maxmin(m); + const float range = (float)(1.0f+M-m); + const unsigned int + bx0 = bgridx>=0?bgridx:width*(-bgridx)/100, + by0 = bgridy>=0?bgridy:height*(-bgridy)/100, + bz0 = bgridz>=0?bgridz:depth*(-bgridz)/100, + br0 = bgridr>=0?bgridr:(int)(-range*bgridr/100), + bx = bx0>0?bx0:1, + by = by0>0?by0:1, + bz = bz0>0?bz0:1, + br = br0>0?br0:1; + const float + nsigmax = sigmax*bx/width, + nsigmay = sigmay*by/height, + nsigmaz = sigmaz*bz/depth, + nsigmar = sigmar*br/range; + if (nsigmax>0 || nsigmay>0 || nsigmaz>0 || nsigmar>0) { + const bool threed = depth>1; + if (threed) { // 3d version of the algorithm + CImg bgrid(bx,by,bz,br), bgridw(bx,by,bz,br); + cimg_forV(*this,k) { + bgrid.fill(0); bgridw.fill(0); + cimg_forXYZ(*this,x,y,z) { + const T val = (*this)(x,y,z,k); + const int X = x*bx/width, Y = y*by/height, Z = z*bz/depth, R = (int)((val-m)*br/range); + bgrid(X,Y,Z,R) = (float)val; + bgridw(X,Y,Z,R) = 1; + } + bgrid.blur(nsigmax,nsigmay,nsigmaz,true).deriche(nsigmar,0,'v',false); + bgridw.blur(nsigmax,nsigmay,nsigmaz,true).deriche(nsigmar,0,'v',false); + if (interpolation_type) cimg_forXYZ(*this,x,y,z) { + const T val = (*this)(x,y,z,k); + const float X = (float)x*bx/width, Y = (float)y*by/height, Z = (float)z*bz/depth, R = (float)((val-m)*br/range), + bval0 = bgrid._linear_atXYZV(X,Y,Z,R), bval1 = bgridw._linear_atXYZV(X,Y,Z,R); + (*this)(x,y,z,k) = (T)(bval0/bval1); + } else cimg_forXYZ(*this,x,y,z) { + const T val = (*this)(x,y,z,k); + const int X = x*bx/width, Y = y*by/height, Z = z*bz/depth, R = (int)((val-m)*br/range); + const float bval0 = bgrid(X,Y,Z,R), bval1 = bgridw(X,Y,Z,R); + (*this)(x,y,z,k) = (T)(bval0/bval1); + } + } + } else { // 2d version of the algorithm + CImg bgrid(bx,by,br,2); + cimg_forV(*this,k) { + bgrid.fill(0); + cimg_forXY(*this,x,y) { + const T val = (*this)(x,y,k); + const int X = x*bx/width, Y = y*by/height, R = (int)((val-m)*br/range); + bgrid(X,Y,R,0) = (float)val; + bgrid(X,Y,R,1) = 1; + } + bgrid.blur(nsigmax,nsigmay,0,true).blur(0,0,nsigmar,false); + if (interpolation_type) cimg_forXY(*this,x,y) { + const T val = (*this)(x,y,k); + const float X = (float)x*bx/width, Y = (float)y*by/height, R = (float)((val-m)*br/range), + bval0 = bgrid._linear_atXYZ(X,Y,R,0), bval1 = bgrid._linear_atXYZ(X,Y,R,1); + (*this)(x,y,k) = (T)(bval0/bval1); + } else cimg_forXY(*this,x,y) { + const T val = (*this)(x,y,k); + const int X = x*bx/width, Y = y*by/height, R = (int)((val-m)*br/range); + const float bval0 = bgrid(X,Y,R,0), bval1 = bgrid(X,Y,R,1); + (*this)(x,y,k) = (T)(bval0/bval1); + } + } + } + } + return *this; + } + + CImg get_blur_bilateral(const float sigmax, const float sigmay, const float sigmaz, const float sigmar, + const int bgridx, const int bgridy, const int bgridz, const int bgridr, + const bool interpolation_type=true) const { + return (+*this).blur_bilateral(sigmax,sigmay,sigmaz,sigmar,bgridx,bgridy,bgridz,bgridr,interpolation_type); + } + + //! Blur an image using the bilateral filter. + CImg& blur_bilateral(const float sigmas, const float sigmar, const int bgrids=-33, const int bgridr=32, + const bool interpolation_type=true) { + return blur_bilateral(sigmas,sigmas,sigmas,sigmar,bgrids,bgrids,bgrids,bgridr,interpolation_type); + } + + CImg get_blur_bilateral(const float sigmas, const float sigmar, const int bgrids=-33, const int bgridr=32, + const bool interpolation_type=true) const { + return (+*this).blur_bilateral(sigmas,sigmas,sigmas,sigmar,bgrids,bgrids,bgrids,bgridr,interpolation_type); + } + + //! Blur an image in its patch-based space. + CImg& blur_patch(const unsigned int patch_size, const float sigma_p, const float sigma_s=10, + const unsigned int lookup_size=4, const bool fast_approx=true) { + +#define _cimg_blur_patch_fastfunc(x) ((x)>3?0:1) +#define _cimg_blur_patch_slowfunc(x) cimg_std::exp(-(x)) +#define _cimg_blur_patch3d(N,func) { \ + const unsigned int N3 = N*N*N; \ + cimg_for##N##XYZ(*this,x,y,z) { \ + cimg_plugin_greycstoration_count; \ + cimg_forV(*this,k) cimg_get##N##x##N##x##N(*this,x,y,z,k,P.ptr(N3*k)); \ + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \ + float sum_weights = 0; \ + cimg_for_in##N##XYZ(*this,x0,y0,z0,x1,y1,z1,p,q,r) { \ + cimg_forV(*this,k) cimg_get##N##x##N##x##N(*this,p,q,r,k,Q.ptr(N3*k)); \ + float distance2 = 0; \ + const T *pQ = Q.end(); \ + cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(--pQ); distance2+=dI*dI; } \ + distance2/=Pnorm; \ + const float dx = (float)p - x, dy = (float)q - y, dz = (float)r - z, \ + alldist = distance2 + (dx*dx + dy*dy + dz*dz)/sigma_s2, weight = (float)func(alldist); \ + sum_weights+=weight; \ + { cimg_forV(*this,k) res(x,y,z,k)+=weight*(*this)(p,q,r,k); } \ + } \ + if (sum_weights>0) cimg_forV(*this,k) res(x,y,z,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,z,k) = (Tfloat)((*this)(x,y,z,k)); \ + }} +#define _cimg_blur_patch2d(N,func) { \ + const unsigned int N2 = N*N; \ + cimg_for##N##XY(*this,x,y) { \ + cimg_plugin_greycstoration_count; \ + cimg_forV(*this,k) cimg_get##N##x##N(*this,x,y,0,k,P.ptr(N2*k)); \ + const int x0 = x-rsize1, y0 = y-rsize1, x1 = x+rsize2, y1 = y+rsize2; \ + float sum_weights = 0; \ + cimg_for_in##N##XY(*this,x0,y0,x1,y1,p,q) { \ + cimg_forV(*this,k) cimg_get##N##x##N(*this,p,q,0,k,Q.ptr(N2*k)); \ + float distance2 = 0; \ + const T *pQ = Q.end(); \ + cimg_for(P,pP,T) { const float dI = (float)*pP-(float)*(--pQ); distance2+=dI*dI; } \ + distance2/=Pnorm; \ + const float dx = (float)p-x, dy = (float)q-y, \ + alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = (float)func(alldist); \ + sum_weights+=weight; \ + { cimg_forV(*this,k) res(x,y,k)+=weight*(*this)(p,q,k); } \ + } \ + if (sum_weights>0) cimg_forV(*this,k) res(x,y,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,k) = (Tfloat)((*this)(x,y,k)); \ + }} + + CImg res(width,height,depth,dim,0); + CImg P(patch_size*patch_size*dim), Q(P); + const float sigma_s2 = sigma_s*sigma_s, sigma_p2 = sigma_p*sigma_p, Pnorm = P.size()*sigma_p2; + const int rsize2 = (int)lookup_size/2, rsize1 = rsize2-1+(lookup_size%2); + if (depth>1) switch (patch_size) { // 3D version + case 2 : + if (fast_approx) { _cimg_blur_patch3d(2,_cimg_blur_patch_fastfunc); } + else { _cimg_blur_patch3d(2,_cimg_blur_patch_slowfunc); } + break; + case 3 : + if (fast_approx) { _cimg_blur_patch3d(3,_cimg_blur_patch_fastfunc); } + else { _cimg_blur_patch3d(3,_cimg_blur_patch_slowfunc); } + break; + default : { + const int psize1 = (int)patch_size/2, psize0 = psize1-1+(patch_size%2); + cimg_forXYZ(*this,x,y,z) { + cimg_plugin_greycstoration_count; + P = get_crop(x - psize0,y - psize0,z - psize0,x + psize1,y + psize1,z + psize1,true); + const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; + float sum_weights = 0; + cimg_for_inXYZ(*this,x0,y0,z0,x1,y1,z1,p,q,r) { + (Q = get_crop(p - psize0,q - psize0,r - psize0,p + psize1,q + psize1,r + psize1,true))-=P; + const float + dx = (float)x - p, dy = (float)y - q, dz = (float)z - r, + distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy + dz*dz)/sigma_s2), + weight = (float)cimg_std::exp(-distance2); + sum_weights+=weight; + cimg_forV(*this,k) res(x,y,z,k)+=weight*(*this)(p,q,r,k); + } + if (sum_weights>0) cimg_forV(*this,k) res(x,y,z,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,z,k) = (Tfloat)((*this)(x,y,z,k)); + } + } + } else switch (patch_size) { // 2D version + case 2 : + if (fast_approx) { _cimg_blur_patch2d(2,_cimg_blur_patch_fastfunc); } + else { _cimg_blur_patch2d(2,_cimg_blur_patch_slowfunc); } + break; + case 3 : + if (fast_approx) { _cimg_blur_patch2d(3,_cimg_blur_patch_fastfunc); } + else { _cimg_blur_patch2d(3,_cimg_blur_patch_slowfunc); } + break; + case 4 : + if (fast_approx) { _cimg_blur_patch2d(4,_cimg_blur_patch_fastfunc); } + else { _cimg_blur_patch2d(4,_cimg_blur_patch_slowfunc); } + break; + case 5 : + if (fast_approx) { _cimg_blur_patch2d(5,_cimg_blur_patch_fastfunc); } + else { _cimg_blur_patch2d(5,_cimg_blur_patch_slowfunc); } + break; + case 6 : + if (fast_approx) { _cimg_blur_patch2d(6,_cimg_blur_patch_fastfunc); } + else { _cimg_blur_patch2d(6,_cimg_blur_patch_slowfunc); } + break; + case 7 : + if (fast_approx) { _cimg_blur_patch2d(7,_cimg_blur_patch_fastfunc); } + else { _cimg_blur_patch2d(7,_cimg_blur_patch_slowfunc); } + break; + case 8 : + if (fast_approx) { _cimg_blur_patch2d(8,_cimg_blur_patch_fastfunc); } + else { _cimg_blur_patch2d(8,_cimg_blur_patch_slowfunc); } + break; + case 9 : + if (fast_approx) { _cimg_blur_patch2d(9,_cimg_blur_patch_fastfunc); } + else { _cimg_blur_patch2d(9,_cimg_blur_patch_slowfunc); } + break; + default : { + const int psize1 = (int)patch_size/2, psize0 = psize1-1+(patch_size%2); + cimg_forXY(*this,x,y) { + cimg_plugin_greycstoration_count; + P = get_crop(x - psize0,y - psize0,x + psize1,y + psize1,true); + const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; + float sum_weights = 0; + cimg_for_inXY(*this,x0,y0,x1,y1,p,q) { + (Q = get_crop(p - psize0,q - psize0,p + psize1,q + psize1,true))-=P; + const float + dx = (float)x - p, dy = (float)y - q, + distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy)/sigma_s2), + weight = (float)cimg_std::exp(-distance2); + sum_weights+=weight; + cimg_forV(*this,k) res(x,y,0,k)+=weight*(*this)(p,q,0,k); + } + if (sum_weights>0) cimg_forV(*this,k) res(x,y,0,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,0,k) = (Tfloat)((*this)(x,y,0,k)); + } + } + } + return res.transfer_to(*this); + } + + CImg get_blur_patch(const unsigned int patch_size, const float sigma_p, const float sigma_s=10, + const unsigned int lookup_size=4, const bool fast_approx=true) const { + return (+*this).blur_patch(patch_size,sigma_p,sigma_s,lookup_size,fast_approx); + } + + //! Compute the Fast Fourier Transform of an image (along a specified axis). + CImgList get_FFT(const char axis, const bool invert=false) const { + return CImgList(*this).FFT(axis,invert); + } + + //! Compute the Fast Fourier Transform on an image. + CImgList get_FFT(const bool invert=false) const { + return CImgList(*this).FFT(invert); + } + + //! Apply a median filter. + CImg& blur_median(const unsigned int n) { + return get_blur_median(n).transfer_to(*this); + } + + CImg get_blur_median(const unsigned int n) { + CImg res(width,height,depth,dim); + if (!n || n==1) return *this; + const int hl=n/2, hr=hl-1+n%2; + if (res.depth!=1) { // 3D median filter + CImg vois; + cimg_forXYZV(*this,x,y,z,k) { + const int + x0 = x - hl, y0 = y - hl, z0 = z-hl, x1 = x + hr, y1 = y + hr, z1 = z+hr, + nx0 = x0<0?0:x0, ny0 = y0<0?0:y0, nz0 = z0<0?0:z0, + nx1 = x1>=dimx()?dimx()-1:x1, ny1 = y1>=dimy()?dimy()-1:y1, nz1 = z1>=dimz()?dimz()-1:z1; + vois = get_crop(nx0,ny0,nz0,k,nx1,ny1,nz1,k); + res(x,y,z,k) = vois.median(); + } + } else { +#define _cimg_median_sort(a,b) if ((a)>(b)) cimg::swap(a,b) + if (res.height!=1) switch (n) { // 2D median filter + case 3 : { + T I[9] = { 0 }; + CImg_3x3(J,T); + cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) { + cimg_std::memcpy(J,I,9*sizeof(T)); + _cimg_median_sort(Jcp, Jnp); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jcn, Jnn); + _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jpc, Jcc); _cimg_median_sort(Jpn, Jcn); + _cimg_median_sort(Jcp, Jnp); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jcn, Jnn); + _cimg_median_sort(Jpp, Jpc); _cimg_median_sort(Jnc, Jnn); _cimg_median_sort(Jcc, Jcn); + _cimg_median_sort(Jpc, Jpn); _cimg_median_sort(Jcp, Jcc); _cimg_median_sort(Jnp, Jnc); + _cimg_median_sort(Jcc, Jcn); _cimg_median_sort(Jcc, Jnp); _cimg_median_sort(Jpn, Jcc); + _cimg_median_sort(Jcc, Jnp); + res(x,y,0,k) = Jcc; + } + } break; + case 5 : { + T I[25] = { 0 }; + CImg_5x5(J,T); + cimg_forV(*this,k) cimg_for5x5(*this,x,y,0,k,I) { + cimg_std::memcpy(J,I,25*sizeof(T)); + _cimg_median_sort(Jbb, Jpb); _cimg_median_sort(Jnb, Jab); _cimg_median_sort(Jcb, Jab); _cimg_median_sort(Jcb, Jnb); + _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jbp, Jcp); _cimg_median_sort(Jbp, Jpp); _cimg_median_sort(Jap, Jbc); + _cimg_median_sort(Jnp, Jbc); _cimg_median_sort(Jnp, Jap); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jpc, Jnc); + _cimg_median_sort(Jpc, Jcc); _cimg_median_sort(Jbn, Jpn); _cimg_median_sort(Jac, Jpn); _cimg_median_sort(Jac, Jbn); + _cimg_median_sort(Jnn, Jan); _cimg_median_sort(Jcn, Jan); _cimg_median_sort(Jcn, Jnn); _cimg_median_sort(Jpa, Jca); + _cimg_median_sort(Jba, Jca); _cimg_median_sort(Jba, Jpa); _cimg_median_sort(Jna, Jaa); _cimg_median_sort(Jcb, Jbp); + _cimg_median_sort(Jnb, Jpp); _cimg_median_sort(Jbb, Jpp); _cimg_median_sort(Jbb, Jnb); _cimg_median_sort(Jab, Jcp); + _cimg_median_sort(Jpb, Jcp); _cimg_median_sort(Jpb, Jab); _cimg_median_sort(Jpc, Jac); _cimg_median_sort(Jnp, Jac); + _cimg_median_sort(Jnp, Jpc); _cimg_median_sort(Jcc, Jbn); _cimg_median_sort(Jap, Jbn); _cimg_median_sort(Jap, Jcc); + _cimg_median_sort(Jnc, Jpn); _cimg_median_sort(Jbc, Jpn); _cimg_median_sort(Jbc, Jnc); _cimg_median_sort(Jba, Jna); + _cimg_median_sort(Jcn, Jna); _cimg_median_sort(Jcn, Jba); _cimg_median_sort(Jpa, Jaa); _cimg_median_sort(Jnn, Jaa); + _cimg_median_sort(Jnn, Jpa); _cimg_median_sort(Jan, Jca); _cimg_median_sort(Jnp, Jcn); _cimg_median_sort(Jap, Jnn); + _cimg_median_sort(Jbb, Jnn); _cimg_median_sort(Jbb, Jap); _cimg_median_sort(Jbc, Jan); _cimg_median_sort(Jpb, Jan); + _cimg_median_sort(Jpb, Jbc); _cimg_median_sort(Jpc, Jba); _cimg_median_sort(Jcb, Jba); _cimg_median_sort(Jcb, Jpc); + _cimg_median_sort(Jcc, Jpa); _cimg_median_sort(Jnb, Jpa); _cimg_median_sort(Jnb, Jcc); _cimg_median_sort(Jnc, Jca); + _cimg_median_sort(Jab, Jca); _cimg_median_sort(Jab, Jnc); _cimg_median_sort(Jac, Jna); _cimg_median_sort(Jbp, Jna); + _cimg_median_sort(Jbp, Jac); _cimg_median_sort(Jbn, Jaa); _cimg_median_sort(Jpp, Jaa); _cimg_median_sort(Jpp, Jbn); + _cimg_median_sort(Jcp, Jpn); _cimg_median_sort(Jcp, Jan); _cimg_median_sort(Jnc, Jpa); _cimg_median_sort(Jbn, Jna); + _cimg_median_sort(Jcp, Jnc); _cimg_median_sort(Jcp, Jbn); _cimg_median_sort(Jpb, Jap); _cimg_median_sort(Jnb, Jpc); + _cimg_median_sort(Jbp, Jcn); _cimg_median_sort(Jpc, Jcn); _cimg_median_sort(Jap, Jcn); _cimg_median_sort(Jab, Jbc); + _cimg_median_sort(Jpp, Jcc); _cimg_median_sort(Jcp, Jac); _cimg_median_sort(Jab, Jpp); _cimg_median_sort(Jab, Jcp); + _cimg_median_sort(Jcc, Jac); _cimg_median_sort(Jbc, Jac); _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jbc, Jcc); + _cimg_median_sort(Jpp, Jbc); _cimg_median_sort(Jpp, Jcn); _cimg_median_sort(Jcc, Jcn); _cimg_median_sort(Jcp, Jcn); + _cimg_median_sort(Jcp, Jbc); _cimg_median_sort(Jcc, Jnn); _cimg_median_sort(Jcp, Jcc); _cimg_median_sort(Jbc, Jnn); + _cimg_median_sort(Jcc, Jba); _cimg_median_sort(Jbc, Jba); _cimg_median_sort(Jbc, Jcc); + res(x,y,0,k) = Jcc; + } + } break; + default : { + CImg vois; + cimg_forXYV(*this,x,y,k) { + const int + x0 = x - hl, y0 = y - hl, x1 = x + hr, y1 = y + hr, + nx0 = x0<0?0:x0, ny0 = y0<0?0:y0, + nx1 = x1>=dimx()?dimx()-1:x1, ny1 = y1>=dimy()?dimy()-1:y1; + vois = get_crop(nx0,ny0,0,k,nx1,ny1,0,k); + res(x,y,0,k) = vois.median(); + } + } + } else switch (n) { // 1D median filter + case 2 : { + T I[4] = { 0 }; + cimg_forV(*this,k) cimg_for2x2(*this,x,y,0,k,I) res(x,0,0,k) = (T)(0.5f*(I[0]+I[1])); + } break; + case 3 : { + T I[9] = { 0 }; + cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) { + res(x,0,0,k) = I[3] vois; + cimg_forXV(*this,x,k) { + const int + x0 = x - hl, x1 = x + hr, + nx0 = x0<0?0:x0, nx1 = x1>=dimx()?dimx()-1:x1; + vois = get_crop(nx0,0,0,k,nx1,0,0,k); + res(x,0,0,k) = vois.median(); + } + } + } + } + return res; + } + + //! Sharpen image using anisotropic shock filters or inverse diffusion. + CImg& sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, const float alpha=0, const float sigma=0) { + if (is_empty()) return *this; + T valm, valM = maxmin(valm); + const bool threed = (depth>1); + const float nedge = 0.5f*edge; + CImg val, vec, veloc(width,height,depth,dim); + + if (threed) { + CImg_3x3x3(I,T); + if (sharpen_type) { // 3D Shock filter. + CImg G = (alpha>0?get_blur(alpha).get_structure_tensor():get_structure_tensor()); + if (sigma>0) G.blur(sigma); + + cimg_forXYZ(G,x,y,z) { + G.get_tensor_at(x,y,z).symmetric_eigen(val,vec); + G(x,y,z,0) = vec(0,0); + G(x,y,z,1) = vec(0,1); + G(x,y,z,2) = vec(0,2); + G(x,y,z,3) = 1 - (Tfloat)cimg_std::pow(1+val[0]+val[1]+val[2],-(Tfloat)nedge); + } + cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) { + const Tfloat + u = G(x,y,z,0), + v = G(x,y,z,1), + w = G(x,y,z,2), + amp = G(x,y,z,3), + ixx = (Tfloat)Incc + Ipcc - 2*Iccc, + ixy = 0.25f*((Tfloat)Innc + Ippc - Inpc - Ipnc), + ixz = 0.25f*((Tfloat)Incn + Ipcp - Incp - Ipcn), + iyy = (Tfloat)Icnc + Icpc - 2*Iccc, + iyz = 0.25f*((Tfloat)Icnn + Icpp - Icnp - Icpn), + izz = (Tfloat)Iccn + Iccp - 2*Iccc, + ixf = (Tfloat)Incc - Iccc, + ixb = (Tfloat)Iccc - Ipcc, + iyf = (Tfloat)Icnc - Iccc, + iyb = (Tfloat)Iccc - Icpc, + izf = (Tfloat)Iccn - Iccc, + izb = (Tfloat)Iccc - Iccp, + itt = u*u*ixx + v*v*iyy + w*w*izz + 2*u*v*ixy + 2*u*w*ixz + 2*v*w*iyz, + it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb) + w*cimg::minmod(izf,izb); + veloc(x,y,z,k) = -amp*cimg::sign(itt)*cimg::abs(it); + } + } else cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) veloc(x,y,z,k) = -(Tfloat)Ipcc-Incc-Icpc-Icnc-Iccp-Iccn+6*Iccc; // 3D Inverse diffusion. + } else { + CImg_3x3(I,T); + if (sharpen_type) { // 2D Shock filter. + CImg G = (alpha>0?get_blur(alpha).get_structure_tensor():get_structure_tensor()); + if (sigma>0) G.blur(sigma); + cimg_forXY(G,x,y) { + G.get_tensor_at(x,y).symmetric_eigen(val,vec); + G(x,y,0) = vec(0,0); + G(x,y,1) = vec(0,1); + G(x,y,2) = 1 - (Tfloat)cimg_std::pow(1+val[0]+val[1],-(Tfloat)nedge); + } + cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) { + const Tfloat + u = G(x,y,0), + v = G(x,y,1), + amp = G(x,y,2), + ixx = (Tfloat)Inc + Ipc - 2*Icc, + ixy = 0.25f*((Tfloat)Inn + Ipp - Inp - Ipn), + iyy = (Tfloat)Icn + Icp - 2*Icc, + ixf = (Tfloat)Inc - Icc, + ixb = (Tfloat)Icc - Ipc, + iyf = (Tfloat)Icn - Icc, + iyb = (Tfloat)Icc - Icp, + itt = u*u*ixx + v*v*iyy + 2*u*v*ixy, + it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb); + veloc(x,y,k) = -amp*cimg::sign(itt)*cimg::abs(it); + } + } else cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) veloc(x,y,k) = -(Tfloat)Ipc-Inc-Icp-Icn+4*Icc; // 3D Inverse diffusion. + } + float m, M = (float)veloc.maxmin(m); + const float vmax = (float)cimg::max(cimg::abs(m),cimg::abs(M)); + if (vmax!=0) { veloc*=amplitude/vmax; (*this)+=veloc; } + return cut(valm,valM); + } + + CImg get_sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, const float alpha=0, const float sigma=0) const { + return (+*this).sharpen(amplitude,sharpen_type,edge,alpha,sigma); + } + + //! Compute the Haar multiscale wavelet transform (monodimensional version). + /** + \param axis Axis considered for the transform. + \param invert Set inverse of direct transform. + \param nb_scales Number of scales used for the transform. + **/ + CImg& haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) { + return get_haar(axis,invert,nb_scales).transfer_to(*this); + } + + CImg get_haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) const { + if (is_empty() || !nb_scales) return *this; + CImg res; + + if (nb_scales==1) { + switch (cimg::uncase(axis)) { // Single scale transform + case 'x' : { + const unsigned int w = width/2; + if (w) { + if (w%2) + throw CImgInstanceException("CImg<%s>::haar() : Sub-image width = %u is not even at a particular scale (=%u).", + pixel_type(),w); + res.assign(width,height,depth,dim); + if (invert) cimg_forYZV(*this,y,z,v) { // Inverse transform along X + for (unsigned int x=0, xw=w, x2=0; x::haar() : Sub-image height = %u is not even at a particular scale.", + pixel_type(),h); + res.assign(width,height,depth,dim); + if (invert) cimg_forXZV(*this,x,z,v) { // Inverse transform along Y + for (unsigned int y=0, yh=h, y2=0; y::haar() : Sub-image depth = %u is not even at a particular scale.", + pixel_type(),d); + res.assign(width,height,depth,dim); + if (invert) cimg_forXYV(*this,x,y,v) { // Inverse transform along Z + for (unsigned int z=0, zd=d, z2=0; z::haar() : Invalid axis '%c', must be 'x','y' or 'z'.", + pixel_type(),axis); + } + } else { // Multi-scale version + if (invert) { + res.assign(*this); + switch (cimg::uncase(axis)) { + case 'x' : { + unsigned int w = width; + for (unsigned int s=1; w && s::haar() : Invalid axis '%c', must be 'x','y' or 'z'.", + pixel_type(),axis); + } + } else { // Direct transform + res = get_haar(axis,false,1); + switch (cimg::uncase(axis)) { + case 'x' : { + for (unsigned int s=1, w=width/2; w && s::haar() : Invalid axis '%c', must be 'x','y' or 'z'.", + pixel_type(),axis); + } + } + } + return res; + } + + //! Compute the Haar multiscale wavelet transform. + /** + \param invert Set inverse of direct transform. + \param nb_scales Number of scales used for the transform. + **/ + CImg& haar(const bool invert=false, const unsigned int nb_scales=1) { + return get_haar(invert,nb_scales).transfer_to(*this); + } + + CImg get_haar(const bool invert=false, const unsigned int nb_scales=1) const { + CImg res; + + if (nb_scales==1) { // Single scale transform + if (width>1) get_haar('x',invert,1).transfer_to(res); + if (height>1) { if (res) res.get_haar('y',invert,1).transfer_to(res); else get_haar('y',invert,1).transfer_to(res); } + if (depth>1) { if (res) res.get_haar('z',invert,1).transfer_to(res); else get_haar('z',invert,1).transfer_to(res); } + if (res) return res; + } else { // Multi-scale transform + if (invert) { // Inverse transform + res.assign(*this); + if (width>1) { + if (height>1) { + if (depth>1) { + unsigned int w = width, h = height, d = depth; for (unsigned int s=1; w && h && d && s1) { + unsigned int w = width, d = depth; for (unsigned int s=1; w && d && s1) { + if (depth>1) { + unsigned int h = height, d = depth; for (unsigned int s=1; h && d && s1) { + unsigned int d = depth; for (unsigned int s=1; d && s1) { + if (height>1) { + if (depth>1) for (unsigned int s=1, w=width/2, h=height/2, d=depth/2; w && h && d && s1) for (unsigned int s=1, w=width/2, d=depth/2; w && d && s1) { + if (depth>1) for (unsigned int s=1, h=height/2, d=depth/2; h && d && s1) for (unsigned int s=1, d=depth/2; d && s& displacement_field(const CImg& target, const float smooth=0.1f, const float precision=0.1f, + const unsigned int nb_scales=0, const unsigned int itermax=10000) { + return get_displacement_field(target,smooth,precision,nb_scales,itermax).transfer_to(*this); + } + + CImg get_displacement_field(const CImg& target, + const float smoothness=0.1f, const float precision=0.1f, + const unsigned int nb_scales=0, const unsigned int itermax=10000) const { + if (is_empty() || !target) return *this; + if (!is_sameXYZV(target)) + throw CImgArgumentException("CImg<%s>::displacement_field() : Instance image (%u,%u,%u,%u,%p) and target image (%u,%u,%u,%u,%p) " + "have different size.", + pixel_type(),width,height,depth,dim,data, + target.width,target.height,target.depth,target.dim,target.data); + if (smoothness<0) + throw CImgArgumentException("CImg<%s>::displacement_field() : Smoothness parameter %g is negative.", + pixel_type(),smoothness); + if (precision<0) + throw CImgArgumentException("CImg<%s>::displacement_field() : Precision parameter %g is negative.", + pixel_type(),precision); + + const unsigned int nscales = nb_scales>0?nb_scales:(unsigned int)(2*cimg_std::log((double)(cimg::max(width,height,depth)))); + Tfloat m1, M1 = (Tfloat)maxmin(m1), m2, M2 = (Tfloat)target.maxmin(m2); + const Tfloat factor = cimg::max(cimg::abs(m1),cimg::abs(M1),cimg::abs(m2),cimg::abs(M2)); + CImg U0; + const bool threed = (depth>1); + + // Begin multi-scale motion estimation + for (int scale = (int)nscales-1; scale>=0; --scale) { + const float sfactor = (float)cimg_std::pow(1.5f,(float)scale), sprecision = (float)(precision/cimg_std::pow(2.25,1+scale)); + const int + sw = (int)(width/sfactor), sh = (int)(height/sfactor), sd = (int)(depth/sfactor), + swidth = sw?sw:1, sheight = sh?sh:1, sdepth = sd?sd:1; + CImg + I1 = get_resize(swidth,sheight,sdepth,-100,2), + I2 = target.get_resize(swidth,sheight,sdepth,-100,2); + I1/=factor; I2/=factor; + CImg U; + if (U0) U = (U0*=1.5f).get_resize(I1.dimx(),I1.dimy(),I1.dimz(),-100,3); + else U.assign(I1.dimx(),I1.dimy(),I1.dimz(),threed?3:2,0); + + // Begin single-scale motion estimation + CImg veloc(U); + float dt = 2, Energy = cimg::type::max(); + const CImgList dI = I2.get_gradient(); + for (unsigned int iter=0; iter vector(const T& a0) { + static CImg r(1,1); r[0] = a0; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1) { + static CImg r(1,2); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2) { + static CImg r(1,3); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3) { + static CImg r(1,4); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) { + static CImg r(1,5); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) { + static CImg r(1,6); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6) { + static CImg r(1,7); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7) { + static CImg r(1,8); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8) { + static CImg r(1,9); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8, const T& a9) { + static CImg r(1,10); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; *(ptr++) = a9; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8, const T& a9, const T& a10) { + static CImg r(1,11); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8, const T& a9, const T& a10, const T& a11) { + static CImg r(1,12); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8, const T& a9, const T& a10, const T& a11, + const T& a12) { + static CImg r(1,13); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; + *(ptr++) = a12; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8, const T& a9, const T& a10, const T& a11, + const T& a12, const T& a13) { + static CImg r(1,14); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; + *(ptr++) = a12; *(ptr++) = a13; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8, const T& a9, const T& a10, const T& a11, + const T& a12, const T& a13, const T& a14) { + static CImg r(1,15); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; + *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; + return r; + } + + //! Return a vector with specified coefficients. + static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8, const T& a9, const T& a10, const T& a11, + const T& a12, const T& a13, const T& a14, const T& a15) { + static CImg r(1,16); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; + *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15; + return r; + } + + //! Return a 1x1 square matrix with specified coefficients. + static CImg matrix(const T& a0) { + return vector(a0); + } + + //! Return a 2x2 square matrix with specified coefficients. + static CImg matrix(const T& a0, const T& a1, + const T& a2, const T& a3) { + static CImg r(2,2); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; + *(ptr++) = a2; *(ptr++) = a3; + return r; + } + + //! Return a 3x3 square matrix with specified coefficients. + static CImg matrix(const T& a0, const T& a1, const T& a2, + const T& a3, const T& a4, const T& a5, + const T& a6, const T& a7, const T& a8) { + static CImg r(3,3); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; + *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; + *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; + return r; + } + + //! Return a 4x4 square matrix with specified coefficients. + static CImg matrix(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8, const T& a9, const T& a10, const T& a11, + const T& a12, const T& a13, const T& a14, const T& a15) { + static CImg r(4,4); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11; + *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15; + return r; + } + + //! Return a 5x5 square matrix with specified coefficients. + static CImg matrix(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, + const T& a5, const T& a6, const T& a7, const T& a8, const T& a9, + const T& a10, const T& a11, const T& a12, const T& a13, const T& a14, + const T& a15, const T& a16, const T& a17, const T& a18, const T& a19, + const T& a20, const T& a21, const T& a22, const T& a23, const T& a24) { + static CImg r(5,5); T *ptr = r.data; + *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; + *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; *(ptr++) = a9; + *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; + *(ptr++) = a15; *(ptr++) = a16; *(ptr++) = a17; *(ptr++) = a18; *(ptr++) = a19; + *(ptr++) = a20; *(ptr++) = a21; *(ptr++) = a22; *(ptr++) = a23; *(ptr++) = a24; + return r; + } + + //! Return a 1x1 symmetric matrix with specified coefficients. + static CImg tensor(const T& a1) { + return matrix(a1); + } + + //! Return a 2x2 symmetric matrix tensor with specified coefficients. + static CImg tensor(const T& a1, const T& a2, const T& a3) { + return matrix(a1,a2,a2,a3); + } + + //! Return a 3x3 symmetric matrix with specified coefficients. + static CImg tensor(const T& a1, const T& a2, const T& a3, const T& a4, const T& a5, const T& a6) { + return matrix(a1,a2,a3,a2,a4,a5,a3,a5,a6); + } + + //! Return a 1x1 diagonal matrix with specified coefficients. + static CImg diagonal(const T& a0) { + return matrix(a0); + } + + //! Return a 2x2 diagonal matrix with specified coefficients. + static CImg diagonal(const T& a0, const T& a1) { + return matrix(a0,0,0,a1); + } + + //! Return a 3x3 diagonal matrix with specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2) { + return matrix(a0,0,0,0,a1,0,0,0,a2); + } + + //! Return a 4x4 diagonal matrix with specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2, const T& a3) { + return matrix(a0,0,0,0,0,a1,0,0,0,0,a2,0,0,0,0,a3); + } + + //! Return a 5x5 diagonal matrix with specified coefficients. + static CImg diagonal(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) { + return matrix(a0,0,0,0,0,0,a1,0,0,0,0,0,a2,0,0,0,0,0,a3,0,0,0,0,0,a4); + } + + //! Return a NxN identity matrix. + static CImg identity_matrix(const unsigned int N) { + CImg res(N,N,1,1,0); + cimg_forX(res,x) res(x,x) = 1; + return res; + } + + //! Return a N-numbered sequence vector from \p a0 to \p a1. + static CImg sequence(const unsigned int N, const T a0, const T a1) { + if (N) return CImg(1,N).sequence(a0,a1); + return CImg(); + } + + //! Return a 3x3 rotation matrix along the (x,y,z)-axis with an angle w. + static CImg rotation_matrix(const float x, const float y, const float z, const float w, const bool quaternion_data=false) { + float X,Y,Z,W; + if (!quaternion_data) { + const float norm = (float)cimg_std::sqrt(x*x + y*y + z*z), + nx = norm>0?x/norm:0, + ny = norm>0?y/norm:0, + nz = norm>0?z/norm:1, + nw = norm>0?w:0, + sina = (float)cimg_std::sin(nw/2), + cosa = (float)cimg_std::cos(nw/2); + X = nx*sina; + Y = ny*sina; + Z = nz*sina; + W = cosa; + } else { + const float norm = (float)cimg_std::sqrt(x*x + y*y + z*z + w*w); + if (norm>0) { X = x/norm; Y = y/norm; Z = z/norm; W = w/norm; } + else { X = Y = Z = 0; W = 1; } + } + const float xx = X*X, xy = X*Y, xz = X*Z, xw = X*W, yy = Y*Y, yz = Y*Z, yw = Y*W, zz = Z*Z, zw = Z*W; + return CImg::matrix((T)(1-2*(yy+zz)), (T)(2*(xy+zw)), (T)(2*(xz-yw)), + (T)(2*(xy-zw)), (T)(1-2*(xx+zz)), (T)(2*(yz+xw)), + (T)(2*(xz+yw)), (T)(2*(yz-xw)), (T)(1-2*(xx+yy))); + } + + //! Return a new image corresponding to the vector located at (\p x,\p y,\p z) of the current vector-valued image. + CImg get_vector_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const { + static CImg dest; + if (dest.height!=dim) dest.assign(1,dim); + const unsigned int whz = width*height*depth; + const T *ptrs = ptr(x,y,z); + T *ptrd = dest.data; + cimg_forV(*this,k) { *(ptrd++) = *ptrs; ptrs+=whz; } + return dest; + } + + //! Set the image \p vec as the \a vector \a valued pixel located at (\p x,\p y,\p z) of the current vector-valued image. + template + CImg& set_vector_at(const CImg& vec, const unsigned int x, const unsigned int y=0, const unsigned int z=0) { + if (x get_matrix_at(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const { + const int n = (int)cimg_std::sqrt((double)dim); + CImg dest(n,n); + cimg_forV(*this,k) dest[k]=(*this)(x,y,z,k); + return dest; + } + + //! Set the image \p vec as the \a square \a matrix-valued pixel located at (\p x,\p y,\p z) of the current vector-valued image. + template + CImg& set_matrix_at(const CImg& mat, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) { + return set_vector_at(mat,x,y,z); + } + + //! Return a new image corresponding to the \a diffusion \a tensor located at (\p x,\p y,\p z) of the current vector-valued image. + CImg get_tensor_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const { + if (dim==6) return tensor((*this)(x,y,z,0),(*this)(x,y,z,1),(*this)(x,y,z,2), + (*this)(x,y,z,3),(*this)(x,y,z,4),(*this)(x,y,z,5)); + if (dim==3) return tensor((*this)(x,y,z,0),(*this)(x,y,z,1),(*this)(x,y,z,2)); + return tensor((*this)(x,y,z,0)); + } + + //! Set the image \p vec as the \a tensor \a valued pixel located at (\p x,\p y,\p z) of the current vector-valued image. + template + CImg& set_tensor_at(const CImg& ten, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) { + if (ten.height==2) { + (*this)(x,y,z,0) = (T)ten[0]; + (*this)(x,y,z,1) = (T)ten[1]; + (*this)(x,y,z,2) = (T)ten[3]; + } + else { + (*this)(x,y,z,0) = (T)ten[0]; + (*this)(x,y,z,1) = (T)ten[1]; + (*this)(x,y,z,2) = (T)ten[2]; + (*this)(x,y,z,3) = (T)ten[4]; + (*this)(x,y,z,4) = (T)ten[5]; + (*this)(x,y,z,5) = (T)ten[8]; + } + return *this; + } + + //! Unroll all images values into a one-column vector. + CImg& vector() { + return unroll('y'); + } + + CImg get_vector() const { + return get_unroll('y'); + } + + //! Realign pixel values of the instance image as a square matrix + CImg& matrix() { + const unsigned int siz = size(); + switch (siz) { + case 1 : break; + case 4 : width = height = 2; break; + case 9 : width = height = 3; break; + case 16 : width = height = 4; break; + case 25 : width = height = 5; break; + case 36 : width = height = 6; break; + case 49 : width = height = 7; break; + case 64 : width = height = 8; break; + case 81 : width = height = 9; break; + case 100 : width = height = 10; break; + default : { + unsigned int i = 11, i2 = i*i; + while (i2::matrix() : Image size = %u is not a square number", + pixel_type(),siz); + } + } + return *this; + } + + CImg get_matrix() const { + return (+*this).matrix(); + } + + //! Realign pixel values of the instance image as a symmetric tensor. + CImg& tensor() { + return get_tensor().transfer_to(*this); + } + + CImg get_tensor() const { + CImg res; + const unsigned int siz = size(); + switch (siz) { + case 1 : break; + case 3 : + res.assign(2,2); + res(0,0) = (*this)(0); + res(1,0) = res(0,1) = (*this)(1); + res(1,1) = (*this)(2); + break; + case 6 : + res.assign(3,3); + res(0,0) = (*this)(0); + res(1,0) = res(0,1) = (*this)(1); + res(2,0) = res(0,2) = (*this)(2); + res(1,1) = (*this)(3); + res(2,1) = res(1,2) = (*this)(4); + res(2,2) = (*this)(5); + break; + default : + throw CImgInstanceException("CImg<%s>::tensor() : Wrong vector dimension = %u in instance image.", + pixel_type(), dim); + } + return res; + } + + //! Unroll all images values into specified axis. + CImg& unroll(const char axis) { + const unsigned int siz = size(); + if (siz) switch (axis) { + case 'x' : width = siz; height=depth=dim=1; break; + case 'y' : height = siz; width=depth=dim=1; break; + case 'z' : depth = siz; width=height=dim=1; break; + case 'v' : dim = siz; width=height=depth=1; break; + default : + throw CImgArgumentException("CImg<%s>::unroll() : Given axis is '%c' which is not 'x','y','z' or 'v'", + pixel_type(),axis); + } + return *this; + } + + CImg get_unroll(const char axis) const { + return (+*this).unroll(axis); + } + + //! Get a diagonal matrix, whose diagonal coefficients are the coefficients of the input image. + CImg& diagonal() { + return get_diagonal().transfer_to(*this); + } + + CImg get_diagonal() const { + if (is_empty()) return *this; + CImg res(size(),size(),1,1,0); + cimg_foroff(*this,off) res(off,off) = (*this)(off); + return res; + } + + //! Get an identity matrix having same dimension than instance image. + CImg& identity_matrix() { + return identity_matrix(cimg::max(width,height)).transfer_to(*this); + } + + CImg get_identity_matrix() const { + return identity_matrix(cimg::max(width,height)); + } + + //! Return a N-numbered sequence vector from \p a0 to \p a1. + CImg& sequence(const T a0, const T a1) { + if (is_empty()) return *this; + const unsigned int siz = size() - 1; + T* ptr = data; + if (siz) { + const Tfloat delta = (Tfloat)a1 - a0; + cimg_foroff(*this,l) *(ptr++) = (T)(a0 + delta*l/siz); + } else *ptr = a0; + return *this; + } + + CImg get_sequence(const T a0, const T a1) const { + return (+*this).sequence(a0,a1); + } + + //! Transpose the current matrix. + CImg& transpose() { + if (width==1) { width=height; height=1; return *this; } + if (height==1) { height=width; width=1; return *this; } + if (width==height) { + cimg_forYZV(*this,y,z,v) for (int x=y; x get_transpose() const { + return get_permute_axes("yxzv"); + } + + //! Invert the current matrix. + CImg& invert(const bool use_LU=true) { + if (!is_empty()) { + if (width!=height || depth!=1 || dim!=1) + throw CImgInstanceException("CImg<%s>::invert() : Instance matrix (%u,%u,%u,%u,%p) is not square.", + pixel_type(),width,height,depth,dim,data); +#ifdef cimg_use_lapack + int INFO = (int)use_LU, N = width, LWORK = 4*N, *IPIV = new int[N]; + Tfloat + *lapA = new Tfloat[N*N], + *WORK = new Tfloat[LWORK]; + cimg_forXY(*this,k,l) lapA[k*N+l] = (Tfloat)((*this)(k,l)); + cimg::getrf(N,lapA,IPIV,INFO); + if (INFO) + cimg::warn("CImg<%s>::invert() : LAPACK library function dgetrf_() returned error code %d.", + pixel_type(),INFO); + else { + cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO); + if (INFO) + cimg::warn("CImg<%s>::invert() : LAPACK library function dgetri_() returned Error code %d", + pixel_type(),INFO); + } + if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)(lapA[k*N+l]); else fill(0); + delete[] IPIV; delete[] lapA; delete[] WORK; +#else + const double dete = width>3?-1.0:det(); + if (dete!=0.0 && width==2) { + const double + a = data[0], c = data[1], + b = data[2], d = data[3]; + data[0] = (T)(d/dete); data[1] = (T)(-c/dete); + data[2] = (T)(-b/dete); data[3] = (T)(a/dete); + } else if (dete!=0.0 && width==3) { + const double + a = data[0], d = data[1], g = data[2], + b = data[3], e = data[4], h = data[5], + c = data[6], f = data[7], i = data[8]; + data[0] = (T)((i*e-f*h)/dete), data[1] = (T)((g*f-i*d)/dete), data[2] = (T)((d*h-g*e)/dete); + data[3] = (T)((h*c-i*b)/dete), data[4] = (T)((i*a-c*g)/dete), data[5] = (T)((g*b-a*h)/dete); + data[6] = (T)((b*f-e*c)/dete), data[7] = (T)((d*c-a*f)/dete), data[8] = (T)((a*e-d*b)/dete); + } else { + if (use_LU) { // LU-based inverse computation + CImg A(*this), indx, col(1,width); + bool d; + A._LU(indx,d); + cimg_forX(*this,j) { + col.fill(0); + col(j) = 1; + col._solve(A,indx); + cimg_forX(*this,i) (*this)(j,i) = (T)col(i); + } + } else { // SVD-based inverse computation + CImg U(width,width), S(1,width), V(width,width); + SVD(U,S,V,false); + U.transpose(); + cimg_forY(S,k) if (S[k]!=0) S[k]=1/S[k]; + S.diagonal(); + *this = V*S*U; + } + } +#endif + } + return *this; + } + + CImg get_invert(const bool use_LU=true) const { + return CImg(*this,false).invert(use_LU); + } + + //! Compute the pseudo-inverse (Moore-Penrose) of the matrix. + CImg& pseudoinvert() { + return get_pseudoinvert().transfer_to(*this); + } + + CImg get_pseudoinvert() const { + CImg U, S, V; + SVD(U,S,V); + cimg_forX(V,x) { + const Tfloat s = S(x), invs = s!=0?1/s:(Tfloat)0; + cimg_forY(V,y) V(x,y)*=invs; + } + return V*U.transpose(); + } + + //! Compute the cross product between two 3d vectors. + template + CImg& cross(const CImg& img) { + if (width!=1 || height<3 || img.width!=1 || img.height<3) + throw CImgInstanceException("CImg<%s>::cross() : Arguments (%u,%u,%u,%u,%p) and (%u,%u,%u,%u,%p) must be both 3d vectors.", + pixel_type(),width,height,depth,dim,data,img.width,img.height,img.depth,img.dim,img.data); + const T x = (*this)[0], y = (*this)[1], z = (*this)[2]; + (*this)[0] = (T)(y*img[2]-z*img[1]); + (*this)[1] = (T)(z*img[0]-x*img[2]); + (*this)[2] = (T)(x*img[1]-y*img[0]); + return *this; + } + + template + CImg::type> get_cross(const CImg& img) const { + typedef typename cimg::superset::type Tt; + return CImg(*this).cross(img); + } + + //! Solve a linear system AX=B where B=*this. + template + CImg& solve(const CImg& A) { + if (width!=1 || depth!=1 || dim!=1 || height!=A.height || A.depth!=1 || A.dim!=1) + throw CImgArgumentException("CImg<%s>::solve() : Instance matrix size is (%u,%u,%u,%u) while " + "size of given matrix A is (%u,%u,%u,%u).", + pixel_type(),width,height,depth,dim,A.width,A.height,A.depth,A.dim); + + typedef typename cimg::superset2::type Ttfloat; + if (A.width==A.height) { +#ifdef cimg_use_lapack + char TRANS='N'; + int INFO, N = height, LWORK = 4*N, one = 1, *IPIV = new int[N]; + Ttfloat + *lapA = new Ttfloat[N*N], + *lapB = new Ttfloat[N], + *WORK = new Ttfloat[LWORK]; + cimg_forXY(A,k,l) lapA[k*N+l] = (Ttfloat)(A(k,l)); + cimg_forY(*this,i) lapB[i] = (Ttfloat)((*this)(i)); + cimg::getrf(N,lapA,IPIV,INFO); + if (INFO) + cimg::warn("CImg<%s>::solve() : LAPACK library function dgetrf_() returned error code %d.", + pixel_type(),INFO); + if (!INFO) { + cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO); + if (INFO) + cimg::warn("CImg<%s>::solve() : LAPACK library function dgetrs_() returned Error code %d", + pixel_type(),INFO); + } + if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0); + delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK; +#else + CImg lu(A); + CImg indx; + bool d; + lu._LU(indx,d); + _solve(lu,indx); +#endif + } else assign(A.get_pseudoinvert()*(*this)); + return *this; + } + + template + CImg::type> get_solve(const CImg& A) const { + typedef typename cimg::superset2::type Ttfloat; + return CImg(*this,false).solve(A); + } + + template + CImg& _solve(const CImg& A, const CImg& indx) { + typedef typename cimg::superset2::type Ttfloat; + const int N = size(); + int ii = -1; + Ttfloat sum; + for (int i=0; i=0) for (int j=ii; j<=i-1; ++j) sum-=A(j,i)*(*this)(j); + else if (sum!=0) ii=i; + (*this)(i) = (T)sum; + } + { for (int i=N-1; i>=0; --i) { + sum = (*this)(i); + for (int j=i+1; j + CImg& solve_tridiagonal(const CImg& a, const CImg& b, const CImg& c) { + const int siz = (int)size(); + if ((int)a.size()!=siz || (int)b.size()!=siz || (int)c.size()!=siz) + throw CImgArgumentException("CImg<%s>::solve_tridiagonal() : arrays of triagonal coefficients have different size.",pixel_type); + typedef typename cimg::superset2::type Ttfloat; + CImg nc(siz); + const T *ptra = a.data, *ptrb = b.data, *ptrc = c.data; + T *ptrnc = nc.data, *ptrd = data; + const Ttfloat valb0 = (Ttfloat)*(ptrb++); + *ptrnc = *(ptrc++)/valb0; + Ttfloat vald = (Ttfloat)(*(ptrd++)/=valb0); + for (int i = 1; i=0; --i) vald = (*(--ptrd)-=*(--ptrnc)*vald); + return *this; + } + + template + CImg::type> get_solve_tridiagonal(const CImg& a, const CImg& b, const CImg& c) const { + typedef typename cimg::superset2::type Ttfloat; + return CImg(*this,false).solve_tridiagonal(a,b,c); + } + + //! Sort values of a vector and get permutations. + template + CImg& sort(CImg& permutations, const bool increasing=true) { + if (is_empty()) permutations.assign(); + else { + if (permutations.size()!=size()) permutations.assign(size()); + cimg_foroff(permutations,off) permutations[off] = (t)off; + _quicksort(0,size()-1,permutations,increasing); + } + return *this; + } + + template + CImg get_sort(CImg& permutations, const bool increasing=true) const { + return (+*this).sort(permutations,increasing); + } + + // Sort image values. + CImg& sort(const bool increasing=true) { + CImg foo; + return sort(foo,increasing); + } + + CImg get_sort(const bool increasing=true) const { + return (+*this).sort(increasing); + } + + template + CImg& _quicksort(const int min, const int max, CImg& permutations, const bool increasing) { + if (min(*this)[mid]) { + cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); } + if ((*this)[mid]>(*this)[max]) { + cimg::swap((*this)[max],(*this)[mid]); cimg::swap(permutations[max],permutations[mid]); } + if ((*this)[min]>(*this)[mid]) { + cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); } + } else { + if ((*this)[min]<(*this)[mid]) { + cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); } + if ((*this)[mid]<(*this)[max]) { + cimg::swap((*this)[max],(*this)[mid]); cimg::swap(permutations[max],permutations[mid]); } + if ((*this)[min]<(*this)[mid]) { + cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); } + } + if (max-min>=3) { + const T pivot = (*this)[mid]; + int i = min, j = max; + if (increasing) { + do { + while ((*this)[i]pivot) --j; + if (i<=j) { + cimg::swap((*this)[i],(*this)[j]); + cimg::swap(permutations[i++],permutations[j--]); + } + } while (i<=j); + } else { + do { + while ((*this)[i]>pivot) ++i; + while ((*this)[j] + CImg& permute(const CImg& permutation) { + return get_permute(permutation).transfer_to(*this); + } + + template + CImg get_permute(const CImg& permutation) const { + if (permutation.size()!=size()) + throw CImgArgumentException("CImg<%s>::permute() : Instance image (%u,%u,%u,%u,%p) and permutation (%u,%u,%u,%u,%p)" + "have different sizes.", + pixel_type(),width,height,depth,dim,data, + permutation.width,permutation.height,permutation.depth,permutation.dim,permutation.data); + CImg res(width,height,depth,dim); + const t *p = permutation.ptr(permutation.size()); + cimg_for(res,ptr,T) *ptr = (*this)[*(--p)]; + return res; + } + + //! Compute the SVD of a general matrix. + template + const CImg& SVD(CImg& U, CImg& S, CImg& V, + const bool sorting=true, const unsigned int max_iter=40, const float lambda=0) const { + if (is_empty()) { U.assign(); S.assign(); V.assign(); } + else { + U = *this; + if (lambda!=0) { + const unsigned int delta = cimg::min(U.width,U.height); + for (unsigned int i=0; i rv1(width); + t anorm = 0, c, f, g = 0, h, s, scale = 0; + int l = 0, nm = 0; + + cimg_forX(U,i) { + l = i+1; rv1[i] = scale*g; g = s = scale = 0; + if (i=0?-1:1)*cimg_std::sqrt(s)); h=f*g-s; U(i,i) = f-g; + for (int j=l; j=0?-1:1)*cimg_std::sqrt(s)); h = f*g-s; U(l,i) = f-g; + { for (int k=l; k=0; --i) { + if (i=0; --i) { + l = i+1; g = S[i]; + for (int j=l; j=0; --k) { + for (unsigned int its=0; its=1; --l) { + nm = l-1; + if ((cimg::abs(rv1[l])+anorm)==anorm) { flag = false; break; } + if ((cimg::abs(S[nm])+anorm)==anorm) break; + } + if (flag) { + c = 0; s = 1; + for (int i=l; i<=k; ++i) { + f = s*rv1[i]; rv1[i] = c*rv1[i]; + if ((cimg::abs(f)+anorm)==anorm) break; + g = S[i]; h = (t)cimg::_pythagore(f,g); S[i] = h; h = 1/h; c = g*h; s = -f*h; + cimg_forY(U,j) { const t y = U(nm,j), z = U(i,j); U(nm,j) = y*c+z*s; U(i,j) = z*c-y*s; } + } + } + const t z = S[k]; + if (l==k) { if (z<0) { S[k] = -z; cimg_forX(U,j) V(k,j) = -V(k,j); } break; } + nm = k-1; + t x = S[l], y = S[nm]; + g = rv1[nm]; h = rv1[k]; + f = ((y-z)*(y+z)+(g-h)*(g+h))/(2*h*y); + g = (t)cimg::_pythagore(f,1.0); + f = ((x-z)*(x+z)+h*((y/(f+ (f>=0?g:-g)))-h))/x; + c = s = 1; + for (int j=l; j<=nm; ++j) { + const int i = j+1; + g = rv1[i]; h = s*g; g = c*g; + t y = S[i]; + t z = (t)cimg::_pythagore(f,h); + rv1[j] = z; c = f/z; s = h/z; + f = x*c+g*s; g = g*c-x*s; h = y*s; y*=c; + cimg_forX(U,jj) { const t x = V(j,jj), z = V(i,jj); V(j,jj) = x*c+z*s; V(i,jj) = z*c-x*s; } + z = (t)cimg::_pythagore(f,h); S[j] = z; + if (z) { z = 1/z; c = f*z; s = h*z; } + f = c*g+s*y; x = c*y-s*g; + { cimg_forY(U,jj) { const t y = U(j,jj); z = U(i,jj); U(j,jj) = y*c+z*s; U(i,jj) = z*c-y*s; }} + } + rv1[l] = 0; rv1[k]=f; S[k]=x; + } + } + + if (sorting) { + CImg permutations(width); + CImg tmp(width); + S.sort(permutations,false); + cimg_forY(U,k) { + cimg_forX(permutations,x) tmp(x) = U(permutations(x),k); + cimg_std::memcpy(U.ptr(0,k),tmp.data,sizeof(t)*width); + } + { cimg_forY(V,k) { + cimg_forX(permutations,x) tmp(x) = V(permutations(x),k); + cimg_std::memcpy(V.ptr(0,k),tmp.data,sizeof(t)*width); + }} + } + } + return *this; + } + + //! Compute the SVD of a general matrix. + template + const CImg& SVD(CImgList& USV) const { + if (USV.size<3) USV.assign(3); + return SVD(USV[0],USV[1],USV[2]); + } + + //! Compute the SVD of a general matrix. + CImgList get_SVD(const bool sorting=true) const { + CImgList res(3); + SVD(res[0],res[1],res[2],sorting); + return res; + } + + // INNER ROUTINE : Compute the LU decomposition of a permuted matrix (c.f. numerical recipies) + template + CImg& _LU(CImg& indx, bool& d) { + const int N = dimx(); + int imax = 0; + CImg vv(N); + indx.assign(N); + d = true; + cimg_forX(*this,i) { + Tfloat vmax = 0; + cimg_forX(*this,j) { + const Tfloat tmp = cimg::abs((*this)(j,i)); + if (tmp>vmax) vmax = tmp; + } + if (vmax==0) { indx.fill(0); return fill(0); } + vv[i] = 1/vmax; + } + cimg_forX(*this,j) { + for (int i=0; i=vmax) { vmax=tmp; imax=i; } + }} + if (j!=imax) { + cimg_forX(*this,k) cimg::swap((*this)(k,imax),(*this)(k,j)); + d =!d; + vv[imax] = vv[j]; + } + indx[j] = (t)imax; + if ((*this)(j,j)==0) (*this)(j,j) = (T)1e-20; + if (j + const CImg& eigen(CImg& val, CImg &vec) const { + if (is_empty()) { val.assign(); vec.assign(); } + else { + if (width!=height || depth>1 || dim>1) + throw CImgInstanceException("CImg<%s>::eigen() : Instance object (%u,%u,%u,%u,%p) is empty.", + pixel_type(),width,height,depth,dim,data); + if (val.size()::eigen() : Complex eigenvalues", + pixel_type()); + f = cimg_std::sqrt(f); + const double l1 = 0.5*(e-f), l2 = 0.5*(e+f); + const double theta1 = cimg_std::atan2(l2-a,b), theta2 = cimg_std::atan2(l1-a,b); + val[0]=(t)l2; + val[1]=(t)l1; + vec(0,0) = (t)cimg_std::cos(theta1); + vec(0,1) = (t)cimg_std::sin(theta1); + vec(1,0) = (t)cimg_std::cos(theta2); + vec(1,1) = (t)cimg_std::sin(theta2); + } break; + default : + throw CImgInstanceException("CImg<%s>::eigen() : Eigenvalues computation of general matrices is limited" + "to 2x2 matrices (given is %ux%u)", + pixel_type(),width,height); + } + } + return *this; + } + + //! Compute the eigenvalues and eigenvectors of a matrix. + CImgList get_eigen() const { + CImgList res(2); + eigen(res[0],res[1]); + return res; + } + + //! Compute the eigenvalues and eigenvectors of a symmetric matrix. + template + const CImg& symmetric_eigen(CImg& val, CImg& vec) const { + if (is_empty()) { val.assign(); vec.assign(); } + else { +#ifdef cimg_use_lapack + char JOB = 'V', UPLO = 'U'; + int N = width, LWORK = 4*N, INFO; + Tfloat + *lapA = new Tfloat[N*N], + *lapW = new Tfloat[N], + *WORK = new Tfloat[LWORK]; + cimg_forXY(*this,k,l) lapA[k*N+l] = (Tfloat)((*this)(k,l)); + cimg::syev(JOB,UPLO,N,lapA,lapW,WORK,LWORK,INFO); + if (INFO) + cimg::warn("CImg<%s>::symmetric_eigen() : LAPACK library function dsyev_() returned error code %d.", + pixel_type(),INFO); + val.assign(1,N); + vec.assign(N,N); + if (!INFO) { + cimg_forY(val,i) val(i) = (T)lapW[N-1-i]; + cimg_forXY(vec,k,l) vec(k,l) = (T)(lapA[(N-1-k)*N+l]); + } else { val.fill(0); vec.fill(0); } + delete[] lapA; delete[] lapW; delete[] WORK; +#else + if (width!=height || depth>1 || dim>1) + throw CImgInstanceException("CImg<%s>::eigen() : Instance object (%u,%u,%u,%u,%p) is empty.", + pixel_type(),width,height,depth,dim,data); + val.assign(1,width); + if (vec.data) vec.assign(width,width); + if (width<3) return eigen(val,vec); + CImg V(width,width); + SVD(vec,val,V,false); + bool ambiguous = false; + float eig = 0; + cimg_forY(val,p) { // check for ambiguous cases. + if (val[p]>eig) eig = (float)val[p]; + t scal = 0; + cimg_forY(vec,y) scal+=vec(p,y)*V(p,y); + if (cimg::abs(scal)<0.9f) ambiguous = true; + if (scal<0) val[p] = -val[p]; + } + if (ambiguous) { + (eig*=2)++; + SVD(vec,val,V,false,40,eig); + val-=eig; + } + CImg permutations(width); // sort eigenvalues in decreasing order + CImg tmp(width); + val.sort(permutations,false); + cimg_forY(vec,k) { + cimg_forX(permutations,x) tmp(x) = vec(permutations(x),k); + cimg_std::memcpy(vec.ptr(0,k),tmp.data,sizeof(t)*width); + } +#endif + } + return *this; + } + + //! Compute the eigenvalues and eigenvectors of a symmetric matrix. + CImgList get_symmetric_eigen() const { + CImgList res(2); + symmetric_eigen(res[0],res[1]); + return res; + } + + //@} + //------------------- + // + //! \name Display + //@{ + //------------------- + + //! Display an image into a CImgDisplay window. + const CImg& display(CImgDisplay& disp) const { + disp.display(*this); + return *this; + } + + //! Display an image in a window with a title \p title, and wait a 'is_closed' or 'keyboard' event.\n + const CImg& display(CImgDisplay &disp, const bool display_info) const { + return _display(disp,0,display_info); + } + + //! Display an image in a window with a title \p title, and wait a 'is_closed' or 'keyboard' event.\n + const CImg& display(const char *const title=0, const bool display_info=true) const { + CImgDisplay disp; + return _display(disp,title,display_info); + } + + const CImg& _display(CImgDisplay &disp, const char *const title, const bool display_info) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::display() : Instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),width,height,depth,dim,data); + unsigned int oldw = 0, oldh = 0, XYZ[3], key = 0, mkey = 0; + int x0 = 0, y0 = 0, z0 = 0, x1 = dimx()-1, y1 = dimy()-1, z1 = dimz()-1; + float frametiming = 5; + + char ntitle[256] = { 0 }; + if (!disp) { + if (!title) cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); + disp.assign(cimg_fitscreen(width,height,depth),title?title:ntitle,1); + } + cimg_std::strncpy(ntitle,disp.title,255); + if (display_info) print(ntitle); + + CImg zoom; + for (bool reset_view = true, resize_disp = false; !key && !disp.is_closed; ) { + if (reset_view) { + XYZ[0] = (x0 + x1)/2; XYZ[1] = (y0 + y1)/2; XYZ[2] = (z0 + z1)/2; + x0 = 0; y0 = 0; z0 = 0; x1 = width-1; y1 = height-1; z1 = depth-1; + oldw = disp.width; oldh = disp.height; + reset_view = false; + } + if (!x0 && !y0 && !z0 && x1==dimx()-1 && y1==dimy()-1 && z1==dimz()-1) zoom.assign(); + else zoom = get_crop(x0,y0,z0,x1,y1,z1); + + const unsigned int + dx = 1 + x1 - x0, dy = 1 + y1 - y0, dz = 1 + z1 - z0, + tw = dx + (dz>1?dz:0), th = dy + (dz>1?dz:0); + if (resize_disp) { + const unsigned int + ttw = tw*disp.width/oldw, tth = th*disp.height/oldh, + dM = cimg::max(ttw,tth), diM = cimg::max(disp.width,disp.height), + imgw = cimg::max(16U,ttw*diM/dM), imgh = cimg::max(16U,tth*diM/dM); + disp.normalscreen().resize(cimg_fitscreen(imgw,imgh,1),false); + resize_disp = false; + } + oldw = tw; oldh = th; + + bool + go_up = false, go_down = false, go_left = false, go_right = false, + go_inc = false, go_dec = false, go_in = false, go_out = false, + go_in_center = false; + const CImg& visu = zoom?zoom:*this; + const CImg selection = visu._get_select(disp,0,2,XYZ,0,x0,y0,z0); + if (disp.wheel) { + if (disp.is_keyCTRLLEFT) { if (!mkey || mkey==1) go_out = !(go_in = disp.wheel>0); go_in_center = false; mkey = 1; } + else if (disp.is_keySHIFTLEFT) { if (!mkey || mkey==2) go_right = !(go_left = disp.wheel>0); mkey = 2; } + else if (disp.is_keyALT || depth==1) { if (!mkey || mkey==3) go_down = !(go_up = disp.wheel>0); mkey = 3; } + else mkey = 0; + disp.wheel = 0; + } else mkey = 0; + const int + sx0 = selection(0), sy0 = selection(1), sz0 = selection(2), + sx1 = selection(3), sy1 = selection(4), sz1 = selection(5); + if (sx0>=0 && sy0>=0 && sz0>=0 && sx1>=0 && sy1>=0 && sz1>=0) { + x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1; x0+=sx0; y0+=sy0; z0+=sz0; + if (sx0==sx1 && sy0==sy1 && sz0==sz1) reset_view = true; + resize_disp = true; + } else switch (key = disp.key) { + case 0 : case cimg::keyCTRLLEFT : case cimg::keyPAD5 : case cimg::keySHIFTLEFT : case cimg::keyALT : disp.key = key = 0; break; + case cimg::keyP : if (visu.depth>1 && disp.is_keyCTRLLEFT) { // Special mode : play stack of frames + const unsigned int + w1 = visu.width*disp.width/(visu.width+(visu.depth>1?visu.depth:0)), + h1 = visu.height*disp.height/(visu.height+(visu.depth>1?visu.depth:0)); + disp.resize(cimg_fitscreen(w1,h1,1),false).key = disp.wheel = key = 0; + for (unsigned int timer = 0; !key && !disp.is_closed && !disp.button; ) { + if (disp.is_resized) disp.resize(); + if (!timer) { + visu.get_slice(XYZ[2]).display(disp.set_title("%s | z=%d",ntitle,XYZ[2])); + if (++XYZ[2]>=visu.depth) XYZ[2] = 0; + } + if (++timer>(unsigned int)frametiming) timer = 0; + if (disp.wheel) { frametiming-=disp.wheel/3.0f; disp.wheel = 0; } + switch (key = disp.key) { + case 0 : case cimg::keyCTRLLEFT : disp.key = key = 0; break; + case cimg::keyPAGEUP : frametiming-=0.3f; key = 0; break; + case cimg::keyPAGEDOWN : frametiming+=0.3f; key = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT) { + disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false); + disp.key = key = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT) { + disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false); + disp.key = key = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT) { + disp.normalscreen().resize(cimg_fitscreen(width,height,depth),false); + disp.key = key = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT) { + disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen(); + disp.key = key = 0; + } break; + } + frametiming = frametiming<1?1:(frametiming>39?39:frametiming); + disp.wait(20); + } + const unsigned int + w2 = (visu.width + (visu.depth>1?visu.depth:0))*disp.width/visu.width, + h2 = (visu.height + (visu.depth>1?visu.depth:0))*disp.height/visu.height; + disp.resize(cimg_fitscreen(w2,h2,1),false).set_title(ntitle); + key = disp.key = disp.button = disp.wheel = 0; + } break; + case cimg::keyHOME : case cimg::keyBACKSPACE : reset_view = resize_disp = true; key = 0; break; + case cimg::keyPADADD : go_in = true; go_in_center = true; key = 0; break; + case cimg::keyPADSUB : go_out = true; key = 0; break; + case cimg::keyARROWLEFT : case cimg::keyPAD4: go_left = true; key = 0; break; + case cimg::keyARROWRIGHT : case cimg::keyPAD6: go_right = true; key = 0; break; + case cimg::keyARROWUP : case cimg::keyPAD8: go_up = true; key = 0; break; + case cimg::keyARROWDOWN : case cimg::keyPAD2: go_down = true; key = 0; break; + case cimg::keyPAD7 : go_up = go_left = true; key = 0; break; + case cimg::keyPAD9 : go_up = go_right = true; key = 0; break; + case cimg::keyPAD1 : go_down = go_left = true; key = 0; break; + case cimg::keyPAD3 : go_down = go_right = true; key = 0; break; + case cimg::keyPAGEUP : go_inc = true; key = 0; break; + case cimg::keyPAGEDOWN : go_dec = true; key = 0; break; + } + if (go_in) { + const int + mx = go_in_center?disp.dimx()/2:disp.mouse_x, + my = go_in_center?disp.dimy()/2:disp.mouse_y, + mX = mx*(width+(depth>1?depth:0))/disp.width, + mY = my*(height+(depth>1?depth:0))/disp.height; + int X = XYZ[0], Y = XYZ[1], Z = XYZ[2]; + if (mX=dimy()) { X = x0 + mX*(1+x1-x0)/width; Z = z0 + (mY-height)*(1+z1-z0)/depth; Y = XYZ[1]; } + if (mX>=dimx() && mY4) { x0 = X - 7*(X-x0)/8; x1 = X + 7*(x1-X)/8; } + if (y1-y0>4) { y0 = Y - 7*(Y-y0)/8; y1 = Y + 7*(y1-Y)/8; } + if (z1-z0>4) { z0 = Z - 7*(Z-z0)/8; z1 = Z + 7*(z1-Z)/8; } + } + if (go_out) { + const int + deltax = (x1-x0)/8, deltay = (y1-y0)/8, deltaz = (z1-z0)/8, + ndeltax = deltax?deltax:(width>1?1:0), + ndeltay = deltay?deltay:(height>1?1:0), + ndeltaz = deltaz?deltaz:(depth>1?1:0); + x0-=ndeltax; y0-=ndeltay; z0-=ndeltaz; + x1+=ndeltax; y1+=ndeltay; z1+=ndeltaz; + if (x0<0) { x1-=x0; x0 = 0; if (x1>=dimx()) x1 = dimx()-1; } + if (y0<0) { y1-=y0; y0 = 0; if (y1>=dimy()) y1 = dimy()-1; } + if (z0<0) { z1-=z0; z0 = 0; if (z1>=dimz()) z1 = dimz()-1; } + if (x1>=dimx()) { x0-=(x1-dimx()+1); x1 = dimx()-1; if (x0<0) x0 = 0; } + if (y1>=dimy()) { y0-=(y1-dimy()+1); y1 = dimy()-1; if (y0<0) y0 = 0; } + if (z1>=dimz()) { z0-=(z1-dimz()+1); z1 = dimz()-1; if (z0<0) z0 = 0; } + } + if (go_left) { + const int delta = (x1-x0)/5, ndelta = delta?delta:(width>1?1:0); + if (x0-ndelta>=0) { x0-=ndelta; x1-=ndelta; } + else { x1-=x0; x0 = 0; } + } + if (go_right) { + const int delta = (x1-x0)/5, ndelta = delta?delta:(width>1?1:0); + if (x1+ndelta1?1:0); + if (y0-ndelta>=0) { y0-=ndelta; y1-=ndelta; } + else { y1-=y0; y0 = 0; } + } + if (go_down) { + const int delta = (y1-y0)/5, ndelta = delta?delta:(height>1?1:0); + if (y1+ndelta1?1:0); + if (z0-ndelta>=0) { z0-=ndelta; z1-=ndelta; } + else { z1-=z0; z0 = 0; } + } + if (go_dec) { + const int delta = (z1-z0)/5, ndelta = delta?delta:(depth>1?1:0); + if (z1+ndelta& select(CImgDisplay &disp, + const int select_type=2, unsigned int *const XYZ=0, + const unsigned char *const color=0) { + return get_select(disp,select_type,XYZ,color).transfer_to(*this); + } + + //! Simple interface to select a shape from an image. + CImg& select(const char *const title, + const int select_type=2, unsigned int *const XYZ=0, + const unsigned char *const color=0) { + return get_select(title,select_type,XYZ,color).transfer_to(*this); + } + + //! Simple interface to select a shape from an image. + CImg get_select(CImgDisplay &disp, + const int select_type=2, unsigned int *const XYZ=0, + const unsigned char *const color=0) const { + return _get_select(disp,0,select_type,XYZ,color,0,0,0); + } + + //! Simple interface to select a shape from an image. + CImg get_select(const char *const title, + const int select_type=2, unsigned int *const XYZ=0, + const unsigned char *const color=0) const { + CImgDisplay disp; + return _get_select(disp,title,select_type,XYZ,color,0,0,0); + } + + CImg _get_select(CImgDisplay &disp, const char *const title, + const int coords_type, unsigned int *const XYZ, + const unsigned char *const color, + const int origX, const int origY, const int origZ) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::select() : Instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),width,height,depth,dim,data); + if (!disp) { + char ntitle[64] = { 0 }; if (!title) { cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); } + disp.assign(cimg_fitscreen(width,height,depth),title?title:ntitle,1); + } + + const unsigned int + old_normalization = disp.normalization, + hatch = 0x55555555; + + bool old_is_resized = disp.is_resized; + disp.normalization = 0; + disp.show().key = 0; + + unsigned char foreground_color[] = { 255,255,105 }, background_color[] = { 0,0,0 }; + if (color) cimg_std::memcpy(foreground_color,color,sizeof(unsigned char)*cimg::min(3,dimv())); + + int area = 0, clicked_area = 0, phase = 0, + X0 = (int)((XYZ?XYZ[0]:width/2)%width), Y0 = (int)((XYZ?XYZ[1]:height/2)%height), Z0 = (int)((XYZ?XYZ[2]:depth/2)%depth), + X1 =-1, Y1 = -1, Z1 = -1, + X = -1, Y = -1, Z = -1, + oX = X, oY = Y, oZ = Z; + unsigned int old_button = 0, key = 0; + + bool shape_selected = false, text_down = false; + CImg visu, visu0; + char text[1024] = { 0 }; + + while (!key && !disp.is_closed && !shape_selected) { + + // Handle mouse motion and selection + oX = X; oY = Y; oZ = Z; + int mx = disp.mouse_x, my = disp.mouse_y; + const int mX = mx*(width+(depth>1?depth:0))/disp.width, mY = my*(height+(depth>1?depth:0))/disp.height; + + area = 0; + if (mX=dimy()) { area = 2; X = mX; Z = mY-height; Y = phase?Y1:Y0; } + if (mX>=dimx() && mY=2) { + switch (clicked_area) { + case 1 : Z1 = Z; break; + case 2 : Y1 = Y; break; + case 3 : X1 = X; break; + } + } + if (disp.button&2) { if (phase) { X1 = X; Y1 = Y; Z1 = Z; } else { X0 = X; Y0 = Y; Z0 = Z; } } + if (disp.button&4) { oX = X = X0; oY = Y = Y0; oZ = Z = Z0; phase = 0; visu.assign(); } + if (disp.wheel) { + if (depth>1 && !disp.is_keyCTRLLEFT && !disp.is_keySHIFTLEFT && !disp.is_keyALT) { + switch (area) { + case 1 : if (phase) Z = (Z1+=disp.wheel); else Z = (Z0+=disp.wheel); break; + case 2 : if (phase) Y = (Y1+=disp.wheel); else Y = (Y0+=disp.wheel); break; + case 3 : if (phase) X = (X1+=disp.wheel); else X = (X0+=disp.wheel); break; + } + disp.wheel = 0; + } else key = ~0U; + } + if ((disp.button&1)!=old_button) { + switch (phase++) { + case 0 : X0 = X1 = X; Y0 = Y1 = Y; Z0 = Z1 = Z; clicked_area = area; break; + case 1 : X1 = X; Y1 = Y; Z1 = Z; break; + } + old_button = disp.button&1; + } + if (depth>1 && (X!=oX || Y!=oY || Z!=oZ)) visu0.assign(); + } + + if (phase) { + if (!coords_type) shape_selected = phase?true:false; + else { + if (depth>1) shape_selected = (phase==3)?true:false; + else shape_selected = (phase==2)?true:false; + } + } + + if (X0<0) X0 = 0; if (X0>=dimx()) X0 = dimx()-1; if (Y0<0) Y0 = 0; if (Y0>=dimy()) Y0 = dimy()-1; + if (Z0<0) Z0 = 0; if (Z0>=dimz()) Z0 = dimz()-1; + if (X1<1) X1 = 0; if (X1>=dimx()) X1 = dimx()-1; if (Y1<0) Y1 = 0; if (Y1>=dimy()) Y1 = dimy()-1; + if (Z1<0) Z1 = 0; if (Z1>=dimz()) Z1 = dimz()-1; + + // Draw visualization image on the display + if (oX!=X || oY!=Y || oZ!=Z || !visu0) { + if (!visu0) { + CImg tmp, tmp0; + if (depth!=1) { + tmp0 = (!phase)?get_projections2d(X0,Y0,Z0):get_projections2d(X1,Y1,Z1); + tmp = tmp0.get_channels(0,cimg::min(2U,dim-1)); + } else tmp = get_channels(0,cimg::min(2U,dim-1)); + switch (old_normalization) { + case 0 : visu0 = tmp; break; + case 3 : + if (cimg::type::is_float()) visu0 = tmp.normalize(0,(T)255); + else { + const float m = (float)cimg::type::min(), M = (float)cimg::type::max(); + visu0.assign(tmp.width,tmp.height,1,tmp.dim); + unsigned char *ptrd = visu0.end(); + cimg_for(tmp,ptrs,Tuchar) *(--ptrd) = (unsigned char)((*ptrs-m)*255.0f/(M-m)); + } break; + default : visu0 = tmp.normalize(0,255); + } + visu0.resize(disp); + } + visu = visu0; + if (!color) { + if (visu.mean()<200) { + foreground_color[0] = foreground_color[1] = foreground_color[2] = 255; + background_color[0] = background_color[1] = background_color[2] = 0; + } else { + foreground_color[0] = foreground_color[1] = foreground_color[2] = 0; + background_color[0] = background_color[1] = background_color[2] = 255; + } + } + + const int d = (depth>1)?depth:0; + if (phase) switch (coords_type) { + case 1 : { + const int + x0 = (int)((X0+0.5f)*disp.width/(width+d)), + y0 = (int)((Y0+0.5f)*disp.height/(height+d)), + x1 = (int)((X1+0.5f)*disp.width/(width+d)), + y1 = (int)((Y1+0.5f)*disp.height/(height+d)); + visu.draw_arrow(x0,y0,x1,y1,foreground_color,0.6f,30,5,hatch); + if (d) { + const int + zx0 = (int)((width+Z0+0.5f)*disp.width/(width+d)), + zx1 = (int)((width+Z1+0.5f)*disp.width/(width+d)), + zy0 = (int)((height+Z0+0.5f)*disp.height/(height+d)), + zy1 = (int)((height+Z1+0.5f)*disp.height/(height+d)); + visu.draw_arrow(zx0,y0,zx1,y1,foreground_color,0.6f,30,5,hatch). + draw_arrow(x0,zy0,x1,zy1,foreground_color,0.6f,30,5,hatch); + } + } break; + case 2 : { + const int + x0 = (X0=4 && y1-y0>=4) visu.draw_rectangle(x0,y0,x1,y1,foreground_color,0.4f,~0U); + } + + if (my<12) text_down = true; + if (my>=visu.dimy()-11) text_down = false; + if (!coords_type || !phase) { + if (X>=0 && Y>=0 && Z>=0 && X1) cimg_std::sprintf(text,"Point (%d,%d,%d) = [ ",origX+X,origY+Y,origZ+Z); + else cimg_std::sprintf(text,"Point (%d,%d) = [ ",origX+X,origY+Y); + char *ctext = text + cimg::strlen(text), *const ltext = text + 512; + for (unsigned int k=0; k::format(),cimg::type::format((*this)(X,Y,Z,k))); + ctext = text + cimg::strlen(text); + *(ctext++) = ' '; *ctext = '\0'; + } + cimg_std::sprintf(text + cimg::strlen(text),"]"); + } + } else switch (coords_type) { + case 1 : { + const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), norm = cimg_std::sqrt(dX*dX+dY*dY+dZ*dZ); + if (depth>1) cimg_std::sprintf(text,"Vect (%d,%d,%d)-(%d,%d,%d), Norm = %g", + origX+X0,origY+Y0,origZ+Z0,origX+X1,origY+Y1,origZ+Z1,norm); + else cimg_std::sprintf(text,"Vect (%d,%d)-(%d,%d), Norm = %g", + origX+X0,origY+Y0,origX+X1,origY+Y1,norm); + } break; + case 2 : + if (depth>1) cimg_std::sprintf(text,"Box (%d,%d,%d)-(%d,%d,%d), Size = (%d,%d,%d)", + origX+(X01) cimg_std::sprintf(text,"Ellipse (%d,%d,%d)-(%d,%d,%d), Radii = (%d,%d,%d)", + origX+X0,origY+Y0,origZ+Z0,origX+X1,origY+Y1,origZ+Z1, + 1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1),1+cimg::abs(Z0-Z1)); + else cimg_std::sprintf(text,"Ellipse (%d,%d)-(%d,%d), Radii = (%d,%d)", + origX+X0,origY+Y0,origX+X1,origY+Y1,1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1)); + + } + if (phase || (mx>=0 && my>=0)) visu.draw_text(0,text_down?visu.dimy()-11:0,text,foreground_color,background_color,0.7f,11); + disp.display(visu).wait(25); + } else if (!shape_selected) disp.wait(); + + if (disp.is_resized) { disp.resize(false); old_is_resized = true; disp.is_resized = false; visu0.assign(); } + } + + // Return result + CImg res(1,6,1,1,-1); + if (XYZ) { XYZ[0] = (unsigned int)X0; XYZ[1] = (unsigned int)Y0; XYZ[2] = (unsigned int)Z0; } + if (shape_selected) { + if (coords_type==2) { + if (X0>X1) cimg::swap(X0,X1); + if (Y0>Y1) cimg::swap(Y0,Y1); + if (Z0>Z1) cimg::swap(Z0,Z1); + } + if (X1<0 || Y1<0 || Z1<0) X0 = Y0 = Z0 = X1 = Y1 = Z1 = -1; + switch (coords_type) { + case 1 : + case 2 : res[3] = X1; res[4] = Y1; res[5] = Z1; + default : res[0] = X0; res[1] = Y0; res[2] = Z0; + } + } + disp.button = 0; + disp.normalization = old_normalization; + disp.is_resized = old_is_resized; + if (key!=~0U) disp.key = key; + return res; + } + + //! High-level interface for displaying a 3d object. + template + const CImg& display_object3d(CImgDisplay& disp, + const CImg& points, const CImgList& primitives, + const CImgList& colors, const to& opacities, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool double_sided=false, const float focale=500, + const float specular_light=0.2f, const float specular_shine=0.1f, + const bool display_axes=true, float *const pose_matrix=0) const { + return _display_object3d(disp,0,points,points.width,primitives,colors,opacities,centering,render_static, + render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + } + + //! High-level interface for displaying a 3d object. + template + const CImg& display_object3d(const char *const title, + const CImg& points, const CImgList& primitives, + const CImgList& colors, const to& opacities, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool double_sided=false, const float focale=500, + const float specular_light=0.2f, const float specular_shine=0.1f, + const bool display_axes=true, float *const pose_matrix=0) const { + CImgDisplay disp; + return _display_object3d(disp,title,points,points.width,primitives,colors,opacities,centering,render_static, + render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + } + + //! High-level interface for displaying a 3d object. + template + const CImg& display_object3d(CImgDisplay& disp, + const CImgList& points, const CImgList& primitives, + const CImgList& colors, const to& opacities, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool double_sided=false, const float focale=500, + const float specular_light=0.2f, const float specular_shine=0.1f, + const bool display_axes=true, float *const pose_matrix=0) const { + return _display_object3d(disp,0,points,points.size,primitives,colors,opacities,centering,render_static, + render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + } + + //! High-level interface for displaying a 3d object. + template + const CImg& display_object3d(const char *const title, + const CImgList& points, const CImgList& primitives, + const CImgList& colors, const to& opacities, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool double_sided=false, const float focale=500, + const float specular_light=0.2f, const float specular_shine=0.1f, + const bool display_axes=true, float *const pose_matrix=0) const { + CImgDisplay disp; + return _display_object3d(disp,title,points,points.size,primitives,colors,opacities,centering,render_static, + render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + } + + //! High-level interface for displaying a 3d object. + template + const CImg& display_object3d(CImgDisplay &disp, + const tp& points, const CImgList& primitives, + const CImgList& colors, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool double_sided=false, const float focale=500, + const float specular_light=0.2f, const float specular_shine=0.1f, + const bool display_axes=true, float *const pose_matrix=0) const { + return display_object3d(disp,points,primitives,colors,CImg(),centering, + render_static,render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + } + + //! High-level interface for displaying a 3d object. + template + const CImg& display_object3d(const char *const title, + const tp& points, const CImgList& primitives, + const CImgList& colors, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool double_sided=false, const float focale=500, + const float specular_light=0.2f, const float specular_shine=0.1f, + const bool display_axes=true, float *const pose_matrix=0) const { + return display_object3d(title,points,primitives,colors,CImg(),centering, + render_static,render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + } + + //! High-level interface for displaying a 3d object. + template + const CImg& display_object3d(CImgDisplay &disp, + const tp& points, const CImgList& primitives, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool double_sided=false, const float focale=500, + const float specular_light=0.2f, const float specular_shine=0.1f, + const bool display_axes=true, float *const pose_matrix=0) const { + return display_object3d(disp,points,primitives,CImgList(),centering, + render_static,render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + } + + //! High-level interface for displaying a 3d object. + template + const CImg& display_object3d(const char *const title, + const tp& points, const CImgList& primitives, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool double_sided=false, const float focale=500, + const float specular_light=0.2f, const float specular_shine=0.1f, + const bool display_axes=true, float *const pose_matrix=0) const { + return display_object3d(title,points,primitives,CImgList(),centering, + render_static,render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + } + + //! High-level interface for displaying a 3d object. + template + const CImg& display_object3d(CImgDisplay &disp, + const tp& points, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool double_sided=false, const float focale=500, + const float specular_light=0.2f, const float specular_shine=0.1f, + const bool display_axes=true, float *const pose_matrix=0) const { + return display_object3d(disp,points,CImgList(),centering, + render_static,render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + } + + //! High-level interface for displaying a 3d object. + template + const CImg& display_object3d(const char *const title, + const tp& points, + const bool centering=true, + const int render_static=4, const int render_motion=1, + const bool double_sided=false, const float focale=500, + const float specular_light=0.2f, const float specular_shine=0.1f, + const bool display_axes=true, float *const pose_matrix=0) const { + return display_object3d(title,points,CImgList(),centering, + render_static,render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + } + + T _display_object3d_at2(const int i, const int j) const { + return atXY(i,j,0,0,0); + } + + template + const CImg& _display_object3d(CImgDisplay& disp, const char *const title, + const tp& points, const unsigned int Npoints, + const CImgList& primitives, + const CImgList& colors, const to& opacities, + const bool centering, + const int render_static, const int render_motion, + const bool double_sided, const float focale, + const float specular_light, const float specular_shine, + const bool display_axes, float *const pose_matrix) const { + + // Check input arguments + if (!points || !Npoints) + throw CImgArgumentException("CImg<%s>::display_object3d() : Given points are empty.", + pixel_type()); + if (is_empty()) { + if (disp) return CImg(disp.width,disp.height,1,colors[0].size(),0). + _display_object3d(disp,title,points,Npoints,primitives,colors,opacities,centering, + render_static,render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + else return CImg(cimg_fitscreen(640,480,1),1,colors[0].size(),0). + _display_object3d(disp,title,points,Npoints,primitives,colors,opacities,centering, + render_static,render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + } + if (!primitives) { + CImgList nprimitives(Npoints,1,1,1,1); + cimglist_for(nprimitives,l) nprimitives(l,0) = l; + return _display_object3d(disp,title,points,Npoints,nprimitives,colors,opacities, + centering,render_static,render_motion,double_sided,focale,specular_light,specular_shine, + display_axes,pose_matrix); + } + if (!disp) { + char ntitle[64] = { 0 }; if (!title) { cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); } + disp.assign(cimg_fitscreen(width,height,depth),title?title:ntitle,1); + } + + CImgList _colors; + if (!colors) _colors.insert(primitives.size,CImg::vector(200,200,200)); + const CImgList &ncolors = colors?colors:_colors; + + // Init 3D objects and compute object statistics + CImg + pose, rot_mat, zbuffer, + centered_points = centering?CImg(Npoints,3):CImg(), + rotated_points(Npoints,3), + bbox_points, rotated_bbox_points, + axes_points, rotated_axes_points, + bbox_opacities, axes_opacities; + CImgList bbox_primitives, axes_primitives; + CImgList bbox_colors, bbox_colors2, axes_colors; + float dx = 0, dy = 0, dz = 0, ratio = 1; + + T minval = (T)0, maxval = (T)255; + if (disp.normalization && colors) { + minval = colors.minmax(maxval); + if (minval==maxval) { minval = (T)0; maxval = (T)255; } + } + const float meanval = (float)mean(); + bool color_model = true; + if (cimg::abs(meanval-minval)>cimg::abs(meanval-maxval)) color_model = false; + const CImg + background_color(1,1,1,dim,color_model?minval:maxval), + foreground_color(1,1,1,dim,color_model?maxval:minval); + + float xm = cimg::type::max(), xM = 0, ym = xm, yM = 0, zm = xm, zM = 0; + for (unsigned int i = 0; ixM) xM = x; + if (yyM) yM = y; + if (zzM) zM = z; + } + const float delta = cimg::max(xM-xm,yM-ym,zM-zm); + + if (display_axes) { + rotated_axes_points = axes_points.assign(7,3,1,1, + 0,20,0,0,22,-6,-6, + 0,0,20,0,-6,22,-6, + 0,0,0,20,0,0,22); + axes_opacities.assign(3,1,1,1,1); + axes_colors.assign(3,dim,1,1,1,foreground_color[0]); + axes_primitives.assign(3,1,2,1,1, 0,1, 0,2, 0,3); + } + + // Begin user interaction loop + CImg visu0(*this), visu; + bool init = true, clicked = false, redraw = true; + unsigned int key = 0; + int x0 = 0, y0 = 0, x1 = 0, y1 = 0; + disp.show().flush(); + + while (!disp.is_closed && !key) { + + // Init object position and scale if necessary + if (init) { + ratio = delta>0?(2.0f*cimg::min(disp.width,disp.height)/(3.0f*delta)):0; + dx = 0.5f*(xM + xm); dy = 0.5f*(yM + ym); dz = 0.5f*(zM + zm); + if (centering) { + cimg_forX(centered_points,l) { + centered_points(l,0) = (float)((points(l,0) - dx)*ratio); + centered_points(l,1) = (float)((points(l,1) - dy)*ratio); + centered_points(l,2) = (float)((points(l,2) - dz)*ratio); + } + } + + if (render_static<0 || render_motion<0) { + rotated_bbox_points = bbox_points.assign(8,3,1,1, + xm,xM,xM,xm,xm,xM,xM,xm, + ym,ym,yM,yM,ym,ym,yM,yM, + zm,zm,zm,zm,zM,zM,zM,zM); + bbox_primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 1,2,6,5, 0,4,7,3, 0,1,5,4, 2,3,7,6); + bbox_colors.assign(6,dim,1,1,1,background_color[0]); + bbox_colors2.assign(6,dim,1,1,1,foreground_color[0]); + bbox_opacities.assign(bbox_colors.size,1,1,1,0.3f); + } + + if (!pose) { + if (pose_matrix) pose = CImg(pose_matrix,4,4,1,1,false); + else pose = CImg::identity_matrix(4); + } + init = false; + redraw = true; + } + + // Rotate and Draw 3D object + if (redraw) { + const float + r00 = pose(0,0), r10 = pose(1,0), r20 = pose(2,0), r30 = pose(3,0), + r01 = pose(0,1), r11 = pose(1,1), r21 = pose(2,1), r31 = pose(3,1), + r02 = pose(0,2), r12 = pose(1,2), r22 = pose(2,2), r32 = pose(3,2); + if ((clicked && render_motion>=0) || (!clicked && render_static>=0)) { + if (centering) cimg_forX(centered_points,l) { + const float x = centered_points(l,0), y = centered_points(l,1), z = centered_points(l,2); + rotated_points(l,0) = r00*x + r10*y + r20*z + r30; + rotated_points(l,1) = r01*x + r11*y + r21*z + r31; + rotated_points(l,2) = r02*x + r12*y + r22*z + r32; + } else for (unsigned int l = 0; l0)?zbuffer.fill(0).ptr():0); + + // Draw axes + if (display_axes) { + const float Xaxes = 25, Yaxes = visu.height - 35.0f; + cimg_forX(axes_points,l) { + const float x = axes_points(l,0), y = axes_points(l,1), z = axes_points(l,2); + rotated_axes_points(l,0) = r00*x + r10*y + r20*z; + rotated_axes_points(l,1) = r01*x + r11*y + r21*z; + rotated_axes_points(l,2) = r02*x + r12*y + r22*z; + } + axes_opacities(0,0) = (rotated_axes_points(1,2)>0)?0.5f:1.0f; + axes_opacities(1,0) = (rotated_axes_points(2,2)>0)?0.5f:1.0f; + axes_opacities(2,0) = (rotated_axes_points(3,2)>0)?0.5f:1.0f; + visu.draw_object3d(Xaxes,Yaxes,0,rotated_axes_points,axes_primitives,axes_colors,axes_opacities,1,false,focale). + draw_text((int)(Xaxes+rotated_axes_points(4,0)), + (int)(Yaxes+rotated_axes_points(4,1)), + "X",axes_colors[0].data,0,axes_opacities(0,0),11). + draw_text((int)(Xaxes+rotated_axes_points(5,0)), + (int)(Yaxes+rotated_axes_points(5,1)), + "Y",axes_colors[1].data,0,axes_opacities(1,0),11). + draw_text((int)(Xaxes+rotated_axes_points(6,0)), + (int)(Yaxes+rotated_axes_points(6,1)), + "Z",axes_colors[2].data,0,axes_opacities(2,0),11); + } + visu.display(disp); + if (!clicked || render_motion==render_static) redraw = false; + } + + // Handle user interaction + disp.wait(); + if ((disp.button || disp.wheel) && disp.mouse_x>=0 && disp.mouse_y>=0) { + redraw = true; + if (!clicked) { x0 = x1 = disp.mouse_x; y0 = y1 = disp.mouse_y; if (!disp.wheel) clicked = true; } + else { x1 = disp.mouse_x; y1 = disp.mouse_y; } + if (disp.button&1) { + const float + R = 0.45f*cimg::min(disp.width,disp.height), + R2 = R*R, + u0 = (float)(x0-disp.dimx()/2), + v0 = (float)(y0-disp.dimy()/2), + u1 = (float)(x1-disp.dimx()/2), + v1 = (float)(y1-disp.dimy()/2), + n0 = (float)cimg_std::sqrt(u0*u0+v0*v0), + n1 = (float)cimg_std::sqrt(u1*u1+v1*v1), + nu0 = n0>R?(u0*R/n0):u0, + nv0 = n0>R?(v0*R/n0):v0, + nw0 = (float)cimg_std::sqrt(cimg::max(0,R2-nu0*nu0-nv0*nv0)), + nu1 = n1>R?(u1*R/n1):u1, + nv1 = n1>R?(v1*R/n1):v1, + nw1 = (float)cimg_std::sqrt(cimg::max(0,R2-nu1*nu1-nv1*nv1)), + u = nv0*nw1-nw0*nv1, + v = nw0*nu1-nu0*nw1, + w = nv0*nu1-nu0*nv1, + n = (float)cimg_std::sqrt(u*u+v*v+w*w), + alpha = (float)cimg_std::asin(n/R2); + rot_mat = CImg::rotation_matrix(u,v,w,alpha); + rot_mat *= pose.get_crop(0,0,2,2); + pose.draw_image(rot_mat); + x0=x1; y0=y1; + } + if (disp.button&2) { pose(3,2)+=(y1-y0); x0 = x1; y0 = y1; } + if (disp.wheel) { pose(3,2)-=focale*disp.wheel/10; disp.wheel = 0; } + if (disp.button&4) { pose(3,0)+=(x1-x0); pose(3,1)+=(y1-y0); x0 = x1; y0 = y1; } + if ((disp.button&1) && (disp.button&2)) { init = true; disp.button = 0; x0 = x1; y0 = y1; pose = CImg::identity_matrix(4); } + } else if (clicked) { x0 = x1; y0 = y1; clicked = false; redraw = true; } + + switch (key = disp.key) { + case 0 : case cimg::keyCTRLLEFT : disp.key = key = 0; break; + case cimg::keyD: if (disp.is_keyCTRLLEFT) { + disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false).is_resized = true; + disp.key = key = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT) { + disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false).is_resized = true; + disp.key = key = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT) { + disp.normalscreen().resize(cimg_fitscreen(width,height,depth),false).is_resized = true; + disp.key = key = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT) { + disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen().is_resized = true; + disp.key = key = 0; + } break; + case cimg::keyZ : if (disp.is_keyCTRLLEFT) { // Enable/Disable Z-buffer + if (zbuffer) zbuffer.assign(); + else zbuffer.assign(disp.width,disp.height); + disp.key = key = 0; redraw = true; + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT) { // Save snapshot + static unsigned int snap_number = 0; + char filename[32] = { 0 }; + cimg_std::FILE *file; + do { + cimg_std::sprintf(filename,"CImg_%.4u.bmp",snap_number++); + if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file); + } while (file); + (+visu).draw_text(2,2,"Saving BMP snapshot...",foreground_color,background_color,1,11).display(disp); + visu.save(filename); + visu.draw_text(2,2,"Snapshot '%s' saved.",foreground_color,background_color,1,11,filename).display(disp); + disp.key = key = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT) { // Save object as an .OFF file + static unsigned int snap_number = 0; + char filename[32] = { 0 }; + cimg_std::FILE *file; + do { + cimg_std::sprintf(filename,"CImg_%.4u.off",snap_number++); + if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file); + } while (file); + visu.draw_text(2,2,"Saving object...",foreground_color,background_color,1,11).display(disp); + points.save_off(filename,primitives,ncolors); + visu.draw_text(2,2,"Object '%s' saved.",foreground_color,background_color,1,11,filename).display(disp); + disp.key = key = 0; + } break; +#ifdef cimg_use_board + case cimg::keyP : if (disp.is_keyCTRLLEFT) { // Save object as a .EPS file + static unsigned int snap_number = 0; + char filename[32] = { 0 }; + cimg_std::FILE *file; + do { + cimg_std::sprintf(filename,"CImg_%.4u.eps",snap_number++); + if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file); + } while (file); + visu.draw_text(2,2,"Saving EPS snapshot...",foreground_color,background_color,1,11).display(disp); + BoardLib::Board board; + (+visu).draw_object3d(board,visu.width/2.0f, visu.height/2.0f, 0, + rotated_points,primitives,ncolors,opacities,clicked?render_motion:render_static, + double_sided,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000,specular_light,specular_shine, + zbuffer.fill(0).ptr()); + board.saveEPS(filename); + visu.draw_text(2,2,"Object '%s' saved.",foreground_color,background_color,1,11,filename).display(disp); + disp.key = key = 0; + } break; + case cimg::keyV : if (disp.is_keyCTRLLEFT) { // Save object as a .SVG file + static unsigned int snap_number = 0; + char filename[32] = { 0 }; + cimg_std::FILE *file; + do { + cimg_std::sprintf(filename,"CImg_%.4u.svg",snap_number++); + if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file); + } while (file); + visu.draw_text(2,2,"Saving SVG snapshot...",foreground_color,background_color,1,11).display(disp); + BoardLib::Board board; + (+visu).draw_object3d(board,visu.width/2.0f, visu.height/2.0f, 0, + rotated_points,primitives,ncolors,opacities,clicked?render_motion:render_static, + double_sided,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000,specular_light,specular_shine, + zbuffer.fill(0).ptr()); + board.saveSVG(filename); + visu.draw_text(2,2,"Object '%s' saved.",foreground_color,background_color,1,11,filename).display(disp); + disp.key = key = 0; + } break; +#endif + } + if (disp.is_resized) { disp.resize(false); visu0 = get_resize(disp,1); if (zbuffer) zbuffer.assign(disp.width,disp.height); redraw = true; } + } + if (pose_matrix) cimg_std::memcpy(pose_matrix,pose.data,16*sizeof(float)); + disp.button = 0; + disp.key = key; + return *this; + } + + //! High-level interface for displaying a graph. + const CImg& display_graph(CImgDisplay &disp, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::display_graph() : Instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),width,height,depth,dim,data); + const unsigned int siz = width*height*depth, onormalization = disp.normalization; + if (!disp) { char ntitle[64] = { 0 }; cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); disp.assign(640,480,ntitle,0); } + disp.show().flush().normalization = 0; + double y0 = ymin, y1 = ymax, nxmin = xmin, nxmax = xmax; + if (nxmin==nxmax) { nxmin = 0; nxmax = siz - 1.0; } + int x0 = 0, x1 = size()/dimv()-1, key = 0; + + for (bool reset_view = true, resize_disp = false; !key && !disp.is_closed; ) { + if (reset_view) { x0 = 0; x1 = size()/dimv()-1; y0 = ymin; y1 = ymax; reset_view = false; } + CImg zoom(x1-x0+1,1,1,dimv()); + cimg_forV(*this,k) zoom.get_shared_channel(k) = CImg(ptr(x0,0,0,k),x1-x0+1,1,1,1,true); + + if (y0==y1) y0 = zoom.minmax(y1); + if (y0==y1) { --y0; ++y1; } + const CImg selection = zoom.get_select_graph(disp,plot_type,vertex_type, + labelx,nxmin + x0*(nxmax-nxmin)/siz,nxmin + x1*(nxmax-nxmin)/siz, + labely,y0,y1); + + const int mouse_x = disp.mouse_x, mouse_y = disp.mouse_y; + if (selection[0]>=0 && selection[2]>=0) { + x1 = x0 + selection[2]; + x0 += selection[0]; + if (x0==x1) reset_view = true; + if (selection[1]>=0 && selection[3]>=0) { + y0 = y1 - selection[3]*(y1-y0)/(disp.dimy()-32); + y1 -= selection[1]*(y1-y0)/(disp.dimy()-32); + } + } else { + bool go_in = false, go_out = false, go_left = false, go_right = false, go_up = false, go_down = false; + switch (key = disp.key) { + case cimg::keyHOME : case cimg::keyBACKSPACE : reset_view = resize_disp = true; key = 0; break; + case cimg::keyPADADD : go_in = true; key = 0; break; + case cimg::keyPADSUB : go_out = true; key = 0; break; + case cimg::keyARROWLEFT : case cimg::keyPAD4 : go_left = true; key = 0; break; + case cimg::keyARROWRIGHT : case cimg::keyPAD6 : go_right = true; key = 0; break; + case cimg::keyARROWUP : case cimg::keyPAD8 : go_up = true; key = 0; break; + case cimg::keyARROWDOWN : case cimg::keyPAD2 : go_down = true; key = 0; break; + case cimg::keyPAD7 : go_left = true; go_up = true; key = 0; break; + case cimg::keyPAD9 : go_right = true; go_up = true; key = 0; break; + case cimg::keyPAD1 : go_left = true; go_down = true; key = 0; break; + case cimg::keyPAD3 : go_right = true; go_down = true; key = 0; break; + } + if (disp.wheel) go_out = !(go_in = disp.wheel>0); + + if (go_in) { + const int + xsiz = x1 - x0, + mx = (mouse_x-16)*xsiz/(disp.dimx()-32), + cx = x0 + (mx<0?0:(mx>=xsiz?xsiz:mx)); + if (x1-x0>4) { + x0 = cx - 7*(cx-x0)/8; x1 = cx + 7*(x1-cx)/8; + if (disp.is_keyCTRLLEFT) { + const double + ysiz = y1 - y0, + my = (mouse_y-16)*ysiz/(disp.dimy()-32), + cy = y1 - (my<0?0:(my>=ysiz?ysiz:my)); + y0 = cy - 7*(cy-y0)/8; y1 = cy + 7*(y1-cy)/8; + } else y0 = y1 = 0; + } + } + if (go_out) { + const int deltax = (x1-x0)/8, ndeltax = deltax?deltax:(siz>1?1:0); + x0-=ndeltax; x1+=ndeltax; + if (x0<0) { x1-=x0; x0 = 0; if (x1>=(int)siz) x1 = (int)siz-1; } + if (x1>=(int)siz) { x0-=(x1-siz+1); x1 = (int)siz-1; if (x0<0) x0 = 0; } + if (disp.is_keyCTRLLEFT) { + const double deltay = (y1-y0)/8, ndeltay = deltay?deltay:0.01; + y0-=ndeltay; y1+=ndeltay; + } + } + if (go_left) { + const int delta = (x1-x0)/5, ndelta = delta?delta:1; + if (x0-ndelta>=0) { x0-=ndelta; x1-=ndelta; } + else { x1-=x0; x0 = 0; } + go_left = false; + } + if (go_right) { + const int delta = (x1-x0)/5, ndelta = delta?delta:1; + if (x1+ndelta<(int)siz) { x0+=ndelta; x1+=ndelta; } + else { x0+=(siz-1-x1); x1 = siz-1; } + go_right = false; + } + if (go_up) { + const double delta = (y1-y0)/10, ndelta = delta?delta:1; + y0+=ndelta; y1+=ndelta; + go_up = false; + } + if (go_down) { + const double delta = (y1-y0)/10, ndelta = delta?delta:1; + y0-=ndelta; y1-=ndelta; + go_down = false; + } + } + } + disp.normalization = onormalization; + return *this; + } + + //! High-level interface for displaying a graph. + const CImg& display_graph(const char *const title=0, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::display_graph() : Instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),width,height,depth,dim,data); + char ntitle[64] = { 0 }; if (!title) cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); + CImgDisplay disp(cimg_fitscreen(640,480,1),title?title:ntitle,0); + return display_graph(disp,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax); + } + + //! Select sub-graph in a graph. + CImg get_select_graph(CImgDisplay &disp, + const unsigned int plot_type=1, const unsigned int vertex_type=1, + const char *const labelx=0, const double xmin=0, const double xmax=0, + const char *const labely=0, const double ymin=0, const double ymax=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::display_graph() : Instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),width,height,depth,dim,data); + const unsigned int siz = width*height*depth, onormalization = disp.normalization; + if (!disp) { char ntitle[64] = { 0 }; cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); disp.assign(640,480,ntitle,0); } + disp.show().key = disp.normalization = disp.button = disp.wheel = 0; // Must keep 'key' field unchanged. + double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax; + if (nymin==nymax) nymin = (Tfloat)minmax(nymax); + if (nymin==nymax) { --nymin; ++nymax; } + if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.0; } + + const unsigned char black[] = { 0,0,0 }, white[] = { 255,255,255 }, gray[] = { 220,220,220 }; + const unsigned char gray2[] = { 110,110,110 }, ngray[] = { 35,35,35 }; + static unsigned int odimv = 0; + static CImg palette; + if (odimv!=dim) { + odimv = dim; + palette = CImg(3,dim,1,1,120).noise(70,1); + if (dim==1) { palette[0] = palette[1] = 120; palette[2] = 200; } + else { + palette(0,0) = 220; palette(1,0) = 10; palette(2,0) = 10; + if (dim>1) { palette(0,1) = 10; palette(1,1) = 220; palette(2,1) = 10; } + if (dim>2) { palette(0,2) = 10; palette(1,2) = 10; palette(2,2) = 220; } + } + } + + CImg visu0, visu, graph, text, axes; + const unsigned int whz = width*height*depth; + int x0 = -1, x1 = -1, y0 = -1, y1 = -1, omouse_x = -2, omouse_y = -2; + char message[1024] = { 0 }; + unsigned int okey = 0, obutton = 0; + CImg_3x3(I,unsigned char); + + for (bool selected = false; !selected && !disp.is_closed && !okey && !disp.wheel; ) { + const int mouse_x = disp.mouse_x, mouse_y = disp.mouse_y; + const unsigned int key = disp.key, button = disp.button; + + // Generate graph representation. + if (!visu0) { + visu0.assign(disp.dimx(),disp.dimy(),1,3,220); + const int gdimx = disp.dimx() - 32, gdimy = disp.dimy() - 32; + if (gdimx>0 && gdimy>0) { + graph.assign(gdimx,gdimy,1,3,255); + graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333); + cimg_forV(*this,k) graph.draw_graph(get_shared_channel(k),&palette(0,k),(plot_type!=3 || dim==1)?1:0.6f, + plot_type,vertex_type,nymax,nymin); + + axes.assign(gdimx,gdimy,1,1,0); + const float + dx = (float)cimg::abs(nxmax-nxmin), dy = (float)cimg::abs(nymax-nymin), + px = (float)cimg_std::pow(10.0,(int)cimg_std::log10(dx)-2.0), + py = (float)cimg_std::pow(10.0,(int)cimg_std::log10(dy)-2.0); + const CImg + seqx = CImg::sequence(1 + gdimx/60,nxmin,nxmax).round(px), + seqy = CImg::sequence(1 + gdimy/60,nymax,nymin).round(py); + axes.draw_axis(seqx,seqy,white); + if (nymin>0) axes.draw_axis(seqx,gdimy-1,gray); + if (nymax<0) axes.draw_axis(seqx,0,gray); + if (nxmin>0) axes.draw_axis(0,seqy,gray); + if (nxmax<0) axes.draw_axis(gdimx-1,seqy,gray); + + cimg_for3x3(axes,x,y,0,0,I) + if (Icc) { + if (Icc==255) cimg_forV(graph,k) graph(x,y,k) = 0; + else cimg_forV(graph,k) graph(x,y,k) = (unsigned char)(2*graph(x,y,k)/3); + } + else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp) cimg_forV(graph,k) graph(x,y,k) = (graph(x,y,k)+255)/2; + + visu0.draw_image(16,16,graph); + visu0.draw_line(15,15,16+gdimx,15,gray2).draw_line(16+gdimx,15,16+gdimx,16+gdimy,gray2). + draw_line(16+gdimx,16+gdimy,15,16+gdimy,white).draw_line(15,16+gdimy,15,15,white); + } else graph.assign(); + text.assign().draw_text(0,0,labelx?labelx:"X-axis",white,ngray,1); + visu0.draw_image((visu0.dimx()-text.dimx())/2,visu0.dimy()-14,~text); + text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1).rotate(-90); + visu0.draw_image(2,(visu0.dimy()-text.dimy())/2,~text); + visu.assign(); + } + + // Generate and display current view. + if (!visu) { + visu.assign(visu0); + if (graph && x0>=0 && x1>=0) { + const int + nx0 = x0<=x1?x0:x1, + nx1 = x0<=x1?x1:x0, + ny0 = y0<=y1?y0:y1, + ny1 = y0<=y1?y1:y0, + sx0 = 16 + nx0*(visu.dimx()-32)/whz, + sx1 = 15 + (nx1+1)*(visu.dimx()-32)/whz, + sy0 = 16 + ny0, + sy1 = 16 + ny1; + + if (y0>=0 && y1>=0) + visu.draw_rectangle(sx0,sy0,sx1,sy1,gray,0.5f).draw_rectangle(sx0,sy0,sx1,sy1,black,0.5f,0xCCCCCCCCU); + else visu.draw_rectangle(sx0,0,sx1,visu.dimy()-17,gray,0.5f). + draw_line(sx0,16,sx0,visu.dimy()-17,black,0.5f,0xCCCCCCCCU). + draw_line(sx1,16,sx1,visu.dimy()-17,black,0.5f,0xCCCCCCCCU); + } + if (mouse_x>=16 && mouse_y>=16 && mouse_x=7) + cimg_std::sprintf(message,"Value[%g] = ( %g %g %g ... %g %g %g )",cx, + (double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2), + (double)(*this)(x,0,0,dim-4),(double)(*this)(x,0,0,dim-3),(double)(*this)(x,0,0,dim-1)); + else { + cimg_std::sprintf(message,"Value[%g] = ( ",cx); + cimg_forV(*this,k) cimg_std::sprintf(message+cimg::strlen(message),"%g ",(double)(*this)(x,0,0,k)); + cimg_std::sprintf(message+cimg::strlen(message),")"); + } + if (x0>=0 && x1>=0) { + const int + nx0 = x0<=x1?x0:x1, + nx1 = x0<=x1?x1:x0, + ny0 = y0<=y1?y0:y1, + ny1 = y0<=y1?y1:y0; + const double + cx0 = nxmin + nx0*(nxmax-nxmin)/(visu.dimx()-32), + cx1 = nxmin + nx1*(nxmax-nxmin)/(visu.dimx()-32), + cy0 = nymax - ny0*(nymax-nymin)/(visu.dimy()-32), + cy1 = nymax - ny1*(nymax-nymin)/(visu.dimy()-32); + if (y0>=0 && y1>=0) + cimg_std::sprintf(message+cimg::strlen(message)," - Range ( %g, %g ) - ( %g, %g )",cx0,cy0,cx1,cy1); + else + cimg_std::sprintf(message+cimg::strlen(message)," - Range [ %g - %g ]",cx0,cx1); + } + text.assign().draw_text(0,0,message,white,ngray,1); + visu.draw_image((visu.dimx()-text.dimx())/2,2,~text); + } + visu.display(disp); + } + + // Test keys. + switch (okey = key) { + case cimg::keyCTRLLEFT : okey = 0; break; + case cimg::keyD : if (disp.is_keyCTRLLEFT) { + disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false), + CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false).is_resized = true; + disp.key = okey = 0; + } break; + case cimg::keyC : if (disp.is_keyCTRLLEFT) { + disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false).is_resized = true; + disp.key = okey = 0; + } break; + case cimg::keyR : if (disp.is_keyCTRLLEFT) { + disp.normalscreen().resize(cimg_fitscreen(640,480,1),false).is_resized = true; + disp.key = okey = 0; + } break; + case cimg::keyF : if (disp.is_keyCTRLLEFT) { + disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen().is_resized = true; + disp.key = okey = 0; + } break; + case cimg::keyS : if (disp.is_keyCTRLLEFT) { + static unsigned int snap_number = 0; + if (visu || visu0) { + CImg &screen = visu?visu:visu0; + char filename[32] = { 0 }; + cimg_std::FILE *file; + do { + cimg_std::sprintf(filename,"CImg_%.4u.bmp",snap_number++); + if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file); + } while (file); + (+screen).draw_text(2,2,"Saving BMP snapshot...",black,gray,1,11).display(disp); + screen.save(filename); + screen.draw_text(2,2,"Snapshot '%s' saved.",black,gray,1,11,filename).display(disp); + } + disp.key = okey = 0; + } break; + } + + // Handle mouse motion and mouse buttons + if (obutton!=button || omouse_x!=mouse_x || omouse_y!=mouse_y) { + visu.assign(); + if (disp.mouse_x>=0 && disp.mouse_y>=0) { + const int + mx = (mouse_x-16)*(int)whz/(disp.dimx()-32), + cx = mx<0?0:(mx>=(int)whz?whz-1:mx), + my = mouse_y-16, + cy = my<=0?0:(my>=(disp.dimy()-32)?(disp.dimy()-32):my); + if (button&1) { if (!obutton) { x0 = cx; y0 = -1; } else { x1 = cx; y1 = -1; }} + else if (button&2) { if (!obutton) { x0 = cx; y0 = cy; } else { x1 = cx; y1 = cy; }} + else if (obutton) { x1 = cx; y1 = y1>=0?cy:-1; selected = true; } + } else if (!button && obutton) selected = true; + obutton = button; omouse_x = mouse_x; omouse_y = mouse_y; + } + if (disp.is_resized) { disp.resize(false); visu0.assign(); } + if (visu && visu0) disp.wait(); + } + disp.normalization = onormalization; + if (x1(4,1,1,1,x0,y0,x1,y1); + } + + //@} + //--------------------------- + // + //! \name Image File Loading + //@{ + //--------------------------- + + //! Load an image from a file. + /** + \param filename is the name of the image file to load. + \note The extension of \c filename defines the file format. If no filename + extension is provided, CImg::get_load() will try to load a .cimg file. + **/ + CImg& load(const char *const filename) { + if (!filename) + throw CImgArgumentException("CImg<%s>::load() : Cannot load (null) filename.", + pixel_type()); + const char *ext = cimg::split_filename(filename); + const unsigned int odebug = cimg::exception_mode(); + cimg::exception_mode() = 0; + assign(); + try { +#ifdef cimg_load_plugin + cimg_load_plugin(filename); +#endif +#ifdef cimg_load_plugin1 + cimg_load_plugin1(filename); +#endif +#ifdef cimg_load_plugin2 + cimg_load_plugin2(filename); +#endif +#ifdef cimg_load_plugin3 + cimg_load_plugin3(filename); +#endif +#ifdef cimg_load_plugin4 + cimg_load_plugin4(filename); +#endif +#ifdef cimg_load_plugin5 + cimg_load_plugin5(filename); +#endif +#ifdef cimg_load_plugin6 + cimg_load_plugin6(filename); +#endif +#ifdef cimg_load_plugin7 + cimg_load_plugin7(filename); +#endif +#ifdef cimg_load_plugin8 + cimg_load_plugin8(filename); +#endif + // ASCII formats + if (!cimg::strcasecmp(ext,"asc")) load_ascii(filename); + if (!cimg::strcasecmp(ext,"dlm") || + !cimg::strcasecmp(ext,"txt")) load_dlm(filename); + + // 2D binary formats + if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename); + if (!cimg::strcasecmp(ext,"jpg") || + !cimg::strcasecmp(ext,"jpeg") || + !cimg::strcasecmp(ext,"jpe") || + !cimg::strcasecmp(ext,"jfif") || + !cimg::strcasecmp(ext,"jif")) load_jpeg(filename); + if (!cimg::strcasecmp(ext,"png")) load_png(filename); + if (!cimg::strcasecmp(ext,"ppm") || + !cimg::strcasecmp(ext,"pgm") || + !cimg::strcasecmp(ext,"pnm")) load_pnm(filename); + if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) load_tiff(filename); + if (!cimg::strcasecmp(ext,"cr2") || + !cimg::strcasecmp(ext,"crw") || + !cimg::strcasecmp(ext,"dcr") || + !cimg::strcasecmp(ext,"mrw") || + !cimg::strcasecmp(ext,"nef") || + !cimg::strcasecmp(ext,"orf") || + !cimg::strcasecmp(ext,"pix") || + !cimg::strcasecmp(ext,"ptx") || + !cimg::strcasecmp(ext,"raf") || + !cimg::strcasecmp(ext,"srf")) load_dcraw_external(filename); + + // 3D binary formats + if (!cimg::strcasecmp(ext,"dcm") || + !cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename); + if (!cimg::strcasecmp(ext,"hdr") || + !cimg::strcasecmp(ext,"nii")) load_analyze(filename); + if (!cimg::strcasecmp(ext,"par") || + !cimg::strcasecmp(ext,"rec")) load_parrec(filename); + if (!cimg::strcasecmp(ext,"inr")) load_inr(filename); + if (!cimg::strcasecmp(ext,"pan")) load_pandore(filename); + if (!cimg::strcasecmp(ext,"cimg") || + !cimg::strcasecmp(ext,"cimgz") || + *ext=='\0') return load_cimg(filename); + + // Archive files + if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename); + + // Image sequences + if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) load_ffmpeg(filename); + if (is_empty()) throw CImgIOException("CImg<%s>::load()",pixel_type()); + } catch (CImgException& e) { + if (!cimg::strncasecmp(e.message,"cimg::fopen()",13)) { + cimg::exception_mode() = odebug; + throw CImgIOException("CImg<%s>::load() : File '%s' cannot be opened.",pixel_type(),filename); + } else try { + const char *const ftype = cimg::file_type(0,filename); + assign(); + if (!cimg::strcmp(ftype,"pnm")) load_pnm(filename); + if (!cimg::strcmp(ftype,"bmp")) load_bmp(filename); + if (!cimg::strcmp(ftype,"jpeg")) load_jpeg(filename); + if (!cimg::strcmp(ftype,"pan")) load_pandore(filename); + if (!cimg::strcmp(ftype,"png")) load_png(filename); + if (!cimg::strcmp(ftype,"tiff")) load_tiff(filename); + if (is_empty()) throw CImgIOException("CImg<%s>::load()",pixel_type()); + } catch (CImgException&) { + try { + load_other(filename); + } catch (CImgException&) { + assign(); + } + } + } + cimg::exception_mode() = odebug; + if (is_empty()) + throw CImgIOException("CImg<%s>::load() : File '%s', format not recognized.",pixel_type(),filename); + return *this; + } + + static CImg get_load(const char *const filename) { + return CImg().load(filename); + } + + //! Load an image from an ASCII file. + CImg& load_ascii(const char *const filename) { + return _load_ascii(0,filename); + } + + static CImg get_load_ascii(const char *const filename) { + return CImg().load_ascii(filename); + } + + //! Load an image from an ASCII file. + CImg& load_ascii(cimg_std::FILE *const file) { + return _load_ascii(file,0); + } + + static CImg get_load_ascii(cimg_std::FILE *const file) { + return CImg().load_ascii(file); + } + + CImg& _load_ascii(cimg_std::FILE *const file, const char *const filename) { + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_ascii() : Cannot load (null) filename.", + pixel_type()); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + char line[256] = { 0 }; + int err = cimg_std::fscanf(nfile,"%*[^0-9]%255[^\n]",line); + unsigned int off, dx = 0, dy = 1, dz = 1, dv = 1; + cimg_std::sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dv); + err = cimg_std::fscanf(nfile,"%*[^0-9.+-]"); + if (!dx || !dy || !dz || !dv) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_ascii() : File '%s', invalid .ASC header, specified image dimensions are (%u,%u,%u,%u).", + pixel_type(),filename?filename:"(FILE*)",dx,dy,dz,dv); + } + assign(dx,dy,dz,dv); + const unsigned long siz = size(); + double val; + T *ptr = data; + for (err = 1, off = 0; off::load_ascii() : File '%s', only %u/%lu values read.", + pixel_type(),filename?filename:"(FILE*)",off-1,siz); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image from a DLM file. + CImg& load_dlm(const char *const filename) { + return _load_dlm(0,filename); + } + + static CImg get_load_dlm(const char *const filename) { + return CImg().load_dlm(filename); + } + + //! Load an image from a DLM file. + CImg& load_dlm(cimg_std::FILE *const file) { + return _load_dlm(file,0); + } + + static CImg get_load_dlm(cimg_std::FILE *const file) { + return CImg().load_dlm(file); + } + + CImg& _load_dlm(cimg_std::FILE *const file, const char *const filename) { + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_dlm() : Cannot load (null) filename.", + pixel_type()); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); + assign(256,256); + char c, delimiter[256] = { 0 }, tmp[256]; + unsigned int cdx = 0, dx = 0, dy = 0; + int oerr = 0, err; + double val; + while ((err = cimg_std::fscanf(nfile,"%lf%255[^0-9.+-]",&val,delimiter))!=EOF) { + oerr = err; + if (err>0) (*this)(cdx++,dy) = (T)val; + if (cdx>=width) resize(width+256,1,1,1,0); + c = 0; if (!cimg_std::sscanf(delimiter,"%255[^\n]%c",tmp,&c) || c=='\n') { + dx = cimg::max(cdx,dx); + ++dy; + if (dy>=height) resize(width,height+256,1,1,0); + cdx = 0; + } + } + if (cdx && oerr==1) { dx=cdx; ++dy; } + if (!dx || !dy) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_dlm() : File '%s', invalid DLM file, specified image dimensions are (%u,%u).", + pixel_type(),filename?filename:"(FILE*)",dx,dy); + } + resize(dx,dy,1,1,0); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image from a BMP file. + CImg& load_bmp(const char *const filename) { + return _load_bmp(0,filename); + } + + static CImg get_load_bmp(const char *const filename) { + return CImg().load_bmp(filename); + } + + //! Load an image from a BMP file. + CImg& load_bmp(cimg_std::FILE *const file) { + return _load_bmp(file,0); + } + + static CImg get_load_bmp(cimg_std::FILE *const file) { + return CImg().load_bmp(file); + } + + CImg& _load_bmp(cimg_std::FILE *const file, const char *const filename) { + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_bmp() : Cannot load (null) filename.", + pixel_type()); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + unsigned char header[64]; + cimg::fread(header,54,nfile); + if (header[0]!='B' || header[1]!='M') { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_bmp() : Invalid valid BMP file (filename '%s').", + pixel_type(),filename?filename:"(FILE*)"); + } + assign(); + + // Read header and pixel buffer + int + file_size = header[0x02] + (header[0x03]<<8) + (header[0x04]<<16) + (header[0x05]<<24), + offset = header[0x0A] + (header[0x0B]<<8) + (header[0x0C]<<16) + (header[0x0D]<<24), + dx = header[0x12] + (header[0x13]<<8) + (header[0x14]<<16) + (header[0x15]<<24), + dy = header[0x16] + (header[0x17]<<8) + (header[0x18]<<16) + (header[0x19]<<24), + compression = header[0x1E] + (header[0x1F]<<8) + (header[0x20]<<16) + (header[0x21]<<24), + nb_colors = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24), + bpp = header[0x1C] + (header[0x1D]<<8), + *palette = 0; + const int + dx_bytes = (bpp==1)?(dx/8+(dx%8?1:0)):((bpp==4)?(dx/2+(dx%2?1:0)):(dx*bpp/8)), + align = (4-dx_bytes%4)%4, + buf_size = cimg::min(cimg::abs(dy)*(dx_bytes+align),file_size-offset); + + if (bpp<16) { if (!nb_colors) nb_colors=1<0) cimg_std::fseek(nfile,xoffset,SEEK_CUR); + unsigned char *buffer = new unsigned char[buf_size], *ptrs = buffer; + cimg::fread(buffer,buf_size,nfile); + if (!file) cimg::fclose(nfile); + + // Decompress buffer (if necessary) + if (compression) { + delete[] buffer; + if (file) { + throw CImgIOException("CImg<%s>::load_bmp() : Not able to read a compressed BMP file using a *FILE input", + pixel_type()); + } else return load_other(filename); + } + + // Read pixel data + assign(dx,cimg::abs(dy),1,3); + switch (bpp) { + case 1 : { // Monochrome + for (int y=height-1; y>=0; --y) { + unsigned char mask = 0x80, val = 0; + cimg_forX(*this,x) { + if (mask==0x80) val = *(ptrs++); + const unsigned char *col = (unsigned char*)(palette+(val&mask?1:0)); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + mask = cimg::ror(mask); + } ptrs+=align; } + } break; + case 4 : { // 16 colors + for (int y=height-1; y>=0; --y) { + unsigned char mask = 0xF0, val = 0; + cimg_forX(*this,x) { + if (mask==0xF0) val = *(ptrs++); + const unsigned char color = (unsigned char)((mask<16)?(val&mask):((val&mask)>>4)); + unsigned char *col = (unsigned char*)(palette+color); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + mask = cimg::ror(mask,4); + } ptrs+=align; } + } break; + case 8 : { // 256 colors + for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) { + const unsigned char *col = (unsigned char*)(palette+*(ptrs++)); + (*this)(x,y,2) = (T)*(col++); + (*this)(x,y,1) = (T)*(col++); + (*this)(x,y,0) = (T)*(col++); + } ptrs+=align; } + } break; + case 16 : { // 16 bits colors + for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) { + const unsigned char c1 = *(ptrs++), c2 = *(ptrs++); + const unsigned short col = (unsigned short)(c1|(c2<<8)); + (*this)(x,y,2) = (T)(col&0x1F); + (*this)(x,y,1) = (T)((col>>5)&0x1F); + (*this)(x,y,0) = (T)((col>>10)&0x1F); + } ptrs+=align; } + } break; + case 24 : { // 24 bits colors + for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) { + (*this)(x,y,2) = (T)*(ptrs++); + (*this)(x,y,1) = (T)*(ptrs++); + (*this)(x,y,0) = (T)*(ptrs++); + } ptrs+=align; } + } break; + case 32 : { // 32 bits colors + for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) { + (*this)(x,y,2) = (T)*(ptrs++); + (*this)(x,y,1) = (T)*(ptrs++); + (*this)(x,y,0) = (T)*(ptrs++); + ++ptrs; + } ptrs+=align; } + } break; + } + if (palette) delete[] palette; + delete[] buffer; + if (dy<0) mirror('y'); + return *this; + } + + //! Load an image from a JPEG file. + CImg& load_jpeg(const char *const filename) { + return _load_jpeg(0,filename); + } + + static CImg get_load_jpeg(const char *const filename) { + return CImg().load_jpeg(filename); + } + + //! Load an image from a JPEG file. + CImg& load_jpeg(cimg_std::FILE *const file) { + return _load_jpeg(file,0); + } + + static CImg get_load_jpeg(cimg_std::FILE *const file) { + return CImg().load_jpeg(file); + } + + CImg& _load_jpeg(cimg_std::FILE *const file, const char *const filename) { + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_jpeg() : Cannot load (null) filename.", + pixel_type()); +#ifndef cimg_use_jpeg + if (file) + throw CImgIOException("CImg<%s>::load_jpeg() : File '(FILE*)' cannot be read without using libjpeg.", + pixel_type()); + else return load_other(filename); +#else + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo,nfile); + jpeg_read_header(&cinfo,TRUE); + jpeg_start_decompress(&cinfo); + + if (cinfo.output_components!=1 && cinfo.output_components!=3 && cinfo.output_components!=4) { + cimg::warn("CImg<%s>::load_jpeg() : Don't know how to read image '%s' with libpeg, trying ImageMagick's convert", + pixel_type(),filename?filename:"(FILE*)"); + if (!file) return load_other(filename); + else { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_jpeg() : Cannot read JPEG image '%s' using a *FILE input.", + pixel_type(),filename?filename:"(FILE*)"); + } + } + + const unsigned int row_stride = cinfo.output_width * cinfo.output_components; + unsigned char *buf = new unsigned char[cinfo.output_width*cinfo.output_height*cinfo.output_components], *buf2 = buf; + JSAMPROW row_pointer[1]; + while (cinfo.output_scanline < cinfo.output_height) { + row_pointer[0] = &buf[cinfo.output_scanline*row_stride]; + jpeg_read_scanlines(&cinfo,row_pointer,1); + } + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + if (!file) cimg::fclose(nfile); + + assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components); + switch (dim) { + case 1 : { + T *ptr_g = data; + cimg_forXY(*this,x,y) *(ptr_g++) = (T)*(buf2++); + } break; + case 3 : { + T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2); + cimg_forXY(*this,x,y) { + *(ptr_r++) = (T)*(buf2++); + *(ptr_g++) = (T)*(buf2++); + *(ptr_b++) = (T)*(buf2++); + } + } break; + case 4 : { + T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), + *ptr_b = ptr(0,0,0,2), *ptr_a = ptr(0,0,0,3); + cimg_forXY(*this,x,y) { + *(ptr_r++) = (T)*(buf2++); + *(ptr_g++) = (T)*(buf2++); + *(ptr_b++) = (T)*(buf2++); + *(ptr_a++) = (T)*(buf2++); + } + } break; + } + delete[] buf; + return *this; +#endif + } + + //! Load an image from a file, using Magick++ library. + // Added April/may 2006 by Christoph Hormann + // This is experimental code, not much tested, use with care. + CImg& load_magick(const char *const filename) { + if (!filename) + throw CImgArgumentException("CImg<%s>::load_magick() : Cannot load (null) filename.", + pixel_type()); +#ifdef cimg_use_magick + Magick::Image image(filename); + const unsigned int W = image.size().width(), H = image.size().height(); + switch (image.type()) { + case Magick::PaletteMatteType : + case Magick::TrueColorMatteType : + case Magick::ColorSeparationType : { + assign(W,H,1,4); + T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2), *adata = ptr(0,0,0,3); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (unsigned int off = W*H; off; --off) { + *(rdata++) = (T)(pixels->red); + *(gdata++) = (T)(pixels->green); + *(bdata++) = (T)(pixels->blue); + *(adata++) = (T)(pixels->opacity); + ++pixels; + } + } break; + case Magick::PaletteType : + case Magick::TrueColorType : { + assign(W,H,1,3); + T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (unsigned int off = W*H; off; --off) { + *(rdata++) = (T)(pixels->red); + *(gdata++) = (T)(pixels->green); + *(bdata++) = (T)(pixels->blue); + ++pixels; + } + } break; + case Magick::GrayscaleMatteType : { + assign(W,H,1,2); + T *data = ptr(0,0,0,0), *adata = ptr(0,0,0,1); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (unsigned int off = W*H; off; --off) { + *(data++) = (T)(pixels->red); + *(adata++) = (T)(pixels->opacity); + ++pixels; + } + } break; + default : { + assign(W,H,1,1); + T *data = ptr(0,0,0,0); + Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); + for (unsigned int off = W*H; off; --off) { + *(data++) = (T)(pixels->red); + ++pixels; + } + } + } +#else + throw CImgIOException("CImg<%s>::load_magick() : File '%s', Magick++ library has not been linked.", + pixel_type(),filename); +#endif + return *this; + } + + static CImg get_load_magick(const char *const filename) { + return CImg().load_magick(filename); + } + + //! Load an image from a PNG file. + CImg& load_png(const char *const filename) { + return _load_png(0,filename); + } + + static CImg get_load_png(const char *const filename) { + return CImg().load_png(filename); + } + + //! Load an image from a PNG file. + CImg& load_png(cimg_std::FILE *const file) { + return _load_png(file,0); + } + + static CImg get_load_png(cimg_std::FILE *const file) { + return CImg().load_png(file); + } + + // (Note : Most of this function has been written by Eric Fausett) + CImg& _load_png(cimg_std::FILE *const file, const char *const filename) { + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_png() : Cannot load (null) filename.", + pixel_type()); +#ifndef cimg_use_png + if (file) + throw CImgIOException("CImg<%s>::load_png() : File '(FILE*)' cannot be read without using libpng.", + pixel_type()); + else return load_other(filename); +#else + // Open file and check for PNG validity + const char *volatile nfilename = filename; // two 'volatile' here to remove a g++ warning due to 'setjmp'. + cimg_std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"rb"); + + unsigned char pngCheck[8]; + cimg::fread(pngCheck,8,(cimg_std::FILE*)nfile); + if (png_sig_cmp(pngCheck,0,8)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_png() : File '%s' is not a valid PNG file.", + pixel_type(),nfilename?nfilename:"(FILE*)"); + } + + // Setup PNG structures for read + png_voidp user_error_ptr = 0; + png_error_ptr user_error_fn = 0, user_warning_fn = 0; + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,user_error_ptr,user_error_fn,user_warning_fn); + if (!png_ptr) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'png_ptr' data structure.", + pixel_type(),nfilename?nfilename:"(FILE*)"); + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,(png_infopp)0,(png_infopp)0); + throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'info_ptr' data structure.", + pixel_type(),nfilename?nfilename:"(FILE*)"); + } + png_infop end_info = png_create_info_struct(png_ptr); + if (!end_info) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr,&info_ptr,(png_infopp)0); + throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'end_info' data structure.", + pixel_type(),nfilename?nfilename:"(FILE*)"); + } + + // Error handling callback for png file reading + if (setjmp(png_jmpbuf(png_ptr))) { + if (!file) cimg::fclose((cimg_std::FILE*)nfile); + png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0); + throw CImgIOException("CImg<%s>::load_png() : File '%s', unknown fatal error.", + pixel_type(),nfilename?nfilename:"(FILE*)"); + } + png_init_io(png_ptr, nfile); + png_set_sig_bytes(png_ptr, 8); + + // Get PNG Header Info up to data block + png_read_info(png_ptr,info_ptr); + png_uint_32 W, H; + int bit_depth, color_type, interlace_type; + png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,int_p_NULL,int_p_NULL); + int new_bit_depth = bit_depth; + int new_color_type = color_type; + + // Transforms to unify image data + if (new_color_type == PNG_COLOR_TYPE_PALETTE){ + png_set_palette_to_rgb(png_ptr); + new_color_type -= PNG_COLOR_MASK_PALETTE; + new_bit_depth = 8; + } + if (new_color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8){ + png_set_expand_gray_1_2_4_to_8(png_ptr); + new_bit_depth = 8; + } + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png_ptr); + if (new_color_type == PNG_COLOR_TYPE_GRAY || new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA){ + png_set_gray_to_rgb(png_ptr); + new_color_type |= PNG_COLOR_MASK_COLOR; + } + if (new_color_type == PNG_COLOR_TYPE_RGB) + png_set_filler(png_ptr, 0xffffU, PNG_FILLER_AFTER); + png_read_update_info(png_ptr,info_ptr); + if (!(new_bit_depth==8 || new_bit_depth==16)) { + if (!file) cimg::fclose(nfile); + png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0); + throw CImgIOException("CImg<%s>::load_png() : File '%s', wrong bit coding (bit_depth=%u)", + pixel_type(),nfilename?nfilename:"(FILE*)",new_bit_depth); + } + const int byte_depth = new_bit_depth>>3; + + // Allocate Memory for Image Read + png_bytep *imgData = new png_bytep[H]; + for (unsigned int row = 0; row::load_png() : File '%s', wrong color coding (new_color_type=%u)", + pixel_type(),nfilename?nfilename:"(FILE*)",new_color_type); + } + const bool no_alpha_channel = (new_color_type==PNG_COLOR_TYPE_RGB); + assign(W,H,1,no_alpha_channel?3:4); + T *ptr1 = ptr(0,0,0,0), *ptr2 = ptr(0,0,0,1), *ptr3 = ptr(0,0,0,2), *ptr4 = ptr(0,0,0,3); + switch (new_bit_depth) { + case 8 : { + cimg_forY(*this,y){ + const unsigned char *ptrs = (unsigned char*)imgData[y]; + cimg_forX(*this,x){ + *(ptr1++) = (T)*(ptrs++); + *(ptr2++) = (T)*(ptrs++); + *(ptr3++) = (T)*(ptrs++); + if (no_alpha_channel) ++ptrs; else *(ptr4++) = (T)*(ptrs++); + } + } + } break; + case 16 : { + cimg_forY(*this,y){ + const unsigned short *ptrs = (unsigned short*)(imgData[y]); + if (!cimg::endianness()) cimg::invert_endianness(ptrs,4*width); + cimg_forX(*this,x){ + *(ptr1++) = (T)*(ptrs++); + *(ptr2++) = (T)*(ptrs++); + *(ptr3++) = (T)*(ptrs++); + if (no_alpha_channel) ++ptrs; else *(ptr4++) = (T)*(ptrs++); + } + } + } break; + } + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + + // Deallocate Image Read Memory + cimg_forY(*this,n) delete[] imgData[n]; + delete[] imgData; + if (!file) cimg::fclose(nfile); + return *this; +#endif + } + + //! Load an image from a PNM file. + CImg& load_pnm(const char *const filename) { + return _load_pnm(0,filename); + } + + static CImg get_load_pnm(const char *const filename) { + return CImg().load_pnm(filename); + } + + //! Load an image from a PNM file. + CImg& load_pnm(cimg_std::FILE *const file) { + return _load_pnm(file,0); + } + + static CImg get_load_pnm(cimg_std::FILE *const file) { + return CImg().load_pnm(file); + } + + CImg& _load_pnm(cimg_std::FILE *const file, const char *const filename) { + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_pnm() : Cannot load (null) filename.", + pixel_type()); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + unsigned int ppm_type, W, H, colormax = 255; + char item[1024] = { 0 }; + int err, rval, gval, bval; + const int cimg_iobuffer = 12*1024*1024; + while ((err=cimg_std::fscanf(nfile,"%1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) cimg_std::fgetc(nfile); + if (cimg_std::sscanf(item," P%u",&ppm_type)!=1) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_pnm() : File '%s', PNM header 'P?' not found.", + pixel_type(),filename?filename:"(FILE*)"); + } + while ((err=cimg_std::fscanf(nfile," %1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) cimg_std::fgetc(nfile); + if ((err=cimg_std::sscanf(item," %u %u %u",&W,&H,&colormax))<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_pnm() : File '%s', WIDTH and HEIGHT fields are not defined in PNM header.", + pixel_type(),filename?filename:"(FILE*)"); + } + if (err==2) { + while ((err=cimg_std::fscanf(nfile," %1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) cimg_std::fgetc(nfile); + if (cimg_std::sscanf(item,"%u",&colormax)!=1) + cimg::warn("CImg<%s>::load_pnm() : File '%s', COLORMAX field is not defined in PNM header.", + pixel_type(),filename?filename:"(FILE*)"); + } + cimg_std::fgetc(nfile); + assign(); + + switch (ppm_type) { + case 2 : { // Grey Ascii + assign(W,H,1,1); + T* rdata = data; + cimg_foroff(*this,off) { if (cimg_std::fscanf(nfile,"%d",&rval)>0) *(rdata++) = (T)rval; else break; } + } break; + case 3 : { // Color Ascii + assign(W,H,1,3); + T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2); + cimg_forXY(*this,x,y) { + if (cimg_std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval)==3) { *(rdata++) = (T)rval; *(gdata++) = (T)gval; *(bdata++) = (T)bval; } + else break; + } + } break; + case 5 : { // Grey Binary + if (colormax<256) { // 8 bits + CImg raw; + assign(W,H,1,1); + T *ptrd = ptr(0,0,0,0); + for (int toread = (int)size(); toread>0; ) { + raw.assign(cimg::min(toread,cimg_iobuffer)); + cimg::fread(raw.data,raw.width,nfile); + toread-=raw.width; + const unsigned char *ptrs = raw.data; + for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } else { // 16 bits + CImg raw; + assign(W,H,1,1); + T *ptrd = ptr(0,0,0,0); + for (int toread = (int)size(); toread>0; ) { + raw.assign(cimg::min(toread,cimg_iobuffer/2)); + cimg::fread(raw.data,raw.width,nfile); + if (!cimg::endianness()) cimg::invert_endianness(raw.data,raw.width); + toread-=raw.width; + const unsigned short *ptrs = raw.data; + for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++); + } + } + } break; + case 6 : { // Color Binary + if (colormax<256) { // 8 bits + CImg raw; + assign(W,H,1,3); + T + *ptr_r = ptr(0,0,0,0), + *ptr_g = ptr(0,0,0,1), + *ptr_b = ptr(0,0,0,2); + for (int toread = (int)size(); toread>0; ) { + raw.assign(cimg::min(toread,cimg_iobuffer)); + cimg::fread(raw.data,raw.width,nfile); + toread-=raw.width; + const unsigned char *ptrs = raw.data; + for (unsigned int off = raw.width/3; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } else { // 16 bits + CImg raw; + assign(W,H,1,3); + T + *ptr_r = ptr(0,0,0,0), + *ptr_g = ptr(0,0,0,1), + *ptr_b = ptr(0,0,0,2); + for (int toread = (int)size(); toread>0; ) { + raw.assign(cimg::min(toread,cimg_iobuffer/2)); + cimg::fread(raw.data,raw.width,nfile); + if (!cimg::endianness()) cimg::invert_endianness(raw.data,raw.width); + toread-=raw.width; + const unsigned short *ptrs = raw.data; + for (unsigned int off = raw.width/3; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_pnm() : File '%s', PPM type 'P%d' not supported.", + pixel_type(),filename?filename:"(FILE*)",ppm_type); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image from a RGB file. + CImg& load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgb(0,filename,dimw,dimh); + } + + static CImg get_load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgb(filename,dimw,dimh); + } + + //! Load an image from a RGB file. + CImg& load_rgb(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgb(file,0,dimw,dimh); + } + + static CImg get_load_rgb(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgb(file,dimw,dimh); + } + + CImg& _load_rgb(cimg_std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) { + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_rgb() : Cannot load (null) filename.", + pixel_type()); + if (!dimw || !dimh) return assign(); + const int cimg_iobuffer = 12*1024*1024; + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg raw; + assign(dimw,dimh,1,3); + T + *ptr_r = ptr(0,0,0,0), + *ptr_g = ptr(0,0,0,1), + *ptr_b = ptr(0,0,0,2); + for (int toread = (int)size(); toread>0; ) { + raw.assign(cimg::min(toread,cimg_iobuffer)); + cimg::fread(raw.data,raw.width,nfile); + toread-=raw.width; + const unsigned char *ptrs = raw.data; + for (unsigned int off = raw.width/3; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image from a RGBA file. + CImg& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgba(0,filename,dimw,dimh); + } + + static CImg get_load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgba(filename,dimw,dimh); + } + + //! Load an image from a RGBA file. + CImg& load_rgba(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return _load_rgba(file,0,dimw,dimh); + } + + static CImg get_load_rgba(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { + return CImg().load_rgba(file,dimw,dimh); + } + + CImg& _load_rgba(cimg_std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) { + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_rgba() : Cannot load (null) filename.", + pixel_type()); + if (!dimw || !dimh) return assign(); + const int cimg_iobuffer = 12*1024*1024; + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + CImg raw; + assign(dimw,dimh,1,4); + T + *ptr_r = ptr(0,0,0,0), + *ptr_g = ptr(0,0,0,1), + *ptr_b = ptr(0,0,0,2), + *ptr_a = ptr(0,0,0,3); + for (int toread = (int)size(); toread>0; ) { + raw.assign(cimg::min(toread,cimg_iobuffer)); + cimg::fread(raw.data,raw.width,nfile); + toread-=raw.width; + const unsigned char *ptrs = raw.data; + for (unsigned int off = raw.width/4; off; --off) { + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + *(ptr_a++) = (T)*(ptrs++); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image from a TIFF file. + CImg& load_tiff(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1) { + if (!filename) + throw CImgArgumentException("CImg<%s>::load_tiff() : Cannot load (null) filename.", + pixel_type()); + const unsigned int + nfirst_frame = first_frame1) + throw CImgArgumentException("CImg<%s>::load_tiff() : File '%s', reading sub-images from a tiff file requires the use of libtiff.\n" + "('cimg_use_tiff' must be defined).", + pixel_type(),filename); + return load_other(filename); +#else + TIFF *tif = TIFFOpen(filename,"r"); + if (tif) { + unsigned int nb_images = 0; + do ++nb_images; while (TIFFReadDirectory(tif)); + if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images)) + cimg::warn("CImg<%s>::load_tiff() : File '%s' contains %u image(s), specified frame range is [%u,%u] (step %u).", + pixel_type(),filename,nb_images,nfirst_frame,nlast_frame,nstep_frame); + if (nfirst_frame>=nb_images) return assign(); + if (nlast_frame>=nb_images) nlast_frame = nb_images-1; + TIFFSetDirectory(tif,0); + CImg frame; + for (unsigned int l = nfirst_frame; l<=nlast_frame; l+=nstep_frame) { + frame._load_tiff(tif,l); + if (l==nfirst_frame) assign(frame.width,frame.height,1+(nlast_frame-nfirst_frame)/nstep_frame,frame.dim); + if (frame.width>width || frame.height>height || frame.dim>dim) + resize(cimg::max(frame.width,width),cimg::max(frame.height,height),-100,cimg::max(frame.dim,dim),0); + draw_image(0,0,(l-nfirst_frame)/nstep_frame,frame); + } + TIFFClose(tif); + } else throw CImgException("CImg<%s>::load_tiff() : File '%s' cannot be opened.", + pixel_type(),filename); + return *this; +#endif + } + + static CImg get_load_tiff(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1) { + return CImg().load_tiff(filename,first_frame,last_frame,step_frame); + } + + // (Original contribution by Jerome Boulanger). +#ifdef cimg_use_tiff + CImg& _load_tiff(TIFF *tif, const unsigned int directory) { + if (!TIFFSetDirectory(tif,directory)) return assign(); + uint16 samplesperpixel, bitspersample; + uint32 nx,ny; + const char *const filename = TIFFFileName(tif); + TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx); + TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny); + TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel); + if (samplesperpixel!=1 && samplesperpixel!=3 && samplesperpixel!=4) { + cimg::warn("CImg<%s>::load_tiff() : File '%s', unknow value for tag : TIFFTAG_SAMPLESPERPIXEL, will force it to 1.", + pixel_type(),filename); + samplesperpixel = 1; + } + TIFFGetFieldDefaulted(tif,TIFFTAG_BITSPERSAMPLE,&bitspersample); + assign(nx,ny,1,samplesperpixel); + if (bitspersample!=8 || !(samplesperpixel==3 || samplesperpixel==4)) { + uint16 photo, config; + TIFFGetField(tif,TIFFTAG_PLANARCONFIG,&config); + TIFFGetField(tif,TIFFTAG_PHOTOMETRIC,&photo); + if (TIFFIsTiled(tif)) { + uint32 tw, th; + TIFFGetField(tif,TIFFTAG_TILEWIDTH,&tw); + TIFFGetField(tif,TIFFTAG_TILELENGTH,&th); + if (config==PLANARCONFIG_CONTIG) switch (bitspersample) { + case 8 : { + unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFTileSize(tif)); + if (buf) { + for (unsigned int row = 0; row::load_tiff() : File '%s', an error occure while reading a tile.", + pixel_type(),filename); + } else { + unsigned char *ptr = buf; + for (unsigned int rr = row; rr::load_tiff() : File '%s', an error occure while reading a tile.", + pixel_type(),filename); + } else { + unsigned short *ptr = buf; + for (unsigned int rr = row; rr::load_tiff() : File '%s', an error occure while reading a tile.", + pixel_type(),filename); + } else { + float *ptr = buf; + for (unsigned int rr = row; rr::load_tiff() : File '%s', an error occure while reading a tile.", + pixel_type(),filename); + } else { + unsigned char *ptr = buf; + for (unsigned int rr = row; rr::load_tiff() : File '%s', an error occure while reading a tile.", + pixel_type(),filename); + } else { + unsigned short *ptr = buf; + for (unsigned int rr = row; rr::load_tiff() : File '%s', an error occure while reading a tile.", + pixel_type(),filename); + } else { + float *ptr = buf; + for (unsigned int rr = row; rrny?ny-row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, 0); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a strip.", + pixel_type(),filename); + } + unsigned char *ptr = buf; + for (unsigned int rr = 0; rrny?ny-row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, 0); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.", + pixel_type(),filename); + } + unsigned short *ptr = buf; + for (unsigned int rr = 0; rrny?ny-row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, 0); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.", + pixel_type(),filename); + } + float *ptr = buf; + for (unsigned int rr = 0; rrny?ny-row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, vv); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a strip.", + pixel_type(),filename); + } + unsigned char *ptr = buf; + for (unsigned int rr = 0;rrny?ny-row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, vv); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.", + pixel_type(),filename); + } + unsigned short *ptr = buf; + for (unsigned int rr = 0; rrny?ny-row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif, row, vv); + if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { + _TIFFfree(buf); TIFFClose(tif); + throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.", + pixel_type(),filename); + } + float *ptr = buf; + for (unsigned int rr = 0; rr::load_tiff() : File '%s', not enough memory for buffer allocation.", + pixel_type(),filename); + } + TIFFReadRGBAImage(tif,nx,ny,raster,0); + switch (samplesperpixel) { + case 1 : { + cimg_forXY(*this,x,y) (*this)(x,y) = (T)(float)((raster[nx*(ny-1-y)+x]+ 128) / 257); + } break; + case 3 : { + cimg_forXY(*this,x,y) { + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny-1-y)+x]); + (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny-1-y)+x]); + (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny-1-y)+x]); + } + } break; + case 4 : { + cimg_forXY(*this,x,y) { + (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny-1-y)+x]); + (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny-1-y)+x]); + (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny-1-y)+x]); + (*this)(x,y,3) = (T)(float)TIFFGetA(raster[nx*(ny-1-y)+x]); + } + } break; + } + _TIFFfree(raster); + } + return *this; + } +#endif + + //! Load an image from an ANALYZE7.5/NIFTI file. + CImg& load_analyze(const char *const filename, float *const voxsize=0) { + return _load_analyze(0,filename,voxsize); + } + + static CImg get_load_analyze(const char *const filename, float *const voxsize=0) { + return CImg().load_analyze(filename,voxsize); + } + + //! Load an image from an ANALYZE7.5/NIFTI file. + CImg& load_analyze(cimg_std::FILE *const file, float *const voxsize=0) { + return _load_analyze(file,0,voxsize); + } + + static CImg get_load_analyze(cimg_std::FILE *const file, float *const voxsize=0) { + return CImg().load_analyze(file,voxsize); + } + + CImg& _load_analyze(cimg_std::FILE *const file, const char *const filename, float *const voxsize=0) { + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_analyze() : Cannot load (null) filename.", + pixel_type()); + cimg_std::FILE *nfile_header = 0, *nfile = 0; + if (!file) { + char body[1024]; + const char *ext = cimg::split_filename(filename,body); + if (!cimg::strcasecmp(ext,"hdr")) { // File is an Analyze header file. + nfile_header = cimg::fopen(filename,"rb"); + cimg_std::sprintf(body+cimg::strlen(body),".img"); + nfile = cimg::fopen(body,"rb"); + } else if (!cimg::strcasecmp(ext,"img")) { // File is an Analyze data file. + nfile = cimg::fopen(filename,"rb"); + cimg_std::sprintf(body+cimg::strlen(body),".hdr"); + nfile_header = cimg::fopen(body,"rb"); + } else nfile_header = nfile = cimg::fopen(filename,"rb"); // File is a Niftii file. + } else nfile_header = nfile = file; // File is a Niftii file. + if (!nfile || !nfile_header) + throw CImgIOException("CImg<%s>::load_analyze() : File '%s', not recognized as an Analyze7.5 or NIFTI file.", + pixel_type(),filename?filename:"(FILE*)"); + + // Read header. + bool endian = false; + unsigned int header_size; + cimg::fread(&header_size,1,nfile_header); + if (!header_size) + throw CImgIOException("CImg<%s>::load_analyze() : File '%s', zero-sized header found.", + pixel_type(),filename?filename:"(FILE*)"); + if (header_size>=4096) { endian = true; cimg::invert_endianness(header_size); } + unsigned char *header = new unsigned char[header_size]; + cimg::fread(header+4,header_size-4,nfile_header); + if (!file && nfile_header!=nfile) cimg::fclose(nfile_header); + if (endian) { + cimg::invert_endianness((short*)(header+40),5); + cimg::invert_endianness((short*)(header+70),1); + cimg::invert_endianness((short*)(header+72),1); + cimg::invert_endianness((float*)(header+76),4); + cimg::invert_endianness((float*)(header+112),1); + } + unsigned short *dim = (unsigned short*)(header+40), dimx = 1, dimy = 1, dimz = 1, dimv = 1; + if (!dim[0]) + cimg::warn("CImg<%s>::load_analyze() : File '%s', tells that image has zero dimensions.", + pixel_type(),filename?filename:"(FILE*)"); + if (dim[0]>4) + cimg::warn("CImg<%s>::load_analyze() : File '%s', number of image dimension is %u, reading only the 4 first dimensions", + pixel_type(),filename?filename:"(FILE*)",dim[0]); + if (dim[0]>=1) dimx = dim[1]; + if (dim[0]>=2) dimy = dim[2]; + if (dim[0]>=3) dimz = dim[3]; + if (dim[0]>=4) dimv = dim[4]; + float scalefactor = *(float*)(header+112); if (scalefactor==0) scalefactor=1; + const unsigned short datatype = *(short*)(header+70); + if (voxsize) { + const float *vsize = (float*)(header+76); + voxsize[0] = vsize[1]; voxsize[1] = vsize[2]; voxsize[2] = vsize[3]; + } + delete[] header; + + // Read pixel data. + assign(dimx,dimy,dimz,dimv); + switch (datatype) { + case 2 : { + unsigned char *buffer = new unsigned char[dimx*dimy*dimz*dimv]; + cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile); + cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 4 : { + short *buffer = new short[dimx*dimy*dimz*dimv]; + cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile); + if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv); + cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 8 : { + int *buffer = new int[dimx*dimy*dimz*dimv]; + cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile); + if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv); + cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 16 : { + float *buffer = new float[dimx*dimy*dimz*dimv]; + cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile); + if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv); + cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + case 64 : { + double *buffer = new double[dimx*dimy*dimz*dimv]; + cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile); + if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv); + cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor); + delete[] buffer; + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_analyze() : File '%s', cannot read images with 'datatype = %d'", + pixel_type(),filename?filename:"(FILE*)",datatype); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image (list) from a .cimg file. + CImg& load_cimg(const char *const filename, const char axis='z', const char align='p') { + CImgList list; + list.load_cimg(filename); + if (list.size==1) return list[0].transfer_to(*this); + return assign(list.get_append(axis,align)); + } + + static CImg get_load_cimg(const char *const filename, const char axis='z', const char align='p') { + return CImg().load_cimg(filename,axis,align); + } + + //! Load an image (list) from a .cimg file. + CImg& load_cimg(cimg_std::FILE *const file, const char axis='z', const char align='p') { + CImgList list; + list.load_cimg(file); + if (list.size==1) return list[0].transfer_to(*this); + return assign(list.get_append(axis,align)); + } + + static CImg get_load_cimg(cimg_std::FILE *const file, const char axis='z', const char align='p') { + return CImg().load_cimg(file,axis,align); + } + + //! Load a sub-image (list) from a .cimg file. + CImg& load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0, + const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1, + const char axis='z', const char align='p') { + CImgList list; + list.load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1); + if (list.size==1) return list[0].transfer_to(*this); + return assign(list.get_append(axis,align)); + } + + static CImg get_load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0, + const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1, + const char axis='z', const char align='p') { + return CImg().load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1,axis,align); + } + + //! Load a sub-image (list) from a non-compressed .cimg file. + CImg& load_cimg(cimg_std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0, + const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1, + const char axis='z', const char align='p') { + CImgList list; + list.load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1); + if (list.size==1) return list[0].transfer_to(*this); + return assign(list.get_append(axis,align)); + } + + static CImg get_load_cimg(cimg_std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0, + const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1, + const char axis='z', const char align='p') { + return CImg().load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1,axis,align); + } + + //! Load an image from an INRIMAGE-4 file. + CImg& load_inr(const char *const filename, float *const voxsize=0) { + return _load_inr(0,filename,voxsize); + } + + static CImg get_load_inr(const char *const filename, float *const voxsize=0) { + return CImg().load_inr(filename,voxsize); + } + + //! Load an image from an INRIMAGE-4 file. + CImg& load_inr(cimg_std::FILE *const file, float *const voxsize=0) { + return _load_inr(file,0,voxsize); + } + + static CImg get_load_inr(cimg_std::FILE *const file, float *voxsize=0) { + return CImg().load_inr(file,voxsize); + } + + // Load an image from an INRIMAGE-4 file (internal). + static void _load_inr_header(cimg_std::FILE *file, int out[8], float *const voxsize) { + char item[1024], tmp1[64], tmp2[64]; + out[0] = cimg_std::fscanf(file,"%63s",item); + out[0] = out[1] = out[2] = out[3] = out[5] = 1; out[4] = out[6] = out[7] = -1; + if(cimg::strncasecmp(item,"#INRIMAGE-4#{",13)!=0) + throw CImgIOException("CImg<%s>::load_inr() : File does not appear to be a valid INR file.\n" + "(INRIMAGE-4 identifier not found)", + pixel_type()); + while (cimg_std::fscanf(file," %63[^\n]%*c",item)!=EOF && cimg::strncmp(item,"##}",3)) { + cimg_std::sscanf(item," XDIM%*[^0-9]%d",out); + cimg_std::sscanf(item," YDIM%*[^0-9]%d",out+1); + cimg_std::sscanf(item," ZDIM%*[^0-9]%d",out+2); + cimg_std::sscanf(item," VDIM%*[^0-9]%d",out+3); + cimg_std::sscanf(item," PIXSIZE%*[^0-9]%d",out+6); + if (voxsize) { + cimg_std::sscanf(item," VX%*[^0-9.+-]%f",voxsize); + cimg_std::sscanf(item," VY%*[^0-9.+-]%f",voxsize+1); + cimg_std::sscanf(item," VZ%*[^0-9.+-]%f",voxsize+2); + } + if (cimg_std::sscanf(item," CPU%*[ =]%s",tmp1)) out[7]=cimg::strncasecmp(tmp1,"sun",3)?0:1; + switch (cimg_std::sscanf(item," TYPE%*[ =]%s %s",tmp1,tmp2)) { + case 0 : break; + case 2 : out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; cimg_std::strcpy(tmp1,tmp2); + case 1 : + if (!cimg::strncasecmp(tmp1,"int",3) || !cimg::strncasecmp(tmp1,"fixed",5)) out[4] = 0; + if (!cimg::strncasecmp(tmp1,"float",5) || !cimg::strncasecmp(tmp1,"double",6)) out[4] = 1; + if (!cimg::strncasecmp(tmp1,"packed",6)) out[4] = 2; + if (out[4]>=0) break; + default : + throw CImgIOException("cimg::inr_header_read() : Invalid TYPE '%s'",tmp2); + } + } + if(out[0]<0 || out[1]<0 || out[2]<0 || out[3]<0) + throw CImgIOException("CImg<%s>::load_inr() : Bad dimensions in .inr file = ( %d , %d , %d , %d )", + pixel_type(),out[0],out[1],out[2],out[3]); + if(out[4]<0 || out[5]<0) + throw CImgIOException("CImg<%s>::load_inr() : TYPE is not fully defined", + pixel_type()); + if(out[6]<0) + throw CImgIOException("CImg<%s>::load_inr() : PIXSIZE is not fully defined", + pixel_type()); + if(out[7]<0) + throw CImgIOException("CImg<%s>::load_inr() : Big/Little Endian coding type is not defined", + pixel_type()); + } + + CImg& _load_inr(cimg_std::FILE *const file, const char *const filename, float *const voxsize) { +#define _cimg_load_inr_case(Tf,sign,pixsize,Ts) \ + if (!loaded && fopt[6]==pixsize && fopt[4]==Tf && fopt[5]==sign) { \ + Ts *xval, *val = new Ts[fopt[0]*fopt[3]]; \ + cimg_forYZ(*this,y,z) { \ + cimg::fread(val,fopt[0]*fopt[3],nfile); \ + if (fopt[7]!=endian) cimg::invert_endianness(val,fopt[0]*fopt[3]); \ + xval = val; cimg_forX(*this,x) cimg_forV(*this,k) (*this)(x,y,z,k) = (T)*(xval++); \ + } \ + delete[] val; \ + loaded = true; \ + } + + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_inr() : Cannot load (null) filename.", + pixel_type()); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + int fopt[8], endian=cimg::endianness()?1:0; + bool loaded = false; + if (voxsize) voxsize[0]=voxsize[1]=voxsize[2]=1; + _load_inr_header(nfile,fopt,voxsize); + assign(fopt[0],fopt[1],fopt[2],fopt[3]); + _cimg_load_inr_case(0,0,8, unsigned char); + _cimg_load_inr_case(0,1,8, char); + _cimg_load_inr_case(0,0,16,unsigned short); + _cimg_load_inr_case(0,1,16,short); + _cimg_load_inr_case(0,0,32,unsigned int); + _cimg_load_inr_case(0,1,32,int); + _cimg_load_inr_case(1,0,32,float); + _cimg_load_inr_case(1,1,32,float); + _cimg_load_inr_case(1,0,64,double); + _cimg_load_inr_case(1,1,64,double); + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_inr() : File '%s', cannot read images of the type specified in the file", + pixel_type(),filename?filename:"(FILE*)"); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image from a PANDORE file. + CImg& load_pandore(const char *const filename) { + return _load_pandore(0,filename); + } + + static CImg get_load_pandore(const char *const filename) { + return CImg().load_pandore(filename); + } + + //! Load an image from a PANDORE file. + CImg& load_pandore(cimg_std::FILE *const file) { + return _load_pandore(file,0); + } + + static CImg get_load_pandore(cimg_std::FILE *const file) { + return CImg().load_pandore(file); + } + + CImg& _load_pandore(cimg_std::FILE *const file, const char *const filename) { +#define __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,ndim,stype) \ + cimg::fread(dims,nbdim,nfile); \ + if (endian) cimg::invert_endianness(dims,nbdim); \ + assign(nwidth,nheight,ndepth,ndim); \ + const unsigned int siz = size(); \ + stype *buffer = new stype[siz]; \ + cimg::fread(buffer,siz,nfile); \ + if (endian) cimg::invert_endianness(buffer,siz); \ + T *ptrd = data; \ + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); \ + buffer-=siz; \ + delete[] buffer + +#define _cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1,stype2,stype3,ltype) { \ + if (sizeof(stype1)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1); } \ + else if (sizeof(stype2)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype2); } \ + else if (sizeof(stype3)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype3); } \ + else throw CImgIOException("CImg<%s>::load_pandore() : File '%s' cannot be read, datatype not supported on this architecture.", \ + pixel_type(),filename?filename:"(FILE*)"); } + + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_pandore() : Cannot load (null) filename.", + pixel_type()); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + typedef unsigned char uchar; + typedef unsigned short ushort; + typedef unsigned int uint; + typedef unsigned long ulong; + char header[32]; + cimg::fread(header,12,nfile); + if (cimg::strncasecmp("PANDORE",header,7)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_pandore() : File '%s' is not a valid PANDORE file, " + "(PANDORE identifier not found).", + pixel_type(),filename?filename:"(FILE*)"); + } + unsigned int imageid, dims[8]; + cimg::fread(&imageid,1,nfile); + const bool endian = (imageid>255); + if (endian) cimg::invert_endianness(imageid); + cimg::fread(header,20,nfile); + + switch (imageid) { + case 2: _cimg_load_pandore_case(2,dims[1],1,1,1,uchar,uchar,uchar,1); break; + case 3: _cimg_load_pandore_case(2,dims[1],1,1,1,long,int,short,4); break; + case 4: _cimg_load_pandore_case(2,dims[1],1,1,1,double,float,float,4); break; + case 5: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,uchar,uchar,uchar,1); break; + case 6: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,long,int,short,4); break; + case 7: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,double,float,float,4); break; + case 8: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,uchar,uchar,uchar,1); break; + case 9: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,long,int,short,4); break; + case 10: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,double,float,float,4); break; + case 11 : { // Region 1D + cimg::fread(dims,3,nfile); + if (endian) cimg::invert_endianness(dims,3); + assign(dims[1],1,1,1); + const unsigned siz = size(); + if (dims[2]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[2]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 12 : { // Region 2D + cimg::fread(dims,4,nfile); + if (endian) cimg::invert_endianness(dims,4); + assign(dims[2],dims[1],1,1); + const unsigned int siz = size(); + if (dims[3]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[3]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned long *buffer = new unsigned long[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 13 : { // Region 3D + cimg::fread(dims,5,nfile); + if (endian) cimg::invert_endianness(dims,5); + assign(dims[3],dims[2],dims[1],1); + const unsigned int siz = size(); + if (dims[4]<256) { + unsigned char *buffer = new unsigned char[siz]; + cimg::fread(buffer,siz,nfile); + T *ptrd = data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + if (dims[4]<65536) { + unsigned short *buffer = new unsigned short[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } else { + unsigned int *buffer = new unsigned int[siz]; + cimg::fread(buffer,siz,nfile); + if (endian) cimg::invert_endianness(buffer,siz); + T *ptrd = data; + cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); + buffer-=siz; + delete[] buffer; + } + } + } + break; + case 16: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,uchar,uchar,uchar,1); break; + case 17: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,long,int,short,4); break; + case 18: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,double,float,float,4); break; + case 19: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,uchar,uchar,uchar,1); break; + case 20: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,long,int,short,4); break; + case 21: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,double,float,float,4); break; + case 22: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],uchar,uchar,uchar,1); break; + case 23: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],long,int,short,4); + case 24: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],ulong,uint,ushort,4); break; + case 25: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],double,float,float,4); break; + case 26: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],uchar,uchar,uchar,1); break; + case 27: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],long,int,short,4); break; + case 28: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],ulong,uint,ushort,4); break; + case 29: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],double,float,float,4); break; + case 30: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],uchar,uchar,uchar,1); break; + case 31: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],long,int,short,4); break; + case 32: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],ulong,uint,ushort,4); break; + case 33: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],double,float,float,4); break; + case 34 : { // Points 1D + int ptbuf[4]; + cimg::fread(ptbuf,1,nfile); + if (endian) cimg::invert_endianness(ptbuf,1); + assign(1); (*this)(0) = (T)ptbuf[0]; + } break; + case 35 : { // Points 2D + int ptbuf[4]; + cimg::fread(ptbuf,2,nfile); + if (endian) cimg::invert_endianness(ptbuf,2); + assign(2); (*this)(0) = (T)ptbuf[1]; (*this)(1) = (T)ptbuf[0]; + } break; + case 36 : { // Points 3D + int ptbuf[4]; + cimg::fread(ptbuf,3,nfile); + if (endian) cimg::invert_endianness(ptbuf,3); + assign(3); (*this)(0) = (T)ptbuf[2]; (*this)(1) = (T)ptbuf[1]; (*this)(2) = (T)ptbuf[0]; + } break; + default : + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_pandore() : File '%s', cannot read images with ID_type = %u", + pixel_type(),filename?filename:"(FILE*)",imageid); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image from a PAR-REC (Philips) file. + CImg& load_parrec(const char *const filename, const char axis='v', const char align='p') { + CImgList list; + list.load_parrec(filename); + if (list.size==1) return list[0].transfer_to(*this); + return assign(list.get_append(axis,align)); + } + + static CImg get_load_parrec(const char *const filename, const char axis='v', const char align='p') { + return CImg().load_parrec(filename,axis,align); + } + + //! Load an image from a .RAW file. + CImg& load_raw(const char *const filename, + const unsigned int sizex, const unsigned int sizey=1, + const unsigned int sizez=1, const unsigned int sizev=1, + const bool multiplexed=false, const bool invert_endianness=false) { + return _load_raw(0,filename,sizex,sizey,sizez,sizev,multiplexed,invert_endianness); + } + + static CImg get_load_raw(const char *const filename, + const unsigned int sizex, const unsigned int sizey=1, + const unsigned int sizez=1, const unsigned int sizev=1, + const bool multiplexed=false, const bool invert_endianness=false) { + return CImg().load_raw(filename,sizex,sizey,sizez,sizev,multiplexed,invert_endianness); + } + + //! Load an image from a .RAW file. + CImg& load_raw(cimg_std::FILE *const file, + const unsigned int sizex, const unsigned int sizey=1, + const unsigned int sizez=1, const unsigned int sizev=1, + const bool multiplexed=false, const bool invert_endianness=false) { + return _load_raw(file,0,sizex,sizey,sizez,sizev,multiplexed,invert_endianness); + } + + static CImg get_load_raw(cimg_std::FILE *const file, + const unsigned int sizex, const unsigned int sizey=1, + const unsigned int sizez=1, const unsigned int sizev=1, + const bool multiplexed=false, const bool invert_endianness=false) { + return CImg().load_raw(file,sizex,sizey,sizez,sizev,multiplexed,invert_endianness); + } + + CImg& _load_raw(cimg_std::FILE *const file, const char *const filename, + const unsigned int sizex, const unsigned int sizey, + const unsigned int sizez, const unsigned int sizev, + const bool multiplexed, const bool invert_endianness) { + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_raw() : Cannot load (null) filename.", + pixel_type()); + assign(sizex,sizey,sizez,sizev,0); + const unsigned int siz = size(); + if (siz) { + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + if (!multiplexed) { + cimg::fread(data,siz,nfile); + if (invert_endianness) cimg::invert_endianness(data,siz); + } + else { + CImg buf(1,1,1,sizev); + cimg_forXYZ(*this,x,y,z) { + cimg::fread(buf.data,sizev,nfile); + if (invert_endianness) cimg::invert_endianness(buf.data,sizev); + set_vector_at(buf,x,y,z); } + } + if (!file) cimg::fclose(nfile); + } + return *this; + } + + //! Load a video sequence using FFMPEG av's libraries. + CImg& load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false, + const char axis='z', const char align='p') { + return get_load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume,axis,align).transfer_to(*this); + } + + static CImg get_load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false, + const char axis='z', const char align='p') { + return CImgList().load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume).get_append(axis,align); + } + + //! Load an image sequence from a YUV file. + CImg& load_yuv(const char *const filename, + const unsigned int sizex, const unsigned int sizey=1, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') { + return get_load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).transfer_to(*this); + } + + static CImg get_load_yuv(const char *const filename, + const unsigned int sizex, const unsigned int sizey=1, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') { + return CImgList().load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis,align); + } + + //! Load an image sequence from a YUV file. + CImg& load_yuv(cimg_std::FILE *const file, + const unsigned int sizex, const unsigned int sizey=1, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') { + return get_load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).transfer_to(*this); + } + + static CImg get_load_yuv(cimg_std::FILE *const file, + const unsigned int sizex, const unsigned int sizey=1, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') { + return CImgList().load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis,align); + } + + //! Load a 3D object from a .OFF file. + template + CImg& load_off(const char *const filename, CImgList& primitives, CImgList& colors, const bool invert_faces=false) { + return _load_off(0,filename,primitives,colors,invert_faces); + } + + template + static CImg get_load_off(const char *const filename, CImgList& primitives, CImgList& colors, + const bool invert_faces=false) { + return CImg().load_off(filename,primitives,colors,invert_faces); + } + + //! Load a 3D object from a .OFF file. + template + CImg& load_off(cimg_std::FILE *const file, CImgList& primitives, CImgList& colors, const bool invert_faces=false) { + return _load_off(file,0,primitives,colors,invert_faces); + } + + template + static CImg get_load_off(cimg_std::FILE *const file, CImgList& primitives, CImgList& colors, + const bool invert_faces=false) { + return CImg().load_off(file,primitives,colors,invert_faces); + } + + template + CImg& _load_off(cimg_std::FILE *const file, const char *const filename, + CImgList& primitives, CImgList& colors, const bool invert_faces) { + if (!filename && !file) + throw CImgArgumentException("CImg<%s>::load_off() : Cannot load (null) filename.", + pixel_type()); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); + unsigned int nb_points = 0, nb_primitives = 0, nb_read = 0; + char line[256] = { 0 }; + int err; + + // Skip comments, and read magic string OFF + do { err = cimg_std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#')); + if (cimg::strncasecmp(line,"OFF",3) && cimg::strncasecmp(line,"COFF",4)) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_off() : File '%s', keyword 'OFF' not found.", + pixel_type(),filename?filename:"(FILE*)"); + } + do { err = cimg_std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#')); + if ((err = cimg_std::sscanf(line,"%u%u%*[^\n] ",&nb_points,&nb_primitives))!=2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_off() : File '%s', invalid vertices/primitives numbers.", + pixel_type(),filename?filename:"(FILE*)"); + } + + // Read points data + assign(nb_points,3); + float X = 0, Y = 0, Z = 0; + cimg_forX(*this,l) { + do { err = cimg_std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#')); + if ((err = cimg_std::sscanf(line,"%f%f%f%*[^\n] ",&X,&Y,&Z))!=3) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::load_off() : File '%s', cannot read point %u/%u.\n", + pixel_type(),filename?filename:"(FILE*)",l+1,nb_points); + } + (*this)(l,0) = (T)X; (*this)(l,1) = (T)Y; (*this)(l,2) = (T)Z; + } + + // Read primitive data + primitives.assign(); + colors.assign(); + bool stopflag = false; + while (!stopflag) { + float c0 = 0.7f, c1 = 0.7f, c2 = 0.7f; + unsigned int prim = 0, i0 = 0, i1 = 0, i2 = 0, i3 = 0, i4 = 0, i5 = 0, i6 = 0, i7 = 0; + line[0]='\0'; + if ((err = cimg_std::fscanf(nfile,"%u",&prim))!=1) stopflag=true; + else { + ++nb_read; + switch (prim) { + case 1 : { + if ((err = cimg_std::fscanf(nfile,"%u%255[^\n] ",&i0,line))<2) { + cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.", + pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives); + err = cimg_std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2); + primitives.insert(CImg::vector(i0)); + colors.insert(CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + } + } break; + case 2 : { + if ((err = cimg_std::fscanf(nfile,"%u%u%255[^\n] ",&i0,&i1,line))<2) { + cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.", + pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives); + err = cimg_std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2); + primitives.insert(CImg::vector(i0,i1)); + colors.insert(CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + } + } break; + case 3 : { + if ((err = cimg_std::fscanf(nfile,"%u%u%u%255[^\n] ",&i0,&i1,&i2,line))<3) { + cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.", + pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives); + err = cimg_std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2); + if (invert_faces) primitives.insert(CImg::vector(i0,i1,i2)); + else primitives.insert(CImg::vector(i0,i2,i1)); + colors.insert(CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); + } + } break; + case 4 : { + if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,line))<4) { + cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.", + pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives); + err = cimg_std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2); + if (invert_faces) primitives.insert(CImg::vector(i0,i1,i2,i3)); + else primitives.insert(CImg::vector(i0,i3,i2,i1)); + colors.insert(CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255))); + } + } break; + case 5 : { + if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line))<5) { + cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.", + pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives); + err = cimg_std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2); + if (invert_faces) { + primitives.insert(CImg::vector(i0,i1,i2,i3)); + primitives.insert(CImg::vector(i0,i3,i4)); + } + else { + primitives.insert(CImg::vector(i0,i3,i2,i1)); + primitives.insert(CImg::vector(i0,i4,i3)); + } + colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255))); + ++nb_primitives; + } + } break; + case 6 : { + if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line))<6) { + cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.", + pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives); + err = cimg_std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2); + if (invert_faces) { + primitives.insert(CImg::vector(i0,i1,i2,i3)); + primitives.insert(CImg::vector(i0,i3,i4,i5)); + } + else { + primitives.insert(CImg::vector(i0,i3,i2,i1)); + primitives.insert(CImg::vector(i0,i5,i4,i3)); + } + colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255))); + ++nb_primitives; + } + } break; + case 7 : { + if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line))<7) { + cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.", + pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives); + err = cimg_std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2); + if (invert_faces) { + primitives.insert(CImg::vector(i0,i1,i3,i4)); + primitives.insert(CImg::vector(i0,i4,i5,i6)); + primitives.insert(CImg::vector(i1,i2,i3)); + } + else { + primitives.insert(CImg::vector(i0,i4,i3,i1)); + primitives.insert(CImg::vector(i0,i6,i5,i4)); + primitives.insert(CImg::vector(i3,i2,i1)); + } + colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255))); + ++(++nb_primitives); + } + } break; + case 8 : { + if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,&i7,line))<7) { + cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.", + pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives); + err = cimg_std::fscanf(nfile,"%*[^\n] "); + } else { + err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2); + if (invert_faces) { + primitives.insert(CImg::vector(i0,i1,i2,i3)); + primitives.insert(CImg::vector(i0,i3,i4,i5)); + primitives.insert(CImg::vector(i0,i5,i6,i7)); + } + else { + primitives.insert(CImg::vector(i0,i3,i2,i1)); + primitives.insert(CImg::vector(i0,i5,i4,i3)); + primitives.insert(CImg::vector(i0,i7,i6,i5)); + } + colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255))); + ++(++nb_primitives); + } + } break; + default : + cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u (%u vertices).", + pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives,prim); + err = cimg_std::fscanf(nfile,"%*[^\n] "); + } + } + } + if (!file) cimg::fclose(nfile); + if (primitives.size!=nb_primitives) + cimg::warn("CImg<%s>::load_off() : File '%s', read only %u primitives instead of %u as claimed in the header.", + pixel_type(),filename?filename:"(FILE*)",primitives.size,nb_primitives); + return *this; + } + + //! Load a video sequence using FFMPEG's external tool 'ffmpeg'. + CImg& load_ffmpeg_external(const char *const filename, const char axis='z', const char align='p') { + return get_load_ffmpeg_external(filename,axis,align).transfer_to(*this); + } + + static CImg get_load_ffmpeg_external(const char *const filename, const char axis='z', const char align='p') { + return CImgList().load_ffmpeg_external(filename).get_append(axis,align); + } + + //! Load an image using GraphicsMagick's external tool 'gm'. + CImg& load_graphicsmagick_external(const char *const filename) { + if (!filename) + throw CImgArgumentException("CImg<%s>::load_graphicsmagick_external() : Cannot load (null) filename.", + pixel_type()); + char command[1024], filetmp[512]; + cimg_std::FILE *file = 0; +#if cimg_OS==1 + cimg_std::sprintf(command,"%s convert \"%s\" ppm:-",cimg::graphicsmagick_path(),filename); + file = popen(command,"r"); + if (file) { load_pnm(file); pclose(file); return *this; } +#endif + do { + cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand()); + if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file); + } while (file); + cimg_std::sprintf(command,"%s convert \"%s\" %s",cimg::graphicsmagick_path(),filename,filetmp); + cimg::system(command,cimg::graphicsmagick_path()); + if (!(file = cimg_std::fopen(filetmp,"rb"))) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException("CImg<%s>::load_graphicsmagick_external() : Failed to open image '%s'.\n\n" + "Path of 'GraphicsMagick's gm' : \"%s\"\n" + "Path of temporary filename : \"%s\"", + pixel_type(),filename,cimg::graphicsmagick_path(),filetmp); + } else cimg::fclose(file); + load_pnm(filetmp); + cimg_std::remove(filetmp); + return *this; + } + + static CImg get_load_graphicsmagick_external(const char *const filename) { + return CImg().load_graphicsmagick_external(filename); + } + + //! Load a gzipped image file, using external tool 'gunzip'. + CImg& load_gzip_external(const char *const filename) { + if (!filename) + throw CImgIOException("CImg<%s>::load_gzip_external() : Cannot load (null) filename.", + pixel_type()); + char command[1024], filetmp[512], body[512]; + const char + *ext = cimg::split_filename(filename,body), + *ext2 = cimg::split_filename(body,0); + cimg_std::FILE *file = 0; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand(),ext2); + else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand()); + } else { + if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand(),ext); + else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand()); + } + if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file); + } while (file); + cimg_std::sprintf(command,"%s -c \"%s\" > %s",cimg::gunzip_path(),filename,filetmp); + cimg::system(command); + if (!(file = cimg_std::fopen(filetmp,"rb"))) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException("CImg<%s>::load_gzip_external() : File '%s' cannot be opened.", + pixel_type(),filename); + } else cimg::fclose(file); + load(filetmp); + cimg_std::remove(filetmp); + return *this; + } + + static CImg get_load_gzip_external(const char *const filename) { + return CImg().load_gzip_external(filename); + } + + //! Load an image using ImageMagick's external tool 'convert'. + CImg& load_imagemagick_external(const char *const filename) { + if (!filename) + throw CImgArgumentException("CImg<%s>::load_imagemagick_external() : Cannot load (null) filename.", + pixel_type()); + char command[1024], filetmp[512]; + cimg_std::FILE *file = 0; +#if cimg_OS==1 + cimg_std::sprintf(command,"%s \"%s\" ppm:-",cimg::imagemagick_path(),filename); + file = popen(command,"r"); + if (file) { load_pnm(file); pclose(file); return *this; } +#endif + do { + cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand()); + if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file); + } while (file); + cimg_std::sprintf(command,"%s \"%s\" %s",cimg::imagemagick_path(),filename,filetmp); + cimg::system(command,cimg::imagemagick_path()); + if (!(file = cimg_std::fopen(filetmp,"rb"))) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException("CImg<%s>::load_imagemagick_external() : Failed to open image '%s'.\n\n" + "Path of 'ImageMagick's convert' : \"%s\"\n" + "Path of temporary filename : \"%s\"", + pixel_type(),filename,cimg::imagemagick_path(),filetmp); + } else cimg::fclose(file); + load_pnm(filetmp); + cimg_std::remove(filetmp); + return *this; + } + + static CImg get_load_imagemagick_external(const char *const filename) { + return CImg().load_imagemagick_external(filename); + } + + //! Load a DICOM image file, using XMedcon's external tool 'medcon'. + CImg& load_medcon_external(const char *const filename) { + if (!filename) + throw CImgArgumentException("CImg<%s>::load_medcon_external() : Cannot load (null) filename.", + pixel_type()); + char command[1024], filetmp[512], body[512]; + cimg::fclose(cimg::fopen(filename,"r")); + cimg_std::FILE *file = 0; + do { + cimg_std::sprintf(filetmp,"%s.hdr",cimg::filenamerand()); + if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file); + } while (file); + cimg_std::sprintf(command,"%s -w -c anlz -o %s -f %s",cimg::medcon_path(),filetmp,filename); + cimg::system(command); + cimg::split_filename(filetmp,body); + cimg_std::sprintf(command,"m000-%s.hdr",body); + file = cimg_std::fopen(command,"rb"); + if (!file) { + throw CImgIOException("CImg<%s>::load_medcon_external() : Failed to open image '%s'.\n\n" + "Path of 'medcon' : \"%s\"\n" + "Path of temporary filename : \"%s\"", + pixel_type(),filename,cimg::medcon_path(),filetmp); + } else cimg::fclose(file); + load_analyze(command); + cimg_std::remove(command); + cimg_std::sprintf(command,"m000-%s.img",body); + cimg_std::remove(command); + return *this; + } + + static CImg get_load_medcon_external(const char *const filename) { + return CImg().load_medcon_external(filename); + } + + //! Load a RAW Color Camera image file, using external tool 'dcraw'. + CImg& load_dcraw_external(const char *const filename) { + if (!filename) + throw CImgArgumentException("CImg<%s>::load_dcraw_external() : Cannot load (null) filename.", + pixel_type()); + char command[1024], filetmp[512]; + cimg_std::FILE *file = 0; +#if cimg_OS==1 + cimg_std::sprintf(command,"%s -4 -c \"%s\"",cimg::dcraw_path(),filename); + file = popen(command,"r"); + if (file) { load_pnm(file); pclose(file); return *this; } +#endif + do { + cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand()); + if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file); + } while (file); + cimg_std::sprintf(command,"%s -4 -c \"%s\" > %s",cimg::dcraw_path(),filename,filetmp); + cimg::system(command,cimg::dcraw_path()); + if (!(file = cimg_std::fopen(filetmp,"rb"))) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException("CImg<%s>::load_dcraw_external() : Failed to open image '%s'.\n\n" + "Path of 'dcraw' : \"%s\"\n" + "Path of temporary filename : \"%s\"", + pixel_type(),filename,cimg::dcraw_path(),filetmp); + } else cimg::fclose(file); + load_pnm(filetmp); + cimg_std::remove(filetmp); + return *this; + } + + static CImg get_load_dcraw_external(const char *const filename) { + return CImg().load_dcraw_external(filename); + } + + //! Load an image using ImageMagick's or GraphicsMagick's executables. + CImg& load_other(const char *const filename) { + if (!filename) + throw CImgArgumentException("CImg<%s>::load_other() : Cannot load (null) filename.", + pixel_type()); + const unsigned int odebug = cimg::exception_mode(); + cimg::exception_mode() = 0; + try { load_magick(filename); } + catch (CImgException&) { + try { load_imagemagick_external(filename); } + catch (CImgException&) { + try { load_graphicsmagick_external(filename); } + catch (CImgException&) { + assign(); + } + } + } + cimg::exception_mode() = odebug; + if (is_empty()) + throw CImgIOException("CImg<%s>::load_other() : File '%s' cannot be opened.", + pixel_type(),filename); + return *this; + } + + static CImg get_load_other(const char *const filename) { + return CImg().load_other(filename); + } + + //@} + //--------------------------- + // + //! \name Image File Saving + //@{ + //--------------------------- + + //! Save the image as a file. + /** + The used file format is defined by the file extension in the filename \p filename. + Parameter \p number can be used to add a 6-digit number to the filename before saving. + **/ + const CImg& save(const char *const filename, const int number=-1) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",width,height,depth,dim,data); + if (!filename) + throw CImgArgumentException("CImg<%s>::save() : Instance image (%u,%u,%u,%u,%p) cannot be saved as a (null) filename.", + pixel_type(),width,height,depth,dim,data); + const char *ext = cimg::split_filename(filename); + char nfilename[1024]; + const char *const fn = (number>=0)?cimg::number_filename(filename,number,6,nfilename):filename; +#ifdef cimg_save_plugin + cimg_save_plugin(fn); +#endif +#ifdef cimg_save_plugin1 + cimg_save_plugin1(fn); +#endif +#ifdef cimg_save_plugin2 + cimg_save_plugin2(fn); +#endif +#ifdef cimg_save_plugin3 + cimg_save_plugin3(fn); +#endif +#ifdef cimg_save_plugin4 + cimg_save_plugin4(fn); +#endif +#ifdef cimg_save_plugin5 + cimg_save_plugin5(fn); +#endif +#ifdef cimg_save_plugin6 + cimg_save_plugin6(fn); +#endif +#ifdef cimg_save_plugin7 + cimg_save_plugin7(fn); +#endif +#ifdef cimg_save_plugin8 + cimg_save_plugin8(fn); +#endif + // ASCII formats + if (!cimg::strcasecmp(ext,"asc")) return save_ascii(fn); + if (!cimg::strcasecmp(ext,"dlm") || + !cimg::strcasecmp(ext,"txt")) return save_dlm(fn); + if (!cimg::strcasecmp(ext,"cpp") || + !cimg::strcasecmp(ext,"hpp") || + !cimg::strcasecmp(ext,"h") || + !cimg::strcasecmp(ext,"c")) return save_cpp(fn); + + // 2D binary formats + if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn); + if (!cimg::strcasecmp(ext,"jpg") || + !cimg::strcasecmp(ext,"jpeg") || + !cimg::strcasecmp(ext,"jpe") || + !cimg::strcasecmp(ext,"jfif") || + !cimg::strcasecmp(ext,"jif")) return save_jpeg(fn); + if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn); + if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn); + if (!cimg::strcasecmp(ext,"png")) return save_png(fn); + if (!cimg::strcasecmp(ext,"pgm") || + !cimg::strcasecmp(ext,"ppm") || + !cimg::strcasecmp(ext,"pnm")) return save_pnm(fn); + if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn); + + // 3D binary formats + if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); + if (!cimg::strcasecmp(ext,"cimg") || ext[0]=='\0') return save_cimg(fn,false); + if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn); + if (!cimg::strcasecmp(ext,"hdr") || + !cimg::strcasecmp(ext,"nii")) return save_analyze(fn); + if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn); + if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn); + if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn); + + // Archive files + if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn); + + // Image sequences + if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true); + if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return save_ffmpeg(fn); + return save_other(fn); + } + + // Save the image as an ASCII file (ASCII Raw + simple header) (internal). + const CImg& _save_ascii(cimg_std::FILE *const file, const char *const filename) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_ascii() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_ascii() : Instance image (%u,%u,%u,%u,%p), specified file is (null).", + pixel_type(),width,height,depth,dim,data); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + cimg_std::fprintf(nfile,"%u %u %u %u\n",width,height,depth,dim); + const T* ptrs = data; + cimg_forYZV(*this,y,z,v) { + cimg_forX(*this,x) cimg_std::fprintf(nfile,"%g ",(double)*(ptrs++)); + cimg_std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save the image as an ASCII file (ASCII Raw + simple header). + const CImg& save_ascii(const char *const filename) const { + return _save_ascii(0,filename); + } + + //! Save the image as an ASCII file (ASCII Raw + simple header). + const CImg& save_ascii(cimg_std::FILE *const file) const { + return _save_ascii(file,0); + } + + // Save the image as a C or CPP source file (internal). + const CImg& _save_cpp(cimg_std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_cpp() : Instance image (%u,%u,%u,%u,%p), specified file is (null).", + pixel_type(),width,height,depth,dim,data); + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_cpp() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + char varname[1024] = { 0 }; + if (filename) cimg_std::sscanf(cimg::basename(filename),"%1023[a-zA-Z0-9_]",varname); + if (varname[0]=='\0') cimg_std::sprintf(varname,"unnamed"); + cimg_std::fprintf(nfile, + "/* Define image '%s' of size %ux%ux%ux%u and type '%s' */\n" + "%s data_%s[] = { \n ", + varname,width,height,depth,dim,pixel_type(),pixel_type(),varname); + for (unsigned long off = 0, siz = size()-1; off<=siz; ++off) { + cimg_std::fprintf(nfile,cimg::type::format(),cimg::type::format((*this)[off])); + if (off==siz) cimg_std::fprintf(nfile," };\n"); + else if (!((off+1)%16)) cimg_std::fprintf(nfile,",\n "); + else cimg_std::fprintf(nfile,", "); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save the image as a CPP source file. + const CImg& save_cpp(const char *const filename) const { + return _save_cpp(0,filename); + } + + //! Save the image as a CPP source file. + const CImg& save_cpp(cimg_std::FILE *const file) const { + return _save_cpp(file,0); + } + + // Save the image as a DLM file (internal). + const CImg& _save_dlm(cimg_std::FILE *const file, const char *const filename) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_dlm() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_dlm() : Instance image (%u,%u,%u,%u,%p), specified file is (null).", + pixel_type(),width,height,depth,dim,data); + if (depth>1) + cimg::warn("CImg<%s>::save_dlm() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Pixel values along Z will be unrolled.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (dim>1) + cimg::warn("CImg<%s>::save_dlm() : File '%s', instance image (%u,%u,%u,%u,%p) is multispectral. " + "Pixel values along V will be unrolled.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + const T* ptrs = data; + cimg_forYZV(*this,y,z,v) { + cimg_forX(*this,x) cimg_std::fprintf(nfile,"%g%s",(double)*(ptrs++),(x==dimx()-1)?"":","); + cimg_std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save the image as a DLM file. + const CImg& save_dlm(const char *const filename) const { + return _save_dlm(0,filename); + } + + //! Save the image as a DLM file. + const CImg& save_dlm(cimg_std::FILE *const file) const { + return _save_dlm(file,0); + } + + // Save the image as a BMP file (internal). + const CImg& _save_bmp(cimg_std::FILE *const file, const char *const filename) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_bmp() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_bmp() : Instance image (%u,%u,%u,%u,%p), specified file is (null).", + pixel_type(),width,height,depth,dim,data); + if (depth>1) + cimg::warn("CImg<%s>::save_bmp() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (dim>3) + cimg::warn("CImg<%s>::save_bmp() : File '%s', instance image (%u,%u,%u,%u,%p) is multispectral. Only the three first channels will be saved.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + unsigned char header[54] = { 0 }, align_buf[4] = { 0 }; + const unsigned int + align = (4 - (3*width)%4)%4, + buf_size = (3*width+align)*dimy(), + file_size = 54 + buf_size; + header[0] = 'B'; header[1] = 'M'; + header[0x02] = file_size&0xFF; + header[0x03] = (file_size>>8)&0xFF; + header[0x04] = (file_size>>16)&0xFF; + header[0x05] = (file_size>>24)&0xFF; + header[0x0A] = 0x36; + header[0x0E] = 0x28; + header[0x12] = width&0xFF; + header[0x13] = (width>>8)&0xFF; + header[0x14] = (width>>16)&0xFF; + header[0x15] = (width>>24)&0xFF; + header[0x16] = height&0xFF; + header[0x17] = (height>>8)&0xFF; + header[0x18] = (height>>16)&0xFF; + header[0x19] = (height>>24)&0xFF; + header[0x1A] = 1; + header[0x1B] = 0; + header[0x1C] = 24; + header[0x1D] = 0; + header[0x22] = buf_size&0xFF; + header[0x23] = (buf_size>>8)&0xFF; + header[0x24] = (buf_size>>16)&0xFF; + header[0x25] = (buf_size>>24)&0xFF; + header[0x27] = 0x1; + header[0x2B] = 0x1; + cimg::fwrite(header,54,nfile); + + const T + *pR = ptr(0,height-1,0,0), + *pG = (dim>=2)?ptr(0,height-1,0,1):0, + *pB = (dim>=3)?ptr(0,height-1,0,2):0; + + switch (dim) { + case 1 : { + cimg_forY(*this,y) { cimg_forX(*this,x) { + const unsigned char val = (unsigned char)*(pR++); + cimg_std::fputc(val,nfile); cimg_std::fputc(val,nfile); cimg_std::fputc(val,nfile); + } + cimg::fwrite(align_buf,align,nfile); + pR-=2*width; + }} break; + case 2 : { + cimg_forY(*this,y) { cimg_forX(*this,x) { + cimg_std::fputc(0,nfile); + cimg_std::fputc((unsigned char)(*(pG++)),nfile); + cimg_std::fputc((unsigned char)(*(pR++)),nfile); + } + cimg::fwrite(align_buf,align,nfile); + pR-=2*width; pG-=2*width; + }} break; + default : { + cimg_forY(*this,y) { cimg_forX(*this,x) { + cimg_std::fputc((unsigned char)(*(pB++)),nfile); + cimg_std::fputc((unsigned char)(*(pG++)),nfile); + cimg_std::fputc((unsigned char)(*(pR++)),nfile); + } + cimg::fwrite(align_buf,align,nfile); + pR-=2*width; pG-=2*width; pB-=2*width; + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save the image as a BMP file. + const CImg& save_bmp(const char *const filename) const { + return _save_bmp(0,filename); + } + + //! Save the image as a BMP file. + const CImg& save_bmp(cimg_std::FILE *const file) const { + return _save_bmp(file,0); + } + + // Save a file in JPEG format (internal). + const CImg& _save_jpeg(cimg_std::FILE *const file, const char *const filename, const unsigned int quality) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_jpeg() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_jpeg() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).", + pixel_type(),width,height,depth,dim,data); + if (depth>1) + cimg::warn("CImg<%s>::save_jpeg() : File '%s, instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); +#ifndef cimg_use_jpeg + if (!file) return save_other(filename,quality); + else throw CImgIOException("CImg<%s>::save_jpeg() : Cannot save a JPEG image in a *FILE output. Use libjpeg instead.", + pixel_type()); +#else + // Fill pixel buffer + unsigned char *buf; + unsigned int dimbuf = 0; + J_COLOR_SPACE colortype = JCS_RGB; + switch (dim) { + case 1 : { // Greyscale images + unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=1)]; + colortype = JCS_GRAYSCALE; + const T *ptr_g = data; + cimg_forXY(*this,x,y) *(buf2++) = (unsigned char)*(ptr_g++); + } break; + case 2 : { // RG images + unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=3)]; + const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1); + colortype = JCS_RGB; + cimg_forXY(*this,x,y) { + *(buf2++) = (unsigned char)*(ptr_r++); + *(buf2++) = (unsigned char)*(ptr_g++); + *(buf2++) = 0; + } + } break; + case 3 : { // RGB images + unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=3)]; + const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2); + colortype = JCS_RGB; + cimg_forXY(*this,x,y) { + *(buf2++) = (unsigned char)*(ptr_r++); + *(buf2++) = (unsigned char)*(ptr_g++); + *(buf2++) = (unsigned char)*(ptr_b++); + } + } break; + default : { // CMYK images + unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=4)]; + const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2), *ptr_a = ptr(0,0,0,3); + colortype = JCS_CMYK; + cimg_forXY(*this,x,y) { + *(buf2++) = (unsigned char)*(ptr_r++); + *(buf2++) = (unsigned char)*(ptr_g++); + *(buf2++) = (unsigned char)*(ptr_b++); + *(buf2++) = (unsigned char)*(ptr_a++); + } + } + } + + // Call libjpeg functions + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + jpeg_stdio_dest(&cinfo,nfile); + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.input_components = dimbuf; + cinfo.in_color_space = colortype; + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo,quality<100?quality:100,TRUE); + jpeg_start_compress(&cinfo,TRUE); + + const unsigned int row_stride = width*dimbuf; + JSAMPROW row_pointer[1]; + while (cinfo.next_scanline < cinfo.image_height) { + row_pointer[0] = &buf[cinfo.next_scanline*row_stride]; + jpeg_write_scanlines(&cinfo,row_pointer,1); + } + jpeg_finish_compress(&cinfo); + + delete[] buf; + if (!file) cimg::fclose(nfile); + jpeg_destroy_compress(&cinfo); + return *this; +#endif + } + + //! Save a file in JPEG format. + const CImg& save_jpeg(const char *const filename, const unsigned int quality=100) const { + return _save_jpeg(0,filename,quality); + } + + //! Save a file in JPEG format. + const CImg& save_jpeg(cimg_std::FILE *const file, const unsigned int quality=100) const { + return _save_jpeg(file,0,quality); + } + + //! Save the image using built-in ImageMagick++ library. + const CImg& save_magick(const char *const filename) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_magick() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",width,height,depth,dim,data); + if (!filename) + throw CImgArgumentException("CImg<%s>::save_magick() : Instance image (%u,%u,%u,%u,%p), specified file is (null).", + pixel_type(),width,height,depth,dim,data); +#ifdef cimg_use_magick + Magick::Image image(Magick::Geometry(width,height),"black"); + image.type(Magick::TrueColorType); + const T + *rdata = ptr(0,0,0,0), + *gdata = dim>1?ptr(0,0,0,1):0, + *bdata = dim>2?ptr(0,0,0,2):0; + Magick::PixelPacket *pixels = image.getPixels(0,0,width,height); + switch (dim) { + case 1 : // Scalar images + for (unsigned int off = width*height; off; --off) { + pixels->red = pixels->green = pixels->blue = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0); + ++pixels; + } + break; + case 2 : // RG images + for (unsigned int off = width*height; off; --off) { + pixels->red = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0); + pixels->green = Magick::Color::scaleDoubleToQuantum(*(gdata++)/255.0); + pixels->blue = 0; + ++pixels; + } + break; + default : // RGB images + for (unsigned int off = width*height; off; --off) { + pixels->red = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0); + pixels->green = Magick::Color::scaleDoubleToQuantum(*(gdata++)/255.0); + pixels->blue = Magick::Color::scaleDoubleToQuantum(*(bdata++)/255.0); + ++pixels; + } + } + image.syncPixels(); + image.write(filename); +#else + throw CImgIOException("CImg<%s>::save_magick() : File '%s', Magick++ library has not been linked.", + pixel_type(),filename); +#endif + return *this; + } + + // Save an image to a PNG file (internal). + // Most of this function has been written by Eric Fausett + const CImg& _save_png(cimg_std::FILE *const file, const char *const filename) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_png() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (!filename) + throw CImgArgumentException("CImg<%s>::save_png() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).", + pixel_type(),width,height,depth,dim,data); + if (depth>1) + cimg::warn("CImg<%s>::save_png() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); +#ifndef cimg_use_png + if (!file) return save_other(filename); + else throw CImgIOException("CImg<%s>::save_png() : Cannot save a PNG image in a *FILE output. You must use 'libpng' to do this instead.", + pixel_type()); +#else + const char *volatile nfilename = filename; // two 'volatile' here to remove a g++ warning due to 'setjmp'. + cimg_std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb"); + + // Setup PNG structures for write + png_voidp user_error_ptr = 0; + png_error_ptr user_error_fn = 0, user_warning_fn = 0; + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,user_error_ptr, user_error_fn, user_warning_fn); + if(!png_ptr){ + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::save_png() : File '%s', error when initializing 'png_ptr' data structure.", + pixel_type(),nfilename?nfilename:"(FILE*)"); + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr,(png_infopp)0); + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::save_png() : File '%s', error when initializing 'info_ptr' data structure.", + pixel_type(),nfilename?nfilename:"(FILE*)"); + } + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImg<%s>::save_png() : File '%s', unknown fatal error.", + pixel_type(),nfilename?nfilename:"(FILE*)"); + } + png_init_io(png_ptr, nfile); + png_uint_32 width = dimx(), height = dimy(); + float vmin, vmax = (float)maxmin(vmin); + const int bit_depth = (vmin<0 || vmax>=256)?16:8; + int color_type; + switch (dimv()) { + case 1 : color_type = PNG_COLOR_TYPE_GRAY; break; + case 2 : color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; + case 3 : color_type = PNG_COLOR_TYPE_RGB; break; + default : color_type = PNG_COLOR_TYPE_RGB_ALPHA; + } + const int interlace_type = PNG_INTERLACE_NONE; + const int compression_type = PNG_COMPRESSION_TYPE_DEFAULT; + const int filter_method = PNG_FILTER_TYPE_DEFAULT; + png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, interlace_type,compression_type, filter_method); + png_write_info(png_ptr, info_ptr); + const int byte_depth = bit_depth>>3; + const int numChan = dimv()>4?4:dimv(); + const int pixel_bit_depth_flag = numChan * (bit_depth-1); + + // Allocate Memory for Image Save and Fill pixel data + png_bytep *imgData = new png_byte*[height]; + for (unsigned int row = 0; row::save_png() : File '%s', unknown fatal error.", + pixel_type(),nfilename?nfilename:"(FILE*)"); + } + png_write_image(png_ptr, imgData); + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + // Deallocate Image Write Memory + cimg_forY(*this,n) delete[] imgData[n]; + delete[] imgData; + if (!file) cimg::fclose(nfile); + return *this; +#endif + } + + //! Save a file in PNG format + const CImg& save_png(const char *const filename) const { + return _save_png(0,filename); + } + + //! Save a file in PNG format + const CImg& save_png(cimg_std::FILE *const file) const { + return _save_png(file,0); + } + + // Save the image as a PNM file (internal function). + const CImg& _save_pnm(cimg_std::FILE *const file, const char *const filename) const { + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_pnm() : Instance image (%u,%u,%u,%u,%p), specified file is (null).", + pixel_type(),width,height,depth,dim,data); + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + double stmin, stmax = (double)maxmin(stmin); + if (depth>1) + cimg::warn("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (dim>3) + cimg::warn("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) is multispectral. Only the three first channels will be saved.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (stmin<0 || stmax>65535) + cimg::warn("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) has pixel values in [%g,%g]. Probable type overflow.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data,stmin,stmax); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const T + *ptrR = ptr(0,0,0,0), + *ptrG = (dim>=2)?ptr(0,0,0,1):0, + *ptrB = (dim>=3)?ptr(0,0,0,2):0; + const unsigned int buf_size = width*height*(dim==1?1:3); + + cimg_std::fprintf(nfile,"P%c\n# CREATOR: CImg Library (original size = %ux%ux%ux%u)\n%u %u\n%u\n", + (dim==1?'5':'6'),width,height,depth,dim,width,height,stmax<256?255:(stmax<4096?4095:65535)); + + switch (dim) { + case 1 : { // Scalar image + if (stmax<256) { // Binary PGM 8 bits + unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd; + cimg_forXY(*this,x,y) *(xptrd++) = (unsigned char)*(ptrR++); + cimg::fwrite(ptrd,buf_size,nfile); + delete[] ptrd; + } else { // Binary PGM 16 bits + unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd; + cimg_forXY(*this,x,y) *(xptrd++) = (unsigned short)*(ptrR++); + if (!cimg::endianness()) cimg::invert_endianness(ptrd,buf_size); + cimg::fwrite(ptrd,buf_size,nfile); + delete[] ptrd; + } + } break; + case 2 : { // RG image + if (stmax<256) { // Binary PPM 8 bits + unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd; + cimg_forXY(*this,x,y) { + *(xptrd++) = (unsigned char)*(ptrR++); + *(xptrd++) = (unsigned char)*(ptrG++); + *(xptrd++) = 0; + } + cimg::fwrite(ptrd,buf_size,nfile); + delete[] ptrd; + } else { // Binary PPM 16 bits + unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd; + cimg_forXY(*this,x,y) { + *(xptrd++) = (unsigned short)*(ptrR++); + *(xptrd++) = (unsigned short)*(ptrG++); + *(xptrd++) = 0; + } + if (!cimg::endianness()) cimg::invert_endianness(ptrd,buf_size); + cimg::fwrite(ptrd,buf_size,nfile); + delete[] ptrd; + } + } break; + default : { // RGB image + if (stmax<256) { // Binary PPM 8 bits + unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd; + cimg_forXY(*this,x,y) { + *(xptrd++) = (unsigned char)*(ptrR++); + *(xptrd++) = (unsigned char)*(ptrG++); + *(xptrd++) = (unsigned char)*(ptrB++); + } + cimg::fwrite(ptrd,buf_size,nfile); + delete[] ptrd; + } else { // Binary PPM 16 bits + unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd; + cimg_forXY(*this,x,y) { + *(xptrd++) = (unsigned short)*(ptrR++); + *(xptrd++) = (unsigned short)*(ptrG++); + *(xptrd++) = (unsigned short)*(ptrB++); + } + if (!cimg::endianness()) cimg::invert_endianness(ptrd,buf_size); + cimg::fwrite(ptrd,buf_size,nfile); + delete[] ptrd; + } + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save the image as a PNM file. + const CImg& save_pnm(const char *const filename) const { + return _save_pnm(0,filename); + } + + //! Save the image as a PNM file. + const CImg& save_pnm(cimg_std::FILE *const file) const { + return _save_pnm(file,0); + } + + // Save the image as a RGB file (internal). + const CImg& _save_rgb(cimg_std::FILE *const file, const char *const filename) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_rgb() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_rgb() : Instance image (%u,%u,%u,%u,%p), specified file is (null).", + pixel_type(),width,height,depth,dim,data); + if (dim!=3) + cimg::warn("CImg<%s>::save_rgb() : File '%s', instance image (%u,%u,%u,%u,%p) has not exactly 3 channels.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const unsigned int wh = width*height; + unsigned char *buffer = new unsigned char[3*wh], *nbuffer=buffer; + const T + *ptr1 = ptr(0,0,0,0), + *ptr2 = dim>1?ptr(0,0,0,1):0, + *ptr3 = dim>2?ptr(0,0,0,2):0; + switch (dim) { + case 1 : { // Scalar image + for (unsigned int k=0; k& save_rgb(const char *const filename) const { + return _save_rgb(0,filename); + } + + //! Save the image as a RGB file. + const CImg& save_rgb(cimg_std::FILE *const file) const { + return _save_rgb(file,0); + } + + // Save the image as a RGBA file (internal). + const CImg& _save_rgba(cimg_std::FILE *const file, const char *const filename) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_rgba() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_rgba() : Instance image (%u,%u,%u,%u,%p), specified file is (null).", + pixel_type(),width,height,depth,dim,data); + if (dim!=4) + cimg::warn("CImg<%s>::save_rgba() : File '%s, instance image (%u,%u,%u,%u,%p) has not exactly 4 channels.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const unsigned int wh = width*height; + unsigned char *buffer = new unsigned char[4*wh], *nbuffer=buffer; + const T + *ptr1 = ptr(0,0,0,0), + *ptr2 = dim>1?ptr(0,0,0,1):0, + *ptr3 = dim>2?ptr(0,0,0,2):0, + *ptr4 = dim>3?ptr(0,0,0,3):0; + switch (dim) { + case 1 : { // Scalar images + for (unsigned int k=0; k& save_rgba(const char *const filename) const { + return _save_rgba(0,filename); + } + + //! Save the image as a RGBA file. + const CImg& save_rgba(cimg_std::FILE *const file) const { + return _save_rgba(file,0); + } + + // Save a plane into a tiff file +#ifdef cimg_use_tiff + const CImg& _save_tiff(TIFF *tif, const unsigned int directory) const { + if (is_empty() || !tif) return *this; + const char *const filename = TIFFFileName(tif); + uint32 rowsperstrip = (uint32)-1; + uint16 spp = dim, bpp = sizeof(T)*8, photometric, compression = COMPRESSION_NONE; + if (spp==3 || spp==4) photometric = PHOTOMETRIC_RGB; + else photometric = PHOTOMETRIC_MINISBLACK; + TIFFSetDirectory(tif,directory); + TIFFSetField(tif,TIFFTAG_IMAGEWIDTH,width); + TIFFSetField(tif,TIFFTAG_IMAGELENGTH,height); + TIFFSetField(tif,TIFFTAG_ORIENTATION,ORIENTATION_TOPLEFT); + TIFFSetField(tif,TIFFTAG_SAMPLESPERPIXEL,spp); + if (cimg::type::is_float()) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,3); + else if (cimg::type::min()==0) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,1); + else TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,2); + TIFFSetField(tif,TIFFTAG_BITSPERSAMPLE,bpp); + TIFFSetField(tif,TIFFTAG_PLANARCONFIG,PLANARCONFIG_CONTIG); + TIFFSetField(tif,TIFFTAG_PHOTOMETRIC,photometric); + TIFFSetField(tif,TIFFTAG_COMPRESSION,compression); + rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip); + TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip); + TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB); + TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg"); + T *buf = (T*)_TIFFmalloc(TIFFStripSize(tif)); + if (buf){ + for (unsigned int row = 0; rowheight?height-row:rowsperstrip); + tstrip_t strip = TIFFComputeStrip(tif,row,0); + tsize_t i = 0; + for (unsigned int rr = 0; rr::save_tiff() : File '%s', an error has occured while writing a strip.", + pixel_type(),filename?filename:"(FILE*)"); + } + _TIFFfree(buf); + } + TIFFWriteDirectory(tif); + return (*this); + } +#endif + + //! Save a file in TIFF format. + const CImg& save_tiff(const char *const filename) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_tiff() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",width,height,depth,dim,data); + if (!filename) + throw CImgArgumentException("CImg<%s>::save_tiff() : Specified filename is (null) for instance image (%u,%u,%u,%u,%p).", + pixel_type(),width,height,depth,dim,data); +#ifdef cimg_use_tiff + TIFF *tif = TIFFOpen(filename,"w"); + if (tif) { + cimg_forZ(*this,z) get_slice(z)._save_tiff(tif,z); + TIFFClose(tif); + } else throw CImgException("CImg<%s>::save_tiff() : File '%s', error while opening file stream for writing.", + pixel_type(),filename); +#else + return save_other(filename); +#endif + return *this; + } + + //! Save the image as an ANALYZE7.5 or NIFTI file. + const CImg& save_analyze(const char *const filename, const float *const voxsize=0) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_analyze() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",width,height,depth,dim,data); + if (!filename) + throw CImgArgumentException("CImg<%s>::save_analyze() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).", + pixel_type(),width,height,depth,dim,data); + cimg_std::FILE *file; + char header[348], hname[1024], iname[1024]; + const char *ext = cimg::split_filename(filename); + short datatype=-1; + cimg_std::memset(header,0,348); + if (!ext[0]) { cimg_std::sprintf(hname,"%s.hdr",filename); cimg_std::sprintf(iname,"%s.img",filename); } + if (!cimg::strncasecmp(ext,"hdr",3)) { + cimg_std::strcpy(hname,filename); cimg_std::strcpy(iname,filename); cimg_std::sprintf(iname+cimg::strlen(iname)-3,"img"); + } + if (!cimg::strncasecmp(ext,"img",3)) { + cimg_std::strcpy(hname,filename); cimg_std::strcpy(iname,filename); cimg_std::sprintf(hname+cimg::strlen(iname)-3,"hdr"); + } + if (!cimg::strncasecmp(ext,"nii",3)) { + cimg_std::strcpy(hname,filename); iname[0] = 0; + } + ((int*)(header))[0] = 348; + cimg_std::sprintf(header+4,"CImg"); + cimg_std::sprintf(header+14," "); + ((short*)(header+36))[0] = 4096; + ((char*)(header+38))[0] = 114; + ((short*)(header+40))[0] = 4; + ((short*)(header+40))[1] = width; + ((short*)(header+40))[2] = height; + ((short*)(header+40))[3] = depth; + ((short*)(header+40))[4] = dim; + if (!cimg::strcasecmp(pixel_type(),"bool")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"unsigned char")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"char")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"unsigned short")) datatype = 4; + if (!cimg::strcasecmp(pixel_type(),"short")) datatype = 4; + if (!cimg::strcasecmp(pixel_type(),"unsigned int")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"int")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"unsigned long")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"long")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"float")) datatype = 16; + if (!cimg::strcasecmp(pixel_type(),"double")) datatype = 64; + if (datatype<0) + throw CImgIOException("CImg<%s>::save_analyze() : Cannot save image '%s' since pixel type (%s)" + "is not handled in Analyze7.5 specifications.\n", + pixel_type(),filename,pixel_type()); + ((short*)(header+70))[0] = datatype; + ((short*)(header+72))[0] = sizeof(T); + ((float*)(header+112))[0] = 1; + ((float*)(header+76))[0] = 0; + if (voxsize) { + ((float*)(header+76))[1] = voxsize[0]; + ((float*)(header+76))[2] = voxsize[1]; + ((float*)(header+76))[3] = voxsize[2]; + } else ((float*)(header+76))[1] = ((float*)(header+76))[2] = ((float*)(header+76))[3] = 1; + file = cimg::fopen(hname,"wb"); + cimg::fwrite(header,348,file); + if (iname[0]) { cimg::fclose(file); file = cimg::fopen(iname,"wb"); } + cimg::fwrite(data,size(),file); + cimg::fclose(file); + return *this; + } + + //! Save the image as a .cimg file. + const CImg& save_cimg(const char *const filename, const bool compress=false) const { + CImgList(*this,true).save_cimg(filename,compress); + return *this; + } + + // Save the image as a .cimg file. + const CImg& save_cimg(cimg_std::FILE *const file, const bool compress=false) const { + CImgList(*this,true).save_cimg(file,compress); + return *this; + } + + //! Insert the image into an existing .cimg file, at specified coordinates. + const CImg& save_cimg(const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int v0) const { + CImgList(*this,true).save_cimg(filename,n0,x0,y0,z0,v0); + return *this; + } + + //! Insert the image into an existing .cimg file, at specified coordinates. + const CImg& save_cimg(cimg_std::FILE *const file, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int v0) const { + CImgList(*this,true).save_cimg(file,n0,x0,y0,z0,v0); + return *this; + } + + //! Save an empty .cimg file with specified dimensions. + static void save_empty_cimg(const char *const filename, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dv=1) { + return CImgList::save_empty_cimg(filename,1,dx,dy,dz,dv); + } + + //! Save an empty .cimg file with specified dimensions. + static void save_empty_cimg(cimg_std::FILE *const file, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dv=1) { + return CImgList::save_empty_cimg(file,1,dx,dy,dz,dv); + } + + // Save the image as an INRIMAGE-4 file (internal). + const CImg& _save_inr(cimg_std::FILE *const file, const char *const filename, const float *const voxsize) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_inr() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (!filename) + throw CImgArgumentException("CImg<%s>::save_inr() : Instance image (%u,%u,%u,%u,%p), specified file is (null).", + pixel_type(),width,height,depth,dim,data); + int inrpixsize=-1; + const char *inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; + if (!cimg::strcasecmp(pixel_type(),"unsigned char")) { inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; } + if (!cimg::strcasecmp(pixel_type(),"char")) { inrtype = "fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; } + if (!cimg::strcasecmp(pixel_type(),"unsigned short")) { inrtype = "unsigned fixed\nPIXSIZE=16 bits\nSCALE=2**0";inrpixsize = 2; } + if (!cimg::strcasecmp(pixel_type(),"short")) { inrtype = "fixed\nPIXSIZE=16 bits\nSCALE=2**0"; inrpixsize = 2; } + if (!cimg::strcasecmp(pixel_type(),"unsigned int")) { inrtype = "unsigned fixed\nPIXSIZE=32 bits\nSCALE=2**0";inrpixsize = 4; } + if (!cimg::strcasecmp(pixel_type(),"int")) { inrtype = "fixed\nPIXSIZE=32 bits\nSCALE=2**0"; inrpixsize = 4; } + if (!cimg::strcasecmp(pixel_type(),"float")) { inrtype = "float\nPIXSIZE=32 bits"; inrpixsize = 4; } + if (!cimg::strcasecmp(pixel_type(),"double")) { inrtype = "float\nPIXSIZE=64 bits"; inrpixsize = 8; } + if (inrpixsize<=0) + throw CImgIOException("CImg<%s>::save_inr() : Don't know how to save images of '%s'", + pixel_type(),pixel_type()); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + char header[257]; + int err = cimg_std::sprintf(header,"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n",width,height,depth,dim); + if (voxsize) err += cimg_std::sprintf(header+err,"VX=%g\nVY=%g\nVZ=%g\n",voxsize[0],voxsize[1],voxsize[2]); + err += cimg_std::sprintf(header+err,"TYPE=%s\nCPU=%s\n",inrtype,cimg::endianness()?"sun":"decm"); + cimg_std::memset(header+err,'\n',252-err); + cimg_std::memcpy(header+252,"##}\n",4); + cimg::fwrite(header,256,nfile); + cimg_forXYZ(*this,x,y,z) cimg_forV(*this,k) cimg::fwrite(&((*this)(x,y,z,k)),1,nfile); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save the image as an INRIMAGE-4 file. + const CImg& save_inr(const char *const filename, const float *const voxsize=0) const { + return _save_inr(0,filename,voxsize); + } + + //! Save the image as an INRIMAGE-4 file. + const CImg& save_inr(cimg_std::FILE *const file, const float *const voxsize=0) const { + return _save_inr(file,0,voxsize); + } + + // Save the image as a PANDORE-5 file (internal). + unsigned int _save_pandore_header_length(unsigned int id, unsigned int *dims, const unsigned int colorspace) const { + unsigned int nbdims = 0; + if (id==2 || id==3 || id==4) { dims[0] = 1; dims[1] = width; nbdims = 2; } + if (id==5 || id==6 || id==7) { dims[0] = 1; dims[1] = height; dims[2] = width; nbdims=3; } + if (id==8 || id==9 || id==10) { dims[0] = dim; dims[1] = depth; dims[2] = height; dims[3] = width; nbdims = 4; } + if (id==16 || id==17 || id==18) { dims[0] = 3; dims[1] = height; dims[2] = width; dims[3] = colorspace; nbdims = 4; } + if (id==19 || id==20 || id==21) { dims[0] = 3; dims[1] = depth; dims[2] = height; dims[3] = width; dims[4] = colorspace; nbdims = 5; } + if (id==22 || id==23 || id==25) { dims[0] = dim; dims[1] = width; nbdims = 2; } + if (id==26 || id==27 || id==29) { dims[0] = dim; dims[1] = height; dims[2] = width; nbdims=3; } + if (id==30 || id==31 || id==33) { dims[0] = dim; dims[1] = depth; dims[2] = height; dims[3] = width; nbdims = 4; } + return nbdims; + } + + const CImg& _save_pandore(cimg_std::FILE *const file, const char *const filename, const unsigned int colorspace) const { + typedef unsigned char uchar; + typedef unsigned short ushort; + typedef unsigned int uint; + typedef unsigned long ulong; + +#define __cimg_save_pandore_case(dtype) \ + dtype *buffer = new dtype[size()]; \ + const T *ptrs = data; \ + cimg_foroff(*this,off) *(buffer++) = (dtype)(*(ptrs++)); \ + buffer-=size(); \ + cimg::fwrite(buffer,size(),nfile); \ + delete[] buffer + +#define _cimg_save_pandore_case(sy,sz,sv,stype,id) \ + if (!saved && (sy?(sy==height):true) && (sz?(sz==depth):true) && (sv?(sv==dim):true) && !cimg::strcmp(stype,pixel_type())) { \ + unsigned int *iheader = (unsigned int*)(header+12); \ + nbdims = _save_pandore_header_length((*iheader=id),dims,colorspace); \ + cimg::fwrite(header,36,nfile); \ + if (sizeof(ulong)==4) { ulong ndims[5]; for (int d = 0; d<5; ++d) ndims[d] = (ulong)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \ + else if (sizeof(uint)==4) { uint ndims[5]; for (int d = 0; d<5; ++d) ndims[d] = (uint)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \ + else if (sizeof(ushort)==4) { ushort ndims[5]; for (int d = 0; d<5; ++d) ndims[d] = (ushort)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \ + else throw CImgIOException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p), output type is not" \ + "supported on this architecture.",pixel_type(),filename?filename:"(FILE*)",width,height, \ + depth,dim,data); \ + if (id==2 || id==5 || id==8 || id==16 || id==19 || id==22 || id==26 || id==30) { \ + __cimg_save_pandore_case(uchar); \ + } else if (id==3 || id==6 || id==9 || id==17 || id==20 || id==23 || id==27 || id==31) { \ + if (sizeof(ulong)==4) { __cimg_save_pandore_case(ulong); } \ + else if (sizeof(uint)==4) { __cimg_save_pandore_case(uint); } \ + else if (sizeof(ushort)==4) { __cimg_save_pandore_case(ushort); } \ + else throw CImgIOException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p), output type is not" \ + "supported on this architecture.",pixel_type(),filename?filename:"(FILE*)",width,height, \ + depth,dim,data); \ + } else if (id==4 || id==7 || id==10 || id==18 || id==21 || id==25 || id==29 || id==33) { \ + if (sizeof(double)==4) { __cimg_save_pandore_case(double); } \ + else if (sizeof(float)==4) { __cimg_save_pandore_case(float); } \ + else throw CImgIOException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p), output type is not" \ + "supported on this architecture.",pixel_type(),filename?filename:"(FILE*)",width,height, \ + depth,dim,data); \ + } \ + saved = true; \ + } + + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_pandore() : Instance image (%u,%u,%u,%u,%p), specified file is (null).", + pixel_type(),width,height,depth,dim,data); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + unsigned char header[36] = { 'P','A','N','D','O','R','E','0','4',0,0,0, + 0,0,0,0,'C','I','m','g',0,0,0,0,0,'N','o',' ','d','a','t','e',0,0,0,0 }; + unsigned int nbdims, dims[5]; + bool saved = false; + _cimg_save_pandore_case(1,1,1,"unsigned char",2); + _cimg_save_pandore_case(1,1,1,"char",3); + _cimg_save_pandore_case(1,1,1,"short",3); + _cimg_save_pandore_case(1,1,1,"unsigned short",3); + _cimg_save_pandore_case(1,1,1,"unsigned int",3); + _cimg_save_pandore_case(1,1,1,"int",3); + _cimg_save_pandore_case(1,1,1,"unsigned long",4); + _cimg_save_pandore_case(1,1,1,"long",3); + _cimg_save_pandore_case(1,1,1,"float",4); + _cimg_save_pandore_case(1,1,1,"double",4); + + _cimg_save_pandore_case(0,1,1,"unsigned char",5); + _cimg_save_pandore_case(0,1,1,"char",6); + _cimg_save_pandore_case(0,1,1,"short",6); + _cimg_save_pandore_case(0,1,1,"unsigned short",6); + _cimg_save_pandore_case(0,1,1,"unsigned int",6); + _cimg_save_pandore_case(0,1,1,"int",6); + _cimg_save_pandore_case(0,1,1,"unsigned long",7); + _cimg_save_pandore_case(0,1,1,"long",6); + _cimg_save_pandore_case(0,1,1,"float",7); + _cimg_save_pandore_case(0,1,1,"double",7); + + _cimg_save_pandore_case(0,0,1,"unsigned char",8); + _cimg_save_pandore_case(0,0,1,"char",9); + _cimg_save_pandore_case(0,0,1,"short",9); + _cimg_save_pandore_case(0,0,1,"unsigned short",9); + _cimg_save_pandore_case(0,0,1,"unsigned int",9); + _cimg_save_pandore_case(0,0,1,"int",9); + _cimg_save_pandore_case(0,0,1,"unsigned long",10); + _cimg_save_pandore_case(0,0,1,"long",9); + _cimg_save_pandore_case(0,0,1,"float",10); + _cimg_save_pandore_case(0,0,1,"double",10); + + _cimg_save_pandore_case(0,1,3,"unsigned char",16); + _cimg_save_pandore_case(0,1,3,"char",17); + _cimg_save_pandore_case(0,1,3,"short",17); + _cimg_save_pandore_case(0,1,3,"unsigned short",17); + _cimg_save_pandore_case(0,1,3,"unsigned int",17); + _cimg_save_pandore_case(0,1,3,"int",17); + _cimg_save_pandore_case(0,1,3,"unsigned long",18); + _cimg_save_pandore_case(0,1,3,"long",17); + _cimg_save_pandore_case(0,1,3,"float",18); + _cimg_save_pandore_case(0,1,3,"double",18); + + _cimg_save_pandore_case(0,0,3,"unsigned char",19); + _cimg_save_pandore_case(0,0,3,"char",20); + _cimg_save_pandore_case(0,0,3,"short",20); + _cimg_save_pandore_case(0,0,3,"unsigned short",20); + _cimg_save_pandore_case(0,0,3,"unsigned int",20); + _cimg_save_pandore_case(0,0,3,"int",20); + _cimg_save_pandore_case(0,0,3,"unsigned long",21); + _cimg_save_pandore_case(0,0,3,"long",20); + _cimg_save_pandore_case(0,0,3,"float",21); + _cimg_save_pandore_case(0,0,3,"double",21); + + _cimg_save_pandore_case(1,1,0,"unsigned char",22); + _cimg_save_pandore_case(1,1,0,"char",23); + _cimg_save_pandore_case(1,1,0,"short",23); + _cimg_save_pandore_case(1,1,0,"unsigned short",23); + _cimg_save_pandore_case(1,1,0,"unsigned int",23); + _cimg_save_pandore_case(1,1,0,"int",23); + _cimg_save_pandore_case(1,1,0,"unsigned long",25); + _cimg_save_pandore_case(1,1,0,"long",23); + _cimg_save_pandore_case(1,1,0,"float",25); + _cimg_save_pandore_case(1,1,0,"double",25); + + _cimg_save_pandore_case(0,1,0,"unsigned char",26); + _cimg_save_pandore_case(0,1,0,"char",27); + _cimg_save_pandore_case(0,1,0,"short",27); + _cimg_save_pandore_case(0,1,0,"unsigned short",27); + _cimg_save_pandore_case(0,1,0,"unsigned int",27); + _cimg_save_pandore_case(0,1,0,"int",27); + _cimg_save_pandore_case(0,1,0,"unsigned long",29); + _cimg_save_pandore_case(0,1,0,"long",27); + _cimg_save_pandore_case(0,1,0,"float",29); + _cimg_save_pandore_case(0,1,0,"double",29); + + _cimg_save_pandore_case(0,0,0,"unsigned char",30); + _cimg_save_pandore_case(0,0,0,"char",31); + _cimg_save_pandore_case(0,0,0,"short",31); + _cimg_save_pandore_case(0,0,0,"unsigned short",31); + _cimg_save_pandore_case(0,0,0,"unsigned int",31); + _cimg_save_pandore_case(0,0,0,"int",31); + _cimg_save_pandore_case(0,0,0,"unsigned long",33); + _cimg_save_pandore_case(0,0,0,"long",31); + _cimg_save_pandore_case(0,0,0,"float",33); + _cimg_save_pandore_case(0,0,0,"double",33); + + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save the image as a PANDORE-5 file. + const CImg& save_pandore(const char *const filename, const unsigned int colorspace=0) const { + return _save_pandore(0,filename,colorspace); + } + + //! Save the image as a PANDORE-5 file. + const CImg& save_pandore(cimg_std::FILE *const file, const unsigned int colorspace=0) const { + return _save_pandore(file,0,colorspace); + } + + // Save the image as a RAW file (internal). + const CImg& _save_raw(cimg_std::FILE *const file, const char *const filename, const bool multiplexed) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_raw() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_raw() : Instance image (%u,%u,%u,%u,%p), specified file is (null).", + pixel_type(),width,height,depth,dim,data); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + if (!multiplexed) cimg::fwrite(data,size(),nfile); + else { + CImg buf(dim); + cimg_forXYZ(*this,x,y,z) { + cimg_forV(*this,k) buf[k] = (*this)(x,y,z,k); + cimg::fwrite(buf.data,dim,nfile); + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save the image as a RAW file. + const CImg& save_raw(const char *const filename, const bool multiplexed=false) const { + return _save_raw(0,filename,multiplexed); + } + + //! Save the image as a RAW file. + const CImg& save_raw(cimg_std::FILE *const file, const bool multiplexed=false) const { + return _save_raw(file,0,multiplexed); + } + + //! Save the image as a video sequence file, using FFMPEG library. + const CImg& save_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int fps=25) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_ffmpeg() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",width,height,depth,dim,data); + if (!filename) + throw CImgArgumentException("CImg<%s>::save_ffmpeg() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).", + pixel_type(),width,height,depth,dim,data); + if (!fps) + throw CImgArgumentException("CImg<%s>::save_ffmpeg() : File '%s', specified framerate is 0.", + pixel_type(),filename); +#ifndef cimg_use_ffmpeg + return save_ffmpeg_external(filename,first_frame,last_frame); +#else + get_split('z').save_ffmpeg(filename,first_frame,last_frame,fps); +#endif + return *this; + } + + //! Save the image as a YUV video sequence file. + const CImg& save_yuv(const char *const filename, const bool rgb2yuv=true) const { + get_split('z').save_yuv(filename,rgb2yuv); + return *this; + } + + //! Save the image as a YUV video sequence file. + const CImg& save_yuv(cimg_std::FILE *const file, const bool rgb2yuv=true) const { + get_split('z').save_yuv(file,rgb2yuv); + return *this; + } + + // Save OFF files (internal). + template + const CImg& _save_off(cimg_std::FILE *const file, const char *const filename, + const CImgList& primitives, const CImgList& colors, const bool invert_faces) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_off() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data); + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_off() : Specified filename is (null).", + pixel_type()); + if (height<3) return get_resize(-100,3,1,1,0)._save_off(file,filename,primitives,colors,invert_faces); + CImgList _colors; + if (!colors) _colors.insert(primitives.size,CImg::vector(200,200,200)); + const CImgList& ncolors = colors?colors:_colors; + + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); + cimg_std::fprintf(nfile,"OFF\n%u %u %u\n",width,primitives.size,3*primitives.size); + cimg_forX(*this,i) cimg_std::fprintf(nfile,"%f %f %f\n",(float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2))); + cimglist_for(primitives,l) { + const unsigned int prim = primitives[l].size(); + const bool textured = (prim>4); + const CImg& color = ncolors[l]; + const unsigned int s = textured?color.dimv():color.size(); + const float + r = textured?(s>0?(float)(color.get_shared_channel(0).mean()/255.0f):1.0f):(s>0?(float)(color(0)/255.0f):1.0f), + g = textured?(s>1?(float)(color.get_shared_channel(1).mean()/255.0f):r) :(s>1?(float)(color(1)/255.0f):r), + b = textured?(s>2?(float)(color.get_shared_channel(2).mean()/255.0f):r) :(s>2?(float)(color(2)/255.0f):r); + + switch (prim) { + case 1 : + cimg_std::fprintf(nfile,"1 %u %f %f %f\n",(unsigned int)primitives(l,0),r,g,b); + break; + case 2 : case 6 : + cimg_std::fprintf(nfile,"2 %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b); + break; + case 3 : case 9 : + if (invert_faces) + cimg_std::fprintf(nfile,"3 %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),(unsigned int)primitives(l,2),r,g,b); + else + cimg_std::fprintf(nfile,"3 %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b); + break; + case 4 : case 12 : + if (invert_faces) + cimg_std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),(unsigned int)primitives(l,2),(unsigned int)primitives(l,3),r,g,b); + else + cimg_std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n", + (unsigned int)primitives(l,0),(unsigned int)primitives(l,3),(unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b); + break; + } + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save OFF files. + template + const CImg& save_off(const char *const filename, + const CImgList& primitives, const CImgList& colors, const bool invert_faces=false) const { + return _save_off(0,filename,primitives,colors,invert_faces); + } + + //! Save OFF files. + template + const CImg& save_off(cimg_std::FILE *const file, + const CImgList& primitives, const CImgList& colors, const bool invert_faces=false) const { + return _save_off(file,0,primitives,colors,invert_faces); + } + + //! Save the image as a video sequence file, using the external tool 'ffmpeg'. + const CImg& save_ffmpeg_external(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const char *const codec="mpeg2video") const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_ffmpeg_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",width,height,depth,dim,data); + if (!filename) + throw CImgArgumentException("CImg<%s>::save_ffmpeg_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).", + pixel_type(),width,height,depth,dim,data); + get_split('z').save_ffmpeg_external(filename,first_frame,last_frame,codec); + return *this; + } + + //! Save the image using GraphicsMagick's gm. + /** Function that saves the image for other file formats that are not natively handled by CImg, + using the tool 'gm' from the GraphicsMagick package.\n + This is the case for all compressed image formats (GIF,PNG,JPG,TIF, ...). You need to install + the GraphicsMagick package in order to get + this function working properly (see http://www.graphicsmagick.org ). + **/ + const CImg& save_graphicsmagick_external(const char *const filename, const unsigned int quality=100) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_graphicsmagick_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",width,height,depth,dim,data); + if (!filename) + throw CImgArgumentException("CImg<%s>::save_graphicsmagick_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).", + pixel_type(),width,height,depth,dim,data); + char command[1024],filetmp[512]; + cimg_std::FILE *file; + do { + if (dim==1) cimg_std::sprintf(filetmp,"%s%s%s.pgm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand()); + else cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand()); + if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file); + } while (file); + save_pnm(filetmp); + cimg_std::sprintf(command,"%s -quality %u%% %s \"%s\"",cimg::graphicsmagick_path(),quality,filetmp,filename); + cimg::system(command); + file = cimg_std::fopen(filename,"rb"); + if (!file) + throw CImgIOException("CImg<%s>::save_graphicsmagick_external() : Failed to save image '%s'.\n\n" + "Path of 'gm' : \"%s\"\n" + "Path of temporary filename : \"%s\"\n", + pixel_type(),filename,cimg::graphicsmagick_path(),filetmp); + if (file) cimg::fclose(file); + cimg_std::remove(filetmp); + return *this; + } + + //! Save an image as a gzipped file, using external tool 'gzip'. + const CImg& save_gzip_external(const char *const filename) const { + if (!filename) + throw CImgIOException("CImg<%s>::save_gzip_external() : Cannot save (null) filename.", + pixel_type()); + char command[1024], filetmp[512], body[512]; + const char + *ext = cimg::split_filename(filename,body), + *ext2 = cimg::split_filename(body,0); + cimg_std::FILE *file; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand(),ext2); + else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand()); + } else { + if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand(),ext); + else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand()); + } + if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file); + } while (file); + save(filetmp); + cimg_std::sprintf(command,"%s -c %s > \"%s\"",cimg::gzip_path(),filetmp,filename); + cimg::system(command); + file = cimg_std::fopen(filename,"rb"); + if (!file) + throw CImgIOException("CImgList<%s>::save_gzip_external() : File '%s' cannot be saved.", + pixel_type(),filename); + else cimg::fclose(file); + cimg_std::remove(filetmp); + return *this; + } + + //! Save the image using ImageMagick's convert. + /** Function that saves the image for other file formats that are not natively handled by CImg, + using the tool 'convert' from the ImageMagick package.\n + This is the case for all compressed image formats (GIF,PNG,JPG,TIF, ...). You need to install + the ImageMagick package in order to get + this function working properly (see http://www.imagemagick.org ). + **/ + const CImg& save_imagemagick_external(const char *const filename, const unsigned int quality=100) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_imagemagick_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",width,height,depth,dim,data); + if (!filename) + throw CImgArgumentException("CImg<%s>::save_imagemagick_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).", + pixel_type(),width,height,depth,dim,data); + char command[1024], filetmp[512]; + cimg_std::FILE *file; + do { + cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand(),dim==1?"pgm":"ppm"); + if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file); + } while (file); + save_pnm(filetmp); + cimg_std::sprintf(command,"%s -quality %u%% %s \"%s\"",cimg::imagemagick_path(),quality,filetmp,filename); + cimg::system(command); + file = cimg_std::fopen(filename,"rb"); + if (!file) + throw CImgIOException("CImg<%s>::save_imagemagick_external() : Failed to save image '%s'.\n\n" + "Path of 'convert' : \"%s\"\n" + "Path of temporary filename : \"%s\"\n", + pixel_type(),filename,cimg::imagemagick_path(),filetmp); + if (file) cimg::fclose(file); + cimg_std::remove(filetmp); + return *this; + } + + //! Save an image as a Dicom file (need '(X)Medcon' : http://xmedcon.sourceforge.net ) + const CImg& save_medcon_external(const char *const filename) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_medcon_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",width,height,depth,dim,data); + if (!filename) + throw CImgArgumentException("CImg<%s>::save_medcon_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).", + pixel_type(),width,height,depth,dim,data); + + char command[1024], filetmp[512], body[512]; + cimg_std::FILE *file; + do { + cimg_std::sprintf(filetmp,"%s.hdr",cimg::filenamerand()); + if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file); + } while (file); + save_analyze(filetmp); + cimg_std::sprintf(command,"%s -w -c dicom -o %s -f %s",cimg::medcon_path(),filename,filetmp); + cimg::system(command); + cimg_std::remove(filetmp); + cimg::split_filename(filetmp,body); + cimg_std::sprintf(filetmp,"%s.img",body); + cimg_std::remove(filetmp); + cimg_std::sprintf(command,"m000-%s",filename); + file = cimg_std::fopen(command,"rb"); + if (!file) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException("CImg<%s>::save_medcon_external() : Failed to save image '%s'.\n\n" + "Path of 'medcon' : \"%s\"\n" + "Path of temporary filename : \"%s\"", + pixel_type(),filename,cimg::medcon_path(),filetmp); + } else cimg::fclose(file); + cimg_std::rename(command,filename); + return *this; + } + + // Try to save the image if other extension is provided. + const CImg& save_other(const char *const filename, const unsigned int quality=100) const { + if (is_empty()) + throw CImgInstanceException("CImg<%s>::save_other() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",width,height,depth,dim,data); + if (!filename) + throw CImgIOException("CImg<%s>::save_other() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).", + pixel_type()); + const unsigned int odebug = cimg::exception_mode(); + bool is_saved = true; + cimg::exception_mode() = 0; + try { save_magick(filename); } + catch (CImgException&) { + try { save_imagemagick_external(filename,quality); } + catch (CImgException&) { + try { save_graphicsmagick_external(filename,quality); } + catch (CImgException&) { + is_saved = false; + } + } + } + cimg::exception_mode() = odebug; + if (!is_saved) + throw CImgIOException("CImg<%s>::save_other() : File '%s' cannot be saved.\n" + "Check you have either the ImageMagick or GraphicsMagick package installed.", + pixel_type(),filename); + return *this; + } + + // Get a 40x38 color logo of a 'danger' item (internal). + static CImg logo40x38() { + static bool first_time = true; + static CImg res(40,38,1,3); + if (first_time) { + const unsigned char *ptrs = cimg::logo40x38; + T *ptr1 = res.ptr(0,0,0,0), *ptr2 = res.ptr(0,0,0,1), *ptr3 = res.ptr(0,0,0,2); + for (unsigned int off = 0; off structure + # + # + # + #------------------------------------------ + */ + + //! Class representing list of images CImg. + template + struct CImgList { + + //! Size of the list (number of elements inside). + unsigned int size; + + //! Allocation size of the list. + unsigned int allocsize; + + //! Pointer to the first list element. + CImg *data; + + //! Define a CImgList::iterator. + typedef CImg* iterator; + + //! Define a CImgList::const_iterator. + typedef const CImg* const_iterator; + + //! Get value type. + typedef T value_type; + + // Define common T-dependant types. + typedef typename cimg::superset::type Tbool; + typedef typename cimg::superset::type Tuchar; + typedef typename cimg::superset::type Tchar; + typedef typename cimg::superset::type Tushort; + typedef typename cimg::superset::type Tshort; + typedef typename cimg::superset::type Tuint; + typedef typename cimg::superset::type Tint; + typedef typename cimg::superset::type Tulong; + typedef typename cimg::superset::type Tlong; + typedef typename cimg::superset::type Tfloat; + typedef typename cimg::superset::type Tdouble; + typedef typename cimg::last::type boolT; + typedef typename cimg::last::type ucharT; + typedef typename cimg::last::type charT; + typedef typename cimg::last::type ushortT; + typedef typename cimg::last::type shortT; + typedef typename cimg::last::type uintT; + typedef typename cimg::last::type intT; + typedef typename cimg::last::type ulongT; + typedef typename cimg::last::type longT; + typedef typename cimg::last::type floatT; + typedef typename cimg::last::type doubleT; + + //@} + //--------------------------- + // + //! \name Plugins + //@{ + //--------------------------- +#ifdef cimglist_plugin +#include cimglist_plugin +#endif +#ifdef cimglist_plugin1 +#include cimglist_plugin1 +#endif +#ifdef cimglist_plugin2 +#include cimglist_plugin2 +#endif +#ifdef cimglist_plugin3 +#include cimglist_plugin3 +#endif +#ifdef cimglist_plugin4 +#include cimglist_plugin4 +#endif +#ifdef cimglist_plugin5 +#include cimglist_plugin5 +#endif +#ifdef cimglist_plugin6 +#include cimglist_plugin6 +#endif +#ifdef cimglist_plugin7 +#include cimglist_plugin7 +#endif +#ifdef cimglist_plugin8 +#include cimglist_plugin8 +#endif + //@} + + //------------------------------------------ + // + //! \name Constructors - Destructor - Copy + //@{ + //------------------------------------------ + + //! Destructor. + ~CImgList() { + if (data) delete[] data; + } + + //! Default constructor. + CImgList(): + size(0),allocsize(0),data(0) {} + + //! Construct an image list containing n empty images. + explicit CImgList(const unsigned int n): + size(n) { + data = new CImg[allocsize = cimg::max(16UL,cimg::nearest_pow2(n))]; + } + + //! Default copy constructor. + template + CImgList(const CImgList& list): + size(0),allocsize(0),data(0) { + assign(list.size); + cimglist_for(*this,l) data[l].assign(list[l],false); + } + + CImgList(const CImgList& list): + size(0),allocsize(0),data(0) { + assign(list.size); + cimglist_for(*this,l) data[l].assign(list[l],list[l].is_shared); + } + + //! Advanced copy constructor. + template + CImgList(const CImgList& list, const bool shared): + size(0),allocsize(0),data(0) { + assign(list.size); + if (shared) + throw CImgArgumentException("CImgList<%s>::CImgList() : Cannot construct a list instance with shared images from " + "a CImgList<%s> (different pixel types).", + pixel_type(),CImgList::pixel_type()); + cimglist_for(*this,l) data[l].assign(list[l],false); + } + + CImgList(const CImgList& list, const bool shared): + size(0),allocsize(0),data(0) { + assign(list.size); + cimglist_for(*this,l) data[l].assign(list[l],shared); + } + + //! Construct an image list containing n images with specified size. + CImgList(const unsigned int n, const unsigned int width, const unsigned int height=1, + const unsigned int depth=1, const unsigned int dim=1): + size(0),allocsize(0),data(0) { + assign(n); + cimglist_for(*this,l) data[l].assign(width,height,depth,dim); + } + + //! Construct an image list containing n images with specified size, filled with specified value. + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int dim, const T val): + size(0),allocsize(0),data(0) { + assign(n); + cimglist_for(*this,l) data[l].assign(width,height,depth,dim,val); + } + + //! Construct an image list containing n images with specified size and specified pixel values (int version). + CImgList(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int dim, const int val0, const int val1, ...): + size(0),allocsize(0),data(0) { +#define _CImgList_stdarg(t) { \ + assign(n,width,height,depth,dim); \ + const unsigned int siz = width*height*depth*dim, nsiz = siz*n; \ + T *ptrd = data->data; \ + va_list ap; \ + va_start(ap,val1); \ + for (unsigned int l=0, s=0, i=0; i + CImgList(const unsigned int n, const CImg& img): + size(0),allocsize(0),data(0) { + assign(n); + cimglist_for(*this,l) data[l].assign(img,img.is_shared); + } + + //! Construct a list containing n copies of the image img, forcing the shared state. + template + CImgList(const unsigned int n, const CImg& img, const bool shared): + size(0),allocsize(0),data(0) { + assign(n); + cimglist_for(*this,l) data[l].assign(img,shared); + } + + //! Construct an image list from one image. + template + explicit CImgList(const CImg& img): + size(0),allocsize(0),data(0) { + assign(1); + data[0].assign(img,img.is_shared); + } + + //! Construct an image list from one image, forcing the shared state. + template + explicit CImgList(const CImg& img, const bool shared): + size(0),allocsize(0),data(0) { + assign(1); + data[0].assign(img,shared); + } + + //! Construct an image list from two images. + template + CImgList(const CImg& img1, const CImg& img2): + size(0),allocsize(0),data(0) { + assign(2); + data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); + } + + //! Construct an image list from two images, forcing the shared state. + template + CImgList(const CImg& img1, const CImg& img2, const bool shared): + size(0),allocsize(0),data(0) { + assign(2); + data[0].assign(img1,shared); data[1].assign(img2,shared); + } + + //! Construct an image list from three images. + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3): + size(0),allocsize(0),data(0) { + assign(3); + data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); + } + + //! Construct an image list from three images, forcing the shared state. + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const bool shared): + size(0),allocsize(0),data(0) { + assign(3); + data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); + } + + //! Construct an image list from four images. + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4): + size(0),allocsize(0),data(0) { + assign(4); + data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared); + } + + //! Construct an image list from four images, forcing the shared state. + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, const bool shared): + size(0),allocsize(0),data(0) { + assign(4); + data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared); + } + + //! Construct an image list from five images. + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5): + size(0),allocsize(0),data(0) { + assign(5); + data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared); + data[4].assign(img5,img5.is_shared); + } + + //! Construct an image list from five images, forcing the shared state. + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const bool shared): + size(0),allocsize(0),data(0) { + assign(5); + data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared); + data[4].assign(img5,shared); + } + + //! Construct an image list from six images. + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6): + size(0),allocsize(0),data(0) { + assign(6); + data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared); + data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared); + } + + //! Construct an image list from six images, forcing the shared state. + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const bool shared): + size(0),allocsize(0),data(0) { + assign(6); + data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared); + data[4].assign(img5,shared); data[5].assign(img6,shared); + } + + //! Construct an image list from seven images. + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7): + size(0),allocsize(0),data(0) { + assign(7); + data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared); + data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared); data[6].assign(img7,img7.is_shared); + } + + //! Construct an image list from seven images, forcing the shared state. + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const bool shared): + size(0),allocsize(0),data(0) { + assign(7); + data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared); + data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared); + } + + //! Construct an image list from eight images. + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const CImg& img8): + size(0),allocsize(0),data(0) { + assign(8); + data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared); + data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared); data[6].assign(img7,img7.is_shared); data[7].assign(img8,img8.is_shared); + } + + //! Construct an image list from eight images, forcing the shared state. + template + CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const CImg& img8, const bool shared): + size(0),allocsize(0),data(0) { + assign(8); + data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared); + data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared); data[7].assign(img8,shared); + } + + //! Construct an image list from a filename. + CImgList(const char *const filename): + size(0),allocsize(0),data(0) { + assign(filename); + } + + //! In-place version of the default constructor and default destructor. + CImgList& assign() { + if (data) delete[] data; + size = allocsize = 0; + data = 0; + return *this; + } + + //! Equivalent to assign() (STL-compliant name). + CImgList& clear() { + return assign(); + } + + //! In-place version of the corresponding constructor. + CImgList& assign(const unsigned int n) { + if (n) { + if (allocsize(n<<2)) { + if (data) delete[] data; + data = new CImg[allocsize=cimg::max(16UL,cimg::nearest_pow2(n))]; + } + size = n; + } else assign(); + return *this; + } + + //! In-place version of the corresponding constructor. + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height=1, + const unsigned int depth=1, const unsigned int dim=1) { + assign(n); + cimglist_for(*this,l) data[l].assign(width,height,depth,dim); + return *this; + } + + //! In-place version of the corresponding constructor. + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int dim, const T val) { + assign(n); + cimglist_for(*this,l) data[l].assign(width,height,depth,dim,val); + return *this; + } + + //! In-place version of the corresponding constructor. + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int dim, const int val0, const int val1, ...) { + _CImgList_stdarg(int); + return *this; + } + + //! In-place version of the corresponding constructor. + CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, + const unsigned int depth, const unsigned int dim, const double val0, const double val1, ...) { + _CImgList_stdarg(double); + return *this; + } + + //! In-place version of the copy constructor. + template + CImgList& assign(const CImgList& list) { + assign(list.size); + cimglist_for(*this,l) data[l].assign(list[l],list[l].is_shared); + return *this; + } + + //! In-place version of the copy constructor. + template + CImgList& assign(const CImgList& list, const bool shared) { + assign(list.size); + cimglist_for(*this,l) data[l].assign(list[l],shared); + return *this; + } + + //! In-place version of the corresponding constructor. + template + CImgList& assign(const unsigned int n, const CImg& img, const bool shared=false) { + assign(n); + cimglist_for(*this,l) data[l].assign(img,shared); + return *this; + } + + //! In-place version of the corresponding constructor. + template + CImgList& assign(const CImg& img, const bool shared=false) { + assign(1); + data[0].assign(img,shared); + return *this; + } + + //! In-place version of the corresponding constructor. + template + CImgList& assign(const CImg& img1, const CImg& img2, const bool shared=false) { + assign(2); + data[0].assign(img1,shared); data[1].assign(img2,shared); + return *this; + } + + //! In-place version of the corresponding constructor. + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const bool shared=false) { + assign(3); + data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); + return *this; + } + + //! In-place version of the corresponding constructor. + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const bool shared=false) { + assign(4); + data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared); + return *this; + } + + //! In-place version of the corresponding constructor. + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const bool shared=false) { + assign(5); + data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared); + data[4].assign(img5,shared); + return *this; + } + + //! In-place version of the corresponding constructor. + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const bool shared=false) { + assign(6); + data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared); + data[4].assign(img5,shared); data[5].assign(img6,shared); + return *this; + } + + //! In-place version of the corresponding constructor. + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const bool shared=false) { + assign(7); + data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared); + data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared); + return *this; + } + + //! In-place version of the corresponding constructor. + template + CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, + const CImg& img5, const CImg& img6, const CImg& img7, const CImg& img8, const bool shared=false) { + assign(8); + data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared); + data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared); data[7].assign(img8,shared); + return *this; + } + + //! In-place version of the corresponding constructor. + CImgList& assign(const char *const filename) { + return load(filename); + } + + //! Transfer the content of the instance image list into another one. + template + CImgList& transfer_to(CImgList& list) { + list.assign(*this); + assign(); + return list; + } + + CImgList& transfer_to(CImgList& list) { + list.assign(); + return swap(list); + } + + //! Swap all fields of two CImgList instances (use with care !) + CImgList& swap(CImgList& list) { + cimg::swap(size,list.size); + cimg::swap(allocsize,list.allocsize); + cimg::swap(data,list.data); + return list; + } + + //! Return a string describing the type of the image pixels in the list (template parameter \p T). + static const char* pixel_type() { + return cimg::type::string(); + } + + //! Return \p true if list is empty. + bool is_empty() const { + return (!data || !size); + } + + //! Return \p true if list is not empty. + operator bool() const { + return !is_empty(); + } + + //! Return \p true if list if of specified size. + bool is_sameN(const unsigned int n) const { + return (size==n); + } + + //! Return \p true if list if of specified size. + template + bool is_sameN(const CImgList& list) const { + return (size==list.size); + } + + // Define useful dimension check functions. + // (not documented because they are macro-generated). +#define _cimglist_def_is_same1(axis) \ + bool is_same##axis(const unsigned int val) const { \ + bool res = true; for (unsigned int l = 0; l bool is_same##axis(const CImg& img) const { \ + bool res = true; for (unsigned int l = 0; l bool is_same##axis(const CImgList& list) const { \ + const unsigned int lmin = cimg::min(size,list.size); \ + bool res = true; for (unsigned int l = 0; l bool is_sameN##axis(const unsigned int n, const CImg& img) const { \ + return (is_sameN(n) && is_same##axis(img)); \ + } \ + template bool is_sameN##axis(const CImgList& list) const { \ + return (is_sameN(list) && is_same##axis(list)); \ + } + + _cimglist_def_is_same(XY) + _cimglist_def_is_same(XZ) + _cimglist_def_is_same(XV) + _cimglist_def_is_same(YZ) + _cimglist_def_is_same(YV) + _cimglist_def_is_same(XYZ) + _cimglist_def_is_same(XYV) + _cimglist_def_is_same(YZV) + _cimglist_def_is_same(XYZV) + _cimglist_def_is_same1(X) + _cimglist_def_is_same1(Y) + _cimglist_def_is_same1(Z) + _cimglist_def_is_same1(V) + _cimglist_def_is_same2(X,Y) + _cimglist_def_is_same2(X,Z) + _cimglist_def_is_same2(X,V) + _cimglist_def_is_same2(Y,Z) + _cimglist_def_is_same2(Y,V) + _cimglist_def_is_same2(Z,V) + _cimglist_def_is_same3(X,Y,Z) + _cimglist_def_is_same3(X,Y,V) + _cimglist_def_is_same3(X,Z,V) + _cimglist_def_is_same3(Y,Z,V) + + bool is_sameXYZV(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv) const { + bool res = true; + for (unsigned int l = 0; l=0 && n<(int)size && x>=0 && x=0 && y=0 && z=0 && v=0 && n<(int)size; + } + + //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y,z,v). + template + bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& v) const { + if (is_empty()) return false; + cimglist_for(*this,l) if (data[l].contains(pixel,x,y,z,v)) { n = (t)l; return true; } + return false; + } + + //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y,z). + template + bool contains(const T& pixel, t& n, t& x, t&y, t& z) const { + t v; + return contains(pixel,n,x,y,z,v); + } + + //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y). + template + bool contains(const T& pixel, t& n, t& x, t&y) const { + t z,v; + return contains(pixel,n,x,y,z,v); + } + + //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x). + template + bool contains(const T& pixel, t& n, t& x) const { + t y,z,v; + return contains(pixel,n,x,y,z,v); + } + + //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n). + template + bool contains(const T& pixel, t& n) const { + t x,y,z,v; + return contains(pixel,n,x,y,z,v); + } + + //! Return \c true if one of the image list contains the specified referenced value. + bool contains(const T& pixel) const { + unsigned int n,x,y,z,v; + return contains(pixel,n,x,y,z,v); + } + + //! Return \c true if the list contains the image 'img'. If true, returns the position (n) of the image in the list. + template + bool contains(const CImg& img, t& n) const { + if (is_empty()) return false; + const CImg *const ptr = &img; + cimglist_for(*this,i) if (data+i==ptr) { n = (t)i; return true; } + return false; + } + + //! Return \c true if the list contains the image img. + bool contains(const CImg& img) const { + unsigned int n; + return contains(img,n); + } + + //@} + //------------------------------ + // + //! \name Arithmetics Operators + //@{ + //------------------------------ + + //! Assignment operator + template + CImgList& operator=(const CImgList& list) { + return assign(list); + } + + CImgList& operator=(const CImgList& list) { + return assign(list); + } + + //! Assignment operator. + template + CImgList& operator=(const CImg& img) { + cimglist_for(*this,l) data[l] = img; + return *this; + } + + //! Assignment operator. + CImgList& operator=(const T val) { + cimglist_for(*this,l) data[l].fill(val); + return *this; + } + + //! Operator+. + CImgList operator+() const { + return CImgList(*this); + } + + //! Operator+=. +#ifdef cimg_use_visualcpp6 + CImgList& operator+=(const T val) +#else + template + CImgList& operator+=(const t val) +#endif + { + cimglist_for(*this,l) (*this)[l]+=val; + return *this; + } + + //! Operator+=. + template + CImgList& operator+=(const CImgList& list) { + const unsigned int sizemax = cimg::min(size,list.size); + for (unsigned int l=0; l& operator++() { + cimglist_for(*this,l) ++(*this)[l]; + return *this; + } + + //! Operator++ (postfix). + CImgList operator++(int) { + CImgList copy(*this); + ++*this; + return copy; + } + + //! Operator-. + CImgList operator-() const { + CImgList res(size); + cimglist_for(res,l) res[l].assign(-data[l]); + return res; + } + + //! Operator-=. +#ifdef cimg_use_visualcpp6 + CImgList& operator-=(const T val) +#else + template + CImgList& operator-=(const t val) +#endif + { + cimglist_for(*this,l) (*this)[l]-=val; + return *this; + } + + //! Operator-=. + template + CImgList& operator-=(const CImgList& list) { + const unsigned int sizemax = min(size,list.size); + for (unsigned int l=0; l& operator--() { + cimglist_for(*this,l) --(*this)[l]; + return *this; + } + + //! Operator-- (postfix). + CImgList operator--(int) { + CImgList copy(*this); + --*this; + return copy; + } + + //! Operator*=. +#ifdef cimg_use_visualcpp6 + CImgList& operator*=(const double val) +#else + template + CImgList& operator*=(const t val) +#endif + { + cimglist_for(*this,l) (*this)[l]*=val; + return *this; + } + + //! Operator*=. + template + CImgList& operator*=(const CImgList& list) { + const unsigned int N = cimg::min(size,list.size); + for (unsigned int l=0; l& operator/=(const double val) +#else + template + CImgList& operator/=(const t val) +#endif + { + cimglist_for(*this,l) (*this)[l]/=val; + return *this; + } + + //! Operator/=. + template + CImgList& operator/=(const CImgList& list) { + const unsigned int N = cimg::min(size,list.size); + for (unsigned int l=0; l::max() : Instance image list is empty.", + pixel_type()); + const T *ptrmax = data->data; + T max_value = *ptrmax; + cimglist_for(*this,l) { + const CImg& img = data[l]; + cimg_for(img,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr); + } + return *ptrmax; + } + + //! Return a reference to the maximum pixel value of the instance list. + T& max() { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::max() : Instance image list is empty.", + pixel_type()); + T *ptrmax = data->data; + T max_value = *ptrmax; + cimglist_for(*this,l) { + const CImg& img = data[l]; + cimg_for(img,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr); + } + return *ptrmax; + } + + //! Return a reference to the minimum pixel value of the instance list. + const T& min() const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::min() : Instance image list is empty.", + pixel_type()); + const T *ptrmin = data->data; + T min_value = *ptrmin; + cimglist_for(*this,l) { + const CImg& img = data[l]; + cimg_for(img,ptr,T) if ((*ptr)::min() : Instance image list is empty.", + pixel_type()); + T *ptrmin = data->data; + T min_value = *ptrmin; + cimglist_for(*this,l) { + const CImg& img = data[l]; + cimg_for(img,ptr,T) if ((*ptr) + const T& minmax(t& max_val) const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::minmax() : Instance image list is empty.", + pixel_type()); + const T *ptrmin = data->data; + T min_value = *ptrmin, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = data[l]; + cimg_for(img,ptr,T) { + const T val = *ptr; + if (valmax_value) max_value = val; + } + } + max_val = (t)max_value; + return *ptrmin; + } + + //! Return a reference to the minimum pixel value of the instance list. + template + T& minmax(t& max_val) { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::minmax() : Instance image list is empty.", + pixel_type()); + T *ptrmin = data->data; + T min_value = *ptrmin, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = data[l]; + cimg_for(img,ptr,T) { + const T val = *ptr; + if (valmax_value) max_value = val; + } + } + max_val = (t)max_value; + return *ptrmin; + } + + //! Return a reference to the minimum pixel value of the instance list. + template + const T& maxmin(t& min_val) const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::maxmin() : Instance image list is empty.", + pixel_type()); + const T *ptrmax = data->data; + T min_value = *ptrmax, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = data[l]; + cimg_for(img,ptr,T) { + const T val = *ptr; + if (val>max_value) { max_value = val; ptrmax = ptr; } + if (val + T& maxmin(t& min_val) { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::maxmin() : Instance image list is empty.", + pixel_type()); + T *ptrmax = data->data; + T min_value = *ptrmax, max_value = min_value; + cimglist_for(*this,l) { + const CImg& img = data[l]; + cimg_for(img,ptr,T) { + const T val = *ptr; + if (val>max_value) { max_value = val; ptrmax = ptr; } + if (val::mean() : Instance image list is empty.", + pixel_type()); + double val = 0; + unsigned int siz = 0; + cimglist_for(*this,l) { + const CImg& img = data[l]; + cimg_for(img,ptr,T) val+=(double)*ptr; + siz+=img.size(); + } + return val/siz; + } + + //! Return the variance of the instance list. + double variance() { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::variance() : Instance image list is empty.", + pixel_type()); + double res = 0; + unsigned int siz = 0; + double S = 0, S2 = 0; + cimglist_for(*this,l) { + const CImg& img = data[l]; + cimg_for(img,ptr,T) { const double val = (double)*ptr; S+=val; S2+=val*val; } + siz+=img.size(); + } + res = (S2 - S*S/siz)/siz; + return res; + } + + //! Compute a list of statistics vectors (min,max,mean,variance,xmin,ymin,zmin,vmin,xmax,ymax,zmax,vmax). + CImgList& stats(const unsigned int variance_method=1) { + if (is_empty()) return *this; + cimglist_for(*this,l) data[l].stats(variance_method); + return *this; + } + + CImgList get_stats(const unsigned int variance_method=1) const { + CImgList res(size); + cimglist_for(*this,l) res[l] = data[l].get_stats(variance_method); + return res; + } + + //@} + //------------------------- + // + //! \name List Manipulation + //@{ + //------------------------- + + //! Return a reference to the i-th element of the image list. + CImg& operator[](const unsigned int pos) { +#if cimg_debug>=3 + if (pos>=size) { + cimg::warn("CImgList<%s>::operator[] : bad list position %u, in a list of %u images", + pixel_type(),pos,size); + return *data; + } +#endif + return data[pos]; + } + + const CImg& operator[](const unsigned int pos) const { +#if cimg_debug>=3 + if (pos>=size) { + cimg::warn("CImgList<%s>::operator[] : bad list position %u, in a list of %u images", + pixel_type(),pos,size); + return *data; + } +#endif + return data[pos]; + } + + //! Equivalent to CImgList::operator[] + CImg& operator()(const unsigned int pos) { + return (*this)[pos]; + } + + const CImg& operator()(const unsigned int pos) const { + return (*this)[pos]; + } + + //! Return a reference to (x,y,z,v) pixel of the pos-th image of the list + T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int v=0) { + return (*this)[pos](x,y,z,v); + } + const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0, + const unsigned int z=0, const unsigned int v=0) const { + return (*this)[pos](x,y,z,v); + } + + // This function is only here for template tricks. + T _display_object3d_at2(const int i, const int j) const { + return atNXY(i,0,j,0,0,0); + } + + //! Read an image in specified position. + CImg& at(const int pos) { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::at() : Instance list is empty.", + pixel_type()); + return data[pos<0?0:pos>=(int)size?(int)size-1:pos]; + } + + //! Read a pixel value with Dirichlet boundary conditions. + T& atNXYZV(const int pos, const int x, const int y, const int z, const int v, const T out_val) { + return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atXYZV(x,y,z,v,out_val); + } + + T atNXYZV(const int pos, const int x, const int y, const int z, const int v, const T out_val) const { + return (pos<0 || pos>=(int)size)?out_val:data[pos].atXYZV(x,y,z,v,out_val); + } + + //! Read a pixel value with Neumann boundary conditions. + T& atNXYZV(const int pos, const int x, const int y, const int z, const int v) { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::atNXYZV() : Instance list is empty.", + pixel_type()); + return _atNXYZV(pos,x,y,z,v); + } + + T atNXYZV(const int pos, const int x, const int y, const int z, const int v) const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::atNXYZV() : Instance list is empty.", + pixel_type()); + return _atNXYZV(pos,x,y,z,v); + } + + T& _atNXYZV(const int pos, const int x, const int y, const int z, const int v) { + return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZV(x,y,z,v); + } + + T _atNXYZV(const int pos, const int x, const int y, const int z, const int v) const { + return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZV(x,y,z,v); + } + + //! Read a pixel value with Dirichlet boundary conditions for the four first coordinates (\c pos, \c x,\c y,\c z). + T& atNXYZ(const int pos, const int x, const int y, const int z, const int v, const T out_val) { + return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atXYZ(x,y,z,v,out_val); + } + + T atNXYZ(const int pos, const int x, const int y, const int z, const int v, const T out_val) const { + return (pos<0 || pos>=(int)size)?out_val:data[pos].atXYZ(x,y,z,v,out_val); + } + + //! Read a pixel value with Neumann boundary conditions for the four first coordinates (\c pos, \c x,\c y,\c z). + T& atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::atNXYZ() : Instance list is empty.", + pixel_type()); + return _atNXYZ(pos,x,y,z,v); + } + + T atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::atNXYZ() : Instance list is empty.", + pixel_type()); + return _atNXYZ(pos,x,y,z,v); + } + + T& _atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) { + return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZ(x,y,z,v); + } + + T _atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) const { + return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZ(x,y,z,v); + } + + //! Read a pixel value with Dirichlet boundary conditions for the three first coordinates (\c pos, \c x,\c y). + T& atNXY(const int pos, const int x, const int y, const int z, const int v, const T out_val) { + return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atXY(x,y,z,v,out_val); + } + + T atNXY(const int pos, const int x, const int y, const int z, const int v, const T out_val) const { + return (pos<0 || pos>=(int)size)?out_val:data[pos].atXY(x,y,z,v,out_val); + } + + //! Read a pixel value with Neumann boundary conditions for the three first coordinates (\c pos, \c x,\c y). + T& atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::atNXY() : Instance list is empty.", + pixel_type()); + return _atNXY(pos,x,y,z,v); + } + + T atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::atNXY() : Instance list is empty.", + pixel_type()); + return _atNXY(pos,x,y,z,v); + } + + T& _atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) { + return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXY(x,y,z,v); + } + + T _atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) const { + return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXY(x,y,z,v); + } + + //! Read a pixel value with Dirichlet boundary conditions for the two first coordinates (\c pos,\c x). + T& atNX(const int pos, const int x, const int y, const int z, const int v, const T out_val) { + return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atX(x,y,z,v,out_val); + } + + T atNX(const int pos, const int x, const int y, const int z, const int v, const T out_val) const { + return (pos<0 || pos>=(int)size)?out_val:data[pos].atX(x,y,z,v,out_val); + } + + //! Read a pixel value with Neumann boundary conditions for the two first coordinates (\c pos, \c x). + T& atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::atNX() : Instance list is empty.", + pixel_type()); + return _atNX(pos,x,y,z,v); + } + + T atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::atNX() : Instance list is empty.", + pixel_type()); + return _atNX(pos,x,y,z,v); + } + + T& _atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) { + return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atX(x,y,z,v); + } + + T _atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) const { + return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atX(x,y,z,v); + } + + //! Read a pixel value with Dirichlet boundary conditions for the first coordinates (\c pos). + T& atN(const int pos, const int x, const int y, const int z, const int v, const T out_val) { + return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):(*this)(pos,x,y,z,v); + } + + T atN(const int pos, const int x, const int y, const int z, const int v, const T out_val) const { + return (pos<0 || pos>=(int)size)?out_val:(*this)(pos,x,y,z,v); + } + + //! Read a pixel value with Neumann boundary conditions for the first coordinates (\c pos). + T& atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::atN() : Instance list is empty.", + pixel_type()); + return _atN(pos,x,y,z,v); + } + + T atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::atN() : Instance list is empty.", + pixel_type()); + return _atN(pos,x,y,z,v); + } + + T& _atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) { + return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)](x,y,z,v); + } + + T _atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) const { + return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)](x,y,z,v); + } + + //! Returns a reference to the last element. + CImg& back() { + return (*this)(size-1); + } + + const CImg& back() const { + return (*this)(size-1); + } + + //! Returns a reference to the first element. + CImg& front() { + return *data; + } + + const CImg& front() const { + return *data; + } + + //! Returns an iterator to the beginning of the vector. + iterator begin() { + return data; + } + + const_iterator begin() const { + return data; + } + + //! Return a reference to the first image. + const CImg& first() const { + return *data; + } + + CImg& first() { + return *data; + } + + //! Returns an iterator just past the last element. + iterator end() { + return data + size; + } + + const_iterator end() const { + return data + size; + } + + //! Return a reference to the last image. + const CImg& last() const { + return data[size - 1]; + } + + CImg& last() { + return data[size - 1]; + } + + //! Insert a copy of the image \p img into the current image list, at position \p pos. + template + CImgList& insert(const CImg& img, const unsigned int pos, const bool shared) { + const unsigned int npos = pos==~0U?size:pos; + if (npos>size) + throw CImgArgumentException("CImgList<%s>::insert() : Cannot insert at position %u into a list with %u elements", + pixel_type(),npos,size); + if (shared) + throw CImgArgumentException("CImgList<%s>::insert(): Cannot insert a shared image CImg<%s> into a CImgList<%s>", + pixel_type(),img.pixel_type(),pixel_type()); + CImg *new_data = (++size>allocsize)?new CImg[allocsize?(allocsize<<=1):(allocsize=16)]:0; + if (!size || !data) { + data = new_data; + *data = img; + } else { + if (new_data) { + if (npos) cimg_std::memcpy(new_data,data,sizeof(CImg)*npos); + if (npos!=size-1) cimg_std::memcpy(new_data+npos+1,data+npos,sizeof(CImg)*(size-1-npos)); + cimg_std::memset(data,0,sizeof(CImg)*(size-1)); + delete[] data; + data = new_data; + } + else if (npos!=size-1) cimg_std::memmove(data+npos+1,data+npos,sizeof(CImg)*(size-1-npos)); + data[npos].width = data[npos].height = data[npos].depth = data[npos].dim = 0; data[npos].data = 0; + data[npos] = img; + } + return *this; + } + + CImgList& insert(const CImg& img, const unsigned int pos, const bool shared) { + const unsigned int npos = pos==~0U?size:pos; + if (npos>size) + throw CImgArgumentException("CImgList<%s>::insert() : Can't insert at position %u into a list with %u elements", + pixel_type(),npos,size); + if (&img>=data && &img *new_data = (++size>allocsize)?new CImg[allocsize?(allocsize<<=1):(allocsize=16)]:0; + if (!size || !data) { + data = new_data; + if (shared && img) { + data->width = img.width; data->height = img.height; data->depth = img.depth; data->dim = img.dim; + data->is_shared = true; data->data = img.data; + } else *data = img; + } + else { + if (new_data) { + if (npos) cimg_std::memcpy(new_data,data,sizeof(CImg)*npos); + if (npos!=size-1) cimg_std::memcpy(new_data+npos+1,data+npos,sizeof(CImg)*(size-1-npos)); + if (shared && img) { + new_data[npos].width = img.width; new_data[npos].height = img.height; new_data[npos].depth = img.depth; + new_data[npos].dim = img.dim; new_data[npos].is_shared = true; new_data[npos].data = img.data; + } else { + new_data[npos].width = new_data[npos].height = new_data[npos].depth = new_data[npos].dim = 0; new_data[npos].data = 0; + new_data[npos] = img; + } + cimg_std::memset(data,0,sizeof(CImg)*(size-1)); + delete[] data; + data = new_data; + } else { + if (npos!=size-1) cimg_std::memmove(data+npos+1,data+npos,sizeof(CImg)*(size-1-npos)); + if (shared && img) { + data[npos].width = img.width; data[npos].height = img.height; data[npos].depth = img.depth; data[npos].dim = img.dim; + data[npos].is_shared = true; data[npos].data = img.data; + } else { + data[npos].width = data[npos].height = data[npos].depth = data[npos].dim = 0; data[npos].data = 0; + data[npos] = img; + } + } + } + return *this; + } + + // The two functions below are necessary due to Visual C++ 6.0 function overloading bugs, when + // default parameters are used in function signatures. + template + CImgList& insert(const CImg& img, const unsigned int pos) { + return insert(img,pos,false); + } + + //! Insert a copy of the image \p img into the current image list, at position \p pos. + template + CImgList& insert(const CImg& img) { + return insert(img,~0U,false); + } + + template + CImgList get_insert(const CImg& img, const unsigned int pos=~0U, const bool shared=false) const { + return (+*this).insert(img,pos,shared); + } + + //! Insert n empty images img into the current image list, at position \p pos. + CImgList& insert(const unsigned int n, const unsigned int pos=~0U) { + CImg foo; + if (!n) return *this; + const unsigned int npos = pos==~0U?size:pos; + for (unsigned int i=0; i get_insert(const unsigned int n, const unsigned int pos=~0U) const { + return (+*this).insert(n,pos); + } + + //! Insert n copies of the image \p img into the current image list, at position \p pos. + template + CImgList& insert(const unsigned int n, const CImg& img, const unsigned int pos=~0U, const bool shared=false) { + if (!n) return *this; + const unsigned int npos = pos==~0U?size:pos; + insert(img,npos,shared); + for (unsigned int i=1; i + CImgList get_insert(const unsigned int n, const CImg& img, const unsigned int pos=~0U, const bool shared=false) const { + return (+*this).insert(n,img,pos,shared); + } + + //! Insert a copy of the image list \p list into the current image list, starting from position \p pos. + template + CImgList& insert(const CImgList& list, const unsigned int pos=~0U, const bool shared=false) { + const unsigned int npos = pos==~0U?size:pos; + if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos+l,shared); + else insert(CImgList(list),npos,shared); + return *this; + } + + template + CImgList get_insert(const CImgList& list, const unsigned int pos=~0U, const bool shared=false) const { + return (+*this).insert(list,pos,shared); + } + + //! Insert n copies of the list \p list at position \p pos of the current list. + template + CImgList& insert(const unsigned int n, const CImgList& list, const unsigned int pos=~0U, const bool shared=false) { + if (!n) return *this; + const unsigned int npos = pos==~0U?size:pos; + for (unsigned int i=0; i + CImgList get_insert(const unsigned int n, const CImgList& list, const unsigned int pos=~0U, const bool shared=false) const { + return (+*this).insert(n,list,pos,shared); + } + + //! Insert a copy of the image \p img at the end of the current image list. + template + CImgList& operator<<(const CImg& img) { + return insert(img); + } + + //! Insert a copy of the image list \p list at the end of the current image list. + template + CImgList& operator<<(const CImgList& list) { + return insert(list); + } + + //! Return a copy of the current image list, where the image \p img has been inserted at the end. + template + CImgList& operator>>(CImg& img) const { + typedef typename cimg::superset::type Tt; + return CImgList(*this).insert(img); + } + + //! Insert a copy of the current image list at the beginning of the image list \p list. + template + CImgList& operator>>(CImgList& list) const { + return list.insert(*this,0); + } + + //! Remove the images at positions \p pos1 to \p pos2 from the image list. + CImgList& remove(const unsigned int pos1, const unsigned int pos2) { + const unsigned int + npos1 = pos1=size) + cimg::warn("CImgList<%s>::remove() : Cannot remove images from a list (%p,%u), at positions %u->%u.", + pixel_type(),data,size,npos1,tpos2); + else { + if (tpos2>=size) + cimg::warn("CImgList<%s>::remove() : Cannot remove all images from a list (%p,%u), at positions %u->%u.", + pixel_type(),data,size,npos1,tpos2); + for (unsigned int k = npos1; k<=npos2; ++k) data[k].assign(); + const unsigned int nb = 1 + npos2 - npos1; + if (!(size-=nb)) return assign(); + if (size>(allocsize>>2) || allocsize<=8) { // Removing items without reallocation. + if (npos1!=size) cimg_std::memmove(data+npos1,data+npos2+1,sizeof(CImg)*(size-npos1)); + cimg_std::memset(data+size,0,sizeof(CImg)*nb); + } else { // Removing items with reallocation. + allocsize>>=2; + while (allocsize>8 && size<(allocsize>>1)) allocsize>>=1; + CImg *new_data = new CImg[allocsize]; + if (npos1) cimg_std::memcpy(new_data,data,sizeof(CImg)*npos1); + if (npos1!=size) cimg_std::memcpy(new_data+npos1,data+npos2+1,sizeof(CImg)*(size-npos1)); + if (size!=allocsize) cimg_std::memset(new_data+size,0,sizeof(allocsize-size)); + cimg_std::memset(data,0,sizeof(CImg)*(size+nb)); + delete[] data; + data = new_data; + } + } + return *this; + } + + CImgList get_remove(const unsigned int pos1, const unsigned int pos2) const { + return (+*this).remove(pos1,pos2); + } + + //! Remove the image at position \p pos from the image list. + CImgList& remove(const unsigned int pos) { + return remove(pos,pos); + } + + CImgList get_remove(const unsigned int pos) const { + return (+*this).remove(pos); + } + + //! Remove the last image from the image list. + CImgList& remove() { + if (size) return remove(size-1); + else cimg::warn("CImgList<%s>::remove() : List is empty", + pixel_type()); + return *this; + } + + CImgList get_remove() const { + return (+*this).remove(); + } + + //! Reverse list order. + CImgList& reverse() { + for (unsigned int l=0; l get_reverse() const { + return (+*this).reverse(); + } + + //! Get a sub-list. + CImgList& crop(const unsigned int i0, const unsigned int i1, const bool shared=false) { + return get_crop(i0,i1,shared).transfer_to(*this); + } + + CImgList get_crop(const unsigned int i0, const unsigned int i1, const bool shared=false) const { + if (i0>i1 || i1>=size) + throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)", + pixel_type(),i0,i1,size,data); + CImgList res(i1-i0+1); + cimglist_for(res,l) res[l].assign((*this)[i0+l],shared); + return res; + } + + //! Get sub-images of a sublist. + CImgList& crop(const unsigned int i0, const unsigned int i1, + const int x0, const int y0, const int z0, const int v0, + const int x1, const int y1, const int z1, const int v1) { + return get_crop(i0,i1,x0,y0,z0,v0,x1,y1,z1,v1).transfer_to(*this); + } + + CImgList get_crop(const unsigned int i0, const unsigned int i1, + const int x0, const int y0, const int z0, const int v0, + const int x1, const int y1, const int z1, const int v1) const { + if (i0>i1 || i1>=size) + throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)", + pixel_type(),i0,i1,size,data); + CImgList res(i1-i0+1); + cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,z0,v0,x1,y1,z1,v1); + return res; + } + + //! Get sub-images of a sublist. + CImgList& crop(const unsigned int i0, const unsigned int i1, + const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1) { + return get_crop(i0,i1,x0,y0,z0,x1,y1,z1).transfer_to(*this); + } + + CImgList get_crop(const unsigned int i0, const unsigned int i1, + const int x0, const int y0, const int z0, + const int x1, const int y1, const int z1) const { + if (i0>i1 || i1>=size) + throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)", + pixel_type(),i0,i1,size,data); + CImgList res(i1-i0+1); + cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,z0,x1,y1,z1); + return res; + } + + //! Get sub-images of a sublist. + CImgList& crop(const unsigned int i0, const unsigned int i1, + const int x0, const int y0, + const int x1, const int y1) { + return get_crop(i0,i1,x0,y0,x1,y1).transfer_to(*this); + } + + CImgList get_crop(const unsigned int i0, const unsigned int i1, + const int x0, const int y0, + const int x1, const int y1) const { + if (i0>i1 || i1>=size) + throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)", + pixel_type(),i0,i1,size,data); + CImgList res(i1-i0+1); + cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,x1,y1); + return res; + } + + //! Get sub-images of a sublist. + CImgList& crop(const unsigned int i0, const unsigned int i1, + const int x0, const int x1) { + return get_crop(i0,i1,x0,x1).transfer_to(*this); + } + + CImgList get_crop(const unsigned int i0, const unsigned int i1, + const int x0, const int x1) const { + if (i0>i1 || i1>=size) + throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)", + pixel_type(),i0,i1,size,data); + CImgList res(i1-i0+1); + cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,x1); + return res; + } + + //! Display an image list into a CImgDisplay. + const CImgList& operator>>(CImgDisplay& disp) const { + return display(disp); + } + + //! Insert image \p img at the end of the list. + template + CImgList& push_back(const CImg& img) { + return insert(img); + } + + //! Insert image \p img at the front of the list. + template + CImgList& push_front(const CImg& img) { + return insert(img,0); + } + + //! Insert list \p list at the end of the current list. + template + CImgList& push_back(const CImgList& list) { + return insert(list); + } + + //! Insert list \p list at the front of the current list. + template + CImgList& push_front(const CImgList& list) { + return insert(list,0); + } + + //! Remove last element of the list. + CImgList& pop_back() { + return remove(size-1); + } + + //! Remove first element of the list. + CImgList& pop_front() { + return remove(0); + } + + //! Remove the element pointed by iterator \p iter. + CImgList& erase(const iterator iter) { + return remove(iter-data); + } + + //@} + //---------------------------- + // + //! \name Fourier Transforms + //@{ + //---------------------------- + + //! Compute the Fast Fourier Transform (along the specified axis). + CImgList& FFT(const char axis, const bool invert=false) { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::FFT() : Instance list (%u,%p) is empty", + pixel_type(),size,data); + if (!data[0]) + throw CImgInstanceException("CImgList<%s>::FFT() : Real part (%u,%u,%u,%u,%p) is empty", + pixel_type(),data[0].width,data[0].height,data[0].depth,data[0].dim,data[0].data); + if (size>2) + cimg::warn("CImgList<%s>::FFT() : Instance list (%u,%p) have more than 2 images", + pixel_type(),size,data); + if (size==1) insert(CImg(data[0].width,data[0].height,data[0].depth,data[0].dim,0)); + CImg &Ir = data[0], &Ii = data[1]; + if (Ir.width!=Ii.width || Ir.height!=Ii.height || Ir.depth!=Ii.depth || Ir.dim!=Ii.dim) + throw CImgInstanceException("CImgList<%s>::FFT() : Real part (%u,%u,%u,%u,%p) and imaginary part (%u,%u,%u,%u,%p)" + "have different dimensions", + pixel_type(),Ir.width,Ir.height,Ir.depth,Ir.dim,Ir.data,Ii.width,Ii.height,Ii.depth,Ii.dim,Ii.data); + +#ifdef cimg_use_fftw3 + fftw_complex *data_in; + fftw_plan data_plan; + + switch (cimg::uncase(axis)) { + case 'x' : { + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*Ir.width); + data_plan = fftw_plan_dft_1d(Ir.width,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + cimg_forYZV(Ir,y,z,k) { + T *ptrr = Ir.ptr(0,y,z,k), *ptri = Ii.ptr(0,y,z,k); + double *ptrd = (double*)data_in; + cimg_forX(Ir,x) { *(ptrd++) = (double)*(ptrr++); *(ptrd++) = (double)*(ptri++); } + fftw_execute(data_plan); + const unsigned int fact = Ir.width; + if (invert) { cimg_forX(Ir,x) { *(--ptri) = (T)(*(--ptrd)/fact); *(--ptrr) = (T)(*(--ptrd)/fact); }} + else { cimg_forX(Ir,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); }} + } + } break; + + case 'y' : { + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.height); + data_plan = fftw_plan_dft_1d(Ir.height,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + const unsigned int off = Ir.width; + cimg_forXZV(Ir,x,z,k) { + T *ptrr = Ir.ptr(x,0,z,k), *ptri = Ii.ptr(x,0,z,k); + double *ptrd = (double*)data_in; + cimg_forY(Ir,y) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } + fftw_execute(data_plan); + const unsigned int fact = Ir.height; + if (invert) { cimg_forY(Ir,y) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }} + else { cimg_forY(Ir,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }} + } + } break; + + case 'z' : { + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.depth); + data_plan = fftw_plan_dft_1d(Ir.depth,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + const unsigned int off = Ir.width*Ir.height; + cimg_forXYV(Ir,x,y,k) { + T *ptrr = Ir.ptr(x,y,0,k), *ptri = Ii.ptr(x,y,0,k); + double *ptrd = (double*)data_in; + cimg_forZ(Ir,z) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } + fftw_execute(data_plan); + const unsigned int fact = Ir.depth; + if (invert) { cimg_forZ(Ir,z) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }} + else { cimg_forZ(Ir,z) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }} + } + } break; + + case 'v' : { + data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.dim); + data_plan = fftw_plan_dft_1d(Ir.dim,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + const unsigned int off = Ir.width*Ir.height*Ir.depth; + cimg_forXYZ(Ir,x,y,z) { + T *ptrr = Ir.ptr(x,y,z,0), *ptri = Ii.ptr(x,y,z,0); + double *ptrd = (double*)data_in; + cimg_forV(Ir,k) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } + fftw_execute(data_plan); + const unsigned int fact = Ir.dim; + if (invert) { cimg_forV(Ir,k) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }} + else { cimg_forV(Ir,k) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }} + } + } break; + } + + fftw_destroy_plan(data_plan); + fftw_free(data_in); +#else + switch (cimg::uncase(axis)) { + case 'x' : { // Fourier along X + const unsigned int N = Ir.width, N2 = (N>>1); + if (((N-1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image along 'x' is %d != 2^N", + pixel_type(),N); + for (unsigned int i=0, j=0; ii) cimg_forYZV(Ir,y,z,v) { cimg::swap(Ir(i,y,z,v),Ir(j,y,z,v)); cimg::swap(Ii(i,y,z,v),Ii(j,y,z,v)); + if (j=m; j-=m, m=n, n>>=1) {} + } + for (unsigned int delta=2; delta<=N; delta<<=1) { + const unsigned int delta2 = (delta>>1); + for (unsigned int i=0; i>1); + if (((N-1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image(s) along 'y' is %d != 2^N", + pixel_type(),N); + for (unsigned int i=0, j=0; ii) cimg_forXZV(Ir,x,z,v) { cimg::swap(Ir(x,i,z,v),Ir(x,j,z,v)); cimg::swap(Ii(x,i,z,v),Ii(x,j,z,v)); + if (j=m; j-=m, m=n, n>>=1) {} + } + for (unsigned int delta=2; delta<=N; delta<<=1) { + const unsigned int delta2 = (delta>>1); + for (unsigned int i=0; i>1); + if (((N-1)&N) && N!=1) + throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image(s) along 'z' is %d != 2^N", + pixel_type(),N); + for (unsigned int i=0, j=0; ii) cimg_forXYV(Ir,x,y,v) { cimg::swap(Ir(x,y,i,v),Ir(x,y,j,v)); cimg::swap(Ii(x,y,i,v),Ii(x,y,j,v)); + if (j=m; j-=m, m=n, n>>=1) {} + } + for (unsigned int delta=2; delta<=N; delta<<=1) { + const unsigned int delta2 = (delta>>1); + for (unsigned int i=0; i::FFT() : Invalid axis '%c', must be 'x','y' or 'z'."); + } +#endif + return *this; + } + + CImgList get_FFT(const char axis, const bool invert=false) const { + return CImgList(*this).FFT(axis,invert); + } + + //! Compute the Fast Fourier Transform of a complex image. + CImgList& FFT(const bool invert=false) { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::FFT() : Instance list (%u,%p) is empty", + pixel_type(),size,data); + if (size>2) + cimg::warn("CImgList<%s>::FFT() : Instance list (%u,%p) have more than 2 images", + pixel_type(),size,data); + if (size==1) insert(CImg(data->width,data->height,data->depth,data->dim,0)); + CImg &Ir = data[0], &Ii = data[1]; + if (Ii.width!=Ir.width || Ii.height!=Ir.height || Ii.depth!=Ir.depth || Ii.dim!=Ir.dim) + throw CImgInstanceException("CImgList<%s>::FFT() : Real (%u,%u,%u,%u,%p) and Imaginary (%u,%u,%u,%u,%p) parts " + "of the instance image have different dimensions", + pixel_type(),Ir.width,Ir.height,Ir.depth,Ir.dim,Ir.data, + Ii.width,Ii.height,Ii.depth,Ii.dim,Ii.data); +#ifdef cimg_use_fftw3 + fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.width*Ir.height*Ir.depth); + fftw_plan data_plan; + const unsigned int w = Ir.width, wh = w*Ir.height, whd = wh*Ir.depth; + data_plan = fftw_plan_dft_3d(Ir.width,Ir.height,Ir.depth,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + cimg_forV(Ir,k) { + T *ptrr = Ir.ptr(0,0,0,k), *ptri = Ii.ptr(0,0,0,k); + double *ptrd = (double*)data_in; + for (unsigned int x = 0; x1) FFT('z',invert); + if (Ir.height>1) FFT('y',invert); + if (Ir.width>1) FFT('x',invert); +#endif + return *this; + } + + CImgList get_FFT(const bool invert=false) const { + return CImgList(*this).FFT(invert); + } + + // Return a list where each image has been split along the specified axis. + CImgList& split(const char axis) { + return get_split(axis).transfer_to(*this); + } + + CImgList get_split(const char axis) const { + CImgList res; + cimglist_for(*this,l) { + CImgList tmp = data[l].get_split(axis); + const unsigned int pos = res.size; + res.insert(tmp.size); + cimglist_for(tmp,i) tmp[i].transfer_to(data[pos+i]); + } + return res; + } + + //! Return a single image which is the concatenation of all images of the current CImgList instance. + /** + \param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'v'. + \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom). + \return A CImg image corresponding to the concatenation is returned. + **/ + CImg get_append(const char axis, const char align='p') const { + if (is_empty()) return CImg(); + if (size==1) return +((*this)[0]); + unsigned int dx = 0, dy = 0, dz = 0, dv = 0, pos = 0; + CImg res; + switch (cimg::uncase(axis)) { + case 'x' : { + switch (cimg::uncase(align)) { + case 'x' : { dy = dz = dv = 1; cimglist_for(*this,l) dx+=(*this)[l].size(); } break; + case 'y' : { dx = size; dz = dv = 1; cimglist_for(*this,l) dy = cimg::max(dy,(unsigned int)(*this)[l].size()); } break; + case 'z' : { dx = size; dy = dv = 1; cimglist_for(*this,l) dz = cimg::max(dz,(unsigned int)(*this)[l].size()); } break; + case 'v' : { dx = size; dy = dz = 1; cimglist_for(*this,l) dv = cimg::max(dz,(unsigned int)(*this)[l].size()); } break; + default : + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + dx += img.width; + dy = cimg::max(dy,img.height); + dz = cimg::max(dz,img.depth); + dv = cimg::max(dv,img.dim); + } + } + res.assign(dx,dy,dz,dv,0); + switch (cimg::uncase(align)) { + case 'x' : { + cimglist_for(*this,l) { + res.draw_image(pos,CImg((*this)[l],true).unroll('x')); + pos+=(*this)[l].size(); + } + } break; + case 'y' : { + cimglist_for(*this,l) res.draw_image(pos++,CImg((*this)[l],true).unroll('y')); + } break; + case 'z' : { + cimglist_for(*this,l) res.draw_image(pos++,CImg((*this)[l],true).unroll('z')); + } break; + case 'v' : { + cimglist_for(*this,l) res.draw_image(pos++,CImg((*this)[l],true).unroll('v')); + } break; + case 'p' : { + cimglist_for(*this,l) { res.draw_image(pos,(*this)[l]); pos+=(*this)[l].width; } + } break; + case 'n' : { + cimglist_for(*this,l) { + res.draw_image(pos,dy-(*this)[l].height,dz-(*this)[l].depth,dv-(*this)[l].dim,(*this)[l]); + pos+=(*this)[l].width; + } + } break; + default : { + cimglist_for(*this,l) { + res.draw_image(pos,(dy-(*this)[l].height)/2,(dz-(*this)[l].depth)/2,(dv-(*this)[l].dim)/2,(*this)[l]); + pos+=(*this)[l].width; + } + } break; + } + } break; + + case 'y' : { + switch (cimg::uncase(align)) { + case 'x' : { dy = size; dz = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break; + case 'y' : { dx = dz = dv = 1; cimglist_for(*this,l) dy+=(*this)[l].size(); } break; + case 'z' : { dy = size; dx = dv = 1; cimglist_for(*this,l) dz = cimg::max(dz,(unsigned int)(*this)[l].size()); } break; + case 'v' : { dy = size; dx = dz = 1; cimglist_for(*this,l) dv = cimg::max(dv,(unsigned int)(*this)[l].size()); } break; + default : + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + dx = cimg::max(dx,img.width); + dy += img.height; + dz = cimg::max(dz,img.depth); + dv = cimg::max(dv,img.dim); + } + } + res.assign(dx,dy,dz,dv,0); + switch (cimg::uncase(align)) { + case 'x' : { + cimglist_for(*this,l) res.draw_image(0,++pos,CImg((*this)[l],true).unroll('x')); + } break; + case 'y' : { + cimglist_for(*this,l) { + res.draw_image(0,pos,CImg((*this)[l],true).unroll('y')); + pos+=(*this)[l].size(); + } + } break; + case 'z' : { + cimglist_for(*this,l) res.draw_image(0,pos++,CImg((*this)[l],true).unroll('z')); + } break; + case 'v' : { + cimglist_for(*this,l) res.draw_image(0,pos++,CImg((*this)[l],true).unroll('v')); + } break; + case 'p' : { + cimglist_for(*this,l) { res.draw_image(0,pos,(*this)[l]); pos+=(*this)[l].height; } + } break; + case 'n' : { + cimglist_for(*this,l) { + res.draw_image(dx-(*this)[l].width,pos,dz-(*this)[l].depth,dv-(*this)[l].dim,(*this)[l]); + pos+=(*this)[l].height; + } + } break; + default : { + cimglist_for(*this,l) { + res.draw_image((dx-(*this)[l].width)/2,pos,(dz-(*this)[l].depth)/2,(dv-(*this)[l].dim)/2,(*this)[l]); + pos+=(*this)[l].height; + } + } break; + } + } break; + + case 'z' : { + switch (cimg::uncase(align)) { + case 'x' : { dz = size; dy = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break; + case 'y' : { dz = size; dx = dv = 1; cimglist_for(*this,l) dy = cimg::max(dz,(unsigned int)(*this)[l].size()); } break; + case 'z' : { dx = dy = dv = 1; cimglist_for(*this,l) dz+=(*this)[l].size(); } break; + case 'v' : { dz = size; dx = dz = 1; cimglist_for(*this,l) dv = cimg::max(dv,(unsigned int)(*this)[l].size()); } break; + default : + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + dx = cimg::max(dx,img.width); + dy = cimg::max(dy,img.height); + dz += img.depth; + dv = cimg::max(dv,img.dim); + } + } + res.assign(dx,dy,dz,dv,0); + switch (cimg::uncase(align)) { + case 'x' : { + cimglist_for(*this,l) res.draw_image(0,0,pos++,CImg((*this)[l],true).unroll('x')); + } break; + case 'y' : { + cimglist_for(*this,l) res.draw_image(0,0,pos++,CImg((*this)[l],true).unroll('y')); + } break; + case 'z' : { + cimglist_for(*this,l) { + res.draw_image(0,0,pos,CImg((*this)[l],true).unroll('z')); + pos+=(*this)[l].size(); + } + } break; + case 'v' : { + cimglist_for(*this,l) res.draw_image(0,0,pos++,CImg((*this)[l],true).unroll('v')); + } break; + case 'p' : { + cimglist_for(*this,l) { res.draw_image(0,0,pos,(*this)[l]); pos+=(*this)[l].depth; } + } break; + case 'n' : { + cimglist_for(*this,l) { + res.draw_image(dx-(*this)[l].width,dy-(*this)[l].height,pos,dv-(*this)[l].dim,(*this)[l]); + pos+=(*this)[l].depth; + } + } break; + case 'c' : { + cimglist_for(*this,l) { + res.draw_image((dx-(*this)[l].width)/2,(dy-(*this)[l].height)/2,pos,(dv-(*this)[l].dim)/2,(*this)[l]); + pos+=(*this)[l].depth; + } + } break; + } + } break; + + case 'v' : { + switch (cimg::uncase(align)) { + case 'x' : { dv = size; dy = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break; + case 'y' : { dv = size; dx = dv = 1; cimglist_for(*this,l) dy = cimg::max(dz,(unsigned int)(*this)[l].size()); } break; + case 'z' : { dv = size; dx = dv = 1; cimglist_for(*this,l) dz = cimg::max(dv,(unsigned int)(*this)[l].size()); } break; + case 'v' : { dx = dy = dz = 1; cimglist_for(*this,l) dv+=(*this)[l].size(); } break; + default : + cimglist_for(*this,l) { + const CImg& img = (*this)[l]; + dx = cimg::max(dx,img.width); + dy = cimg::max(dy,img.height); + dz = cimg::max(dz,img.depth); + dv += img.dim; + } + } + res.assign(dx,dy,dz,dv,0); + switch (cimg::uncase(align)) { + case 'x' : { + cimglist_for(*this,l) res.draw_image(0,0,0,pos++,CImg((*this)[l],true).unroll('x')); + } break; + case 'y' : { + cimglist_for(*this,l) res.draw_image(0,0,0,pos++,CImg((*this)[l],true).unroll('y')); + } break; + case 'z' : { + cimglist_for(*this,l) res.draw_image(0,0,0,pos++,CImg((*this)[l],true).unroll('v')); + } break; + case 'v' : { + cimglist_for(*this,l) { + res.draw_image(0,0,0,pos,CImg((*this)[l],true).unroll('z')); + pos+=(*this)[l].size(); + } + } break; + case 'p' : { + cimglist_for(*this,l) { res.draw_image(0,0,0,pos,(*this)[l]); pos+=(*this)[l].dim; } + } break; + case 'n' : { + cimglist_for(*this,l) { + res.draw_image(dx-(*this)[l].width,dy-(*this)[l].height,dz-(*this)[l].depth,pos,(*this)[l]); + pos+=(*this)[l].dim; + } + } break; + case 'c' : { + cimglist_for(*this,l) { + res.draw_image((dx-(*this)[l].width)/2,(dy-(*this)[l].height)/2,(dz-(*this)[l].depth)/2,pos,(*this)[l]); + pos+=(*this)[l].dim; + } + } break; + } + } break; + default : + throw CImgArgumentException("CImgList<%s>::get_append() : unknow axis '%c', must be 'x','y','z' or 'v'", + pixel_type(),axis); + } + return res; + } + + //! Create an auto-cropped font (along the X axis) from a input font \p font. + CImgList& crop_font() { + return get_crop_font().transfer_to(*this); + } + + CImgList get_crop_font() const { + CImgList res; + cimglist_for(*this,l) { + const CImg& letter = (*this)[l]; + int xmin = letter.width, xmax = 0; + cimg_forXY(letter,x,y) if (letter(x,y)) { if (xxmax) xmax=x; } + if (xmin>xmax) res.insert(CImg(letter.width,letter.height,1,letter.dim,0)); + else res.insert(letter.get_crop(xmin,0,xmax,letter.height-1)); + } + res[' '].resize(res['f'].width); + res[' '+256].resize(res['f'].width); + return res; + } + + //! Invert primitives orientation of a 3D object. + CImgList& invert_object3d() { + cimglist_for(*this,l) { + CImg& p = data[l]; + const unsigned int siz = p.size(); + if (siz==2 || siz==3 || siz==6 || siz==9) cimg::swap(p[0],p[1]); + else if (siz==4 || siz==12) cimg::swap(p[0],p[3],p[1],p[2]); + } + return *this; + } + + CImgList get_invert_object3d() const { + return (+*this).invert_object3d(); + } + + //! Return a CImg pre-defined font with desired size. + /** + \param font_height = height of the desired font (can be 11,13,24,38 or 57) + \param fixed_size = tell if the font has a fixed or variable width. + **/ + static CImgList font(const unsigned int font_width, const bool variable_size=true) { + if (font_width<=11) { + static CImgList font7x11, nfont7x11; + if (!variable_size && !font7x11) font7x11 = _font(cimg::font7x11,7,11,1,0,false); + if (variable_size && !nfont7x11) nfont7x11 = _font(cimg::font7x11,7,11,1,0,true); + return variable_size?nfont7x11:font7x11; + } + if (font_width<=13) { + static CImgList font10x13, nfont10x13; + if (!variable_size && !font10x13) font10x13 = _font(cimg::font10x13,10,13,1,0,false); + if (variable_size && !nfont10x13) nfont10x13 = _font(cimg::font10x13,10,13,1,0,true); + return variable_size?nfont10x13:font10x13; + } + if (font_width<=17) { + static CImgList font8x17, nfont8x17; + if (!variable_size && !font8x17) font8x17 = _font(cimg::font8x17,8,17,1,0,false); + if (variable_size && !nfont8x17) nfont8x17 = _font(cimg::font8x17,8,17,1,0,true); + return variable_size?nfont8x17:font8x17; + } + if (font_width<=19) { + static CImgList font10x19, nfont10x19; + if (!variable_size && !font10x19) font10x19 = _font(cimg::font10x19,10,19,2,0,false); + if (variable_size && !nfont10x19) nfont10x19 = _font(cimg::font10x19,10,19,2,0,true); + return variable_size?nfont10x19:font10x19; + } + if (font_width<=24) { + static CImgList font12x24, nfont12x24; + if (!variable_size && !font12x24) font12x24 = _font(cimg::font12x24,12,24,2,0,false); + if (variable_size && !nfont12x24) nfont12x24 = _font(cimg::font12x24,12,24,2,0,true); + return variable_size?nfont12x24:font12x24; + } + if (font_width<=32) { + static CImgList font16x32, nfont16x32; + if (!variable_size && !font16x32) font16x32 = _font(cimg::font16x32,16,32,2,0,false); + if (variable_size && !nfont16x32) nfont16x32 = _font(cimg::font16x32,16,32,2,0,true); + return variable_size?nfont16x32:font16x32; + } + if (font_width<=38) { + static CImgList font19x38, nfont19x38; + if (!variable_size && !font19x38) font19x38 = _font(cimg::font19x38,19,38,3,0,false); + if (variable_size && !nfont19x38) nfont19x38 = _font(cimg::font19x38,19,38,3,0,true); + return variable_size?nfont19x38:font19x38; + } + static CImgList font29x57, nfont29x57; + if (!variable_size && !font29x57) font29x57 = _font(cimg::font29x57,29,57,5,0,false); + if (variable_size && !nfont29x57) nfont29x57 = _font(cimg::font29x57,29,57,5,0,true); + return variable_size?nfont29x57:font29x57; + } + + static CImgList _font(const unsigned int *const font, const unsigned int w, const unsigned int h, + const unsigned int paddingx, const unsigned int paddingy, const bool variable_size=true) { + CImgList res = CImgList(256,w,h,1,3).insert(CImgList(256,w,h,1,1)); + const unsigned int *ptr = font; + unsigned int m = 0, val = 0; + for (unsigned int y=0; y>=1; if (!m) { m = 0x80000000; val = *(ptr++); } + CImg& img = res[x/w], &mask = res[x/w+256]; + unsigned int xm = x%w; + img(xm,y,0) = img(xm,y,1) = img(xm,y,2) = mask(xm,y,0) = (T)((val&m)?1:0); + } + if (variable_size) res.crop_font(); + if (paddingx || paddingy) cimglist_for(res,l) res[l].resize(res[l].dimx()+paddingx, res[l].dimy()+paddingy,1,-100,0); + return res; + } + + //! Display the current CImgList instance in an existing CImgDisplay window (by reference). + /** + This function displays the list images of the current CImgList instance into an existing CImgDisplay window. + Images of the list are concatenated in a single temporarly image for visualization purposes. + The function returns immediately. + \param disp : reference to an existing CImgDisplay instance, where the current image list will be displayed. + \param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'v'. + \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom). + \return A reference to the current CImgList instance is returned. + **/ + const CImgList& display(CImgDisplay& disp, const char axis='x', const char align='p') const { + get_append(axis,align).display(disp); + return *this; + } + + //! Display the current CImgList instance in a new display window. + /** + This function opens a new window with a specific title and displays the list images of the current CImgList instance into it. + Images of the list are concatenated in a single temporarly image for visualization purposes. + The function returns when a key is pressed or the display window is closed by the user. + \param title : specify the title of the opening display window. + \param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'v'. + \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom). + \return A reference to the current CImgList instance is returned. + **/ + const CImgList& display(CImgDisplay &disp, + const bool display_info, const char axis='x', const char align='p') const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::display() : Instance list (%u,%u) is empty.", + pixel_type(),size,data); + const CImg visu = get_append(axis,align); + if (display_info) print(disp.title); + visu.display(disp,false); + return *this; + } + + //! Display the current CImgList instance in a new display window. + const CImgList& display(const char *const title=0, + const bool display_info=true, const char axis='x', const char align='p') const { + const CImg visu = get_append(axis,align); + char ntitle[64] = { 0 }; + if (!title) cimg_std::sprintf(ntitle,"CImgList<%s>",pixel_type()); + if (display_info) print(title?title:ntitle); + visu.display(title?title:ntitle,false); + return *this; + } + + //@} + //---------------------------------- + // + //! \name Input-Output + //@{ + //---------------------------------- + + //! Return a C-string containing the values of all images in the instance list. + CImg value_string(const char separator=',', const unsigned int max_size=0) const { + if (is_empty()) return CImg(1,1,1,1,0); + CImgList items; + for (unsigned int l = 0; l item = data[l].value_string(separator,0); + item[item.size()-1] = separator; + items.insert(item); + } + items.insert(data[size-1].value_string(separator,0)); + CImg res = items.get_append('x'); + if (max_size) { res.crop(0,max_size); res(max_size) = 0; } + return res; + } + + //! Print informations about the list on the standard output. + const CImgList& print(const char* title=0, const bool display_stats=true) const { + unsigned long msiz = 0; + cimglist_for(*this,l) msiz += data[l].size(); + msiz*=sizeof(T); + const unsigned int mdisp = msiz<8*1024?0:(msiz<8*1024*1024?1:2); + char ntitle[64] = { 0 }; + if (!title) cimg_std::sprintf(ntitle,"CImgList<%s>",pixel_type()); + cimg_std::fprintf(cimg_stdout,"%s: this = %p, size = %u [%lu %s], data = (CImg<%s>*)%p.\n", + title?title:ntitle,(void*)this,size, + mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)), + mdisp==0?"b":(mdisp==1?"Kb":"Mb"), + pixel_type(),(void*)data); + char tmp[16] = { 0 }; + cimglist_for(*this,ll) { + cimg_std::sprintf(tmp,"[%d]",ll); + cimg_std::fprintf(cimg_stdout," "); + data[ll].print(tmp,display_stats); + if (ll==3 && size>8) { ll = size-5; cimg_std::fprintf(cimg_stdout," ...\n"); } + } + return *this; + } + + //! Load an image list from a file. + CImgList& load(const char *const filename) { + const char *ext = cimg::split_filename(filename); + const unsigned int odebug = cimg::exception_mode(); + cimg::exception_mode() = 0; + assign(); + try { +#ifdef cimglist_load_plugin + cimglist_load_plugin(filename); +#endif +#ifdef cimglist_load_plugin1 + cimglist_load_plugin1(filename); +#endif +#ifdef cimglist_load_plugin2 + cimglist_load_plugin2(filename); +#endif +#ifdef cimglist_load_plugin3 + cimglist_load_plugin3(filename); +#endif +#ifdef cimglist_load_plugin4 + cimglist_load_plugin4(filename); +#endif +#ifdef cimglist_load_plugin5 + cimglist_load_plugin5(filename); +#endif +#ifdef cimglist_load_plugin6 + cimglist_load_plugin6(filename); +#endif +#ifdef cimglist_load_plugin7 + cimglist_load_plugin7(filename); +#endif +#ifdef cimglist_load_plugin8 + cimglist_load_plugin8(filename); +#endif + if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) load_tiff(filename); + if (!cimg::strcasecmp(ext,"cimg") || + !cimg::strcasecmp(ext,"cimgz") || + !ext[0]) load_cimg(filename); + if (!cimg::strcasecmp(ext,"rec") || + !cimg::strcasecmp(ext,"par")) load_parrec(filename); + if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) load_ffmpeg(filename); + if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename); + if (is_empty()) throw CImgIOException("CImgList<%s>::load()",pixel_type()); + } catch (CImgIOException& e) { + if (!cimg::strncasecmp(e.message,"cimg::fopen()",13)) { + cimg::exception_mode() = odebug; + throw CImgIOException("CImgList<%s>::load() : File '%s' cannot be opened.",pixel_type(),filename); + } else try { + assign(1); + data->load(filename); + } catch (CImgException&) { + assign(); + } + } + cimg::exception_mode() = odebug; + if (is_empty()) + throw CImgIOException("CImgList<%s>::load() : File '%s', format not recognized.",pixel_type(),filename); + return *this; + } + + static CImgList get_load(const char *const filename) { + return CImgList().load(filename); + } + + //! Load an image list from a .cimg file. + CImgList& load_cimg(const char *const filename) { + return _load_cimg(0,filename); + } + + static CImgList get_load_cimg(const char *const filename) { + return CImgList().load_cimg(filename); + } + + //! Load an image list from a .cimg file. + CImgList& load_cimg(cimg_std::FILE *const file) { + return _load_cimg(file,0); + } + + static CImgList get_load_cimg(cimg_std::FILE *const file) { + return CImgList().load_cimg(file); + } + + CImgList& _load_cimg(cimg_std::FILE *const file, const char *const filename) { +#ifdef cimg_use_zlib +#define _cimgz_load_cimg_case(Tss) { \ + Bytef *const cbuf = new Bytef[csiz]; \ + cimg::fread(cbuf,csiz,nfile); \ + raw.assign(W,H,D,V); \ + unsigned long destlen = raw.size()*sizeof(T); \ + uncompress((Bytef*)raw.data,&destlen,cbuf,csiz); \ + delete[] cbuf; \ + const Tss *ptrs = raw.data; \ + for (unsigned int off = raw.size(); off; --off) *(ptrd++) = (T)*(ptrs++); \ +} +#else +#define _cimgz_load_cimg_case(Tss) \ + throw CImgIOException("CImgList<%s>::load_cimg() : File '%s' contains compressed data, zlib must be used",\ + pixel_type(),filename?filename:"(FILE*)"); +#endif + +#define _cimg_load_cimg_case(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l=0) tmp[j++] = (char)i; tmp[j] = '\0'; \ + W = H = D = V = 0; csiz = 0; \ + if ((err = cimg_std::sscanf(tmp,"%u %u %u %u #%u",&W,&H,&D,&V,&csiz))<4) \ + throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \ + pixel_type(),filename?filename:("(FILE*)"),W,H,D,V); \ + if (W*H*D*V>0) { \ + CImg raw; \ + CImg &img = data[l]; \ + img.assign(W,H,D,V); \ + T *ptrd = img.data; \ + if (err==5) _cimgz_load_cimg_case(Tss) \ + else for (int toread = (int)img.size(); toread>0; ) { \ + raw.assign(cimg::min(toread,cimg_iobuffer)); \ + cimg::fread(raw.data,raw.width,nfile); \ + if (endian!=cimg::endianness()) cimg::invert_endianness(raw.data,raw.width); \ + toread-=raw.width; \ + const Tss *ptrs = raw.data; \ + for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++); \ + } \ + } \ + } \ + loaded = true; \ + } + + if (!filename && !file) + throw CImgArgumentException("CImgList<%s>::load_cimg() : Cannot load (null) filename.", + pixel_type()); + typedef unsigned char uchar; + typedef unsigned short ushort; + typedef unsigned int uint; + typedef unsigned long ulong; + const int cimg_iobuffer = 12*1024*1024; + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool loaded = false, endian = cimg::endianness(); + char tmp[256], str_pixeltype[256], str_endian[256]; + unsigned int j, err, N = 0, W, H, D, V, csiz; + int i; + j = 0; while((i=cimg_std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = '\0'; + err = cimg_std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Unknow CImg RAW header.", + pixel_type(),filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + assign(N); + _cimg_load_cimg_case("bool",bool); + _cimg_load_cimg_case("unsigned_char",uchar); + _cimg_load_cimg_case("uchar",uchar); + _cimg_load_cimg_case("char",char); + _cimg_load_cimg_case("unsigned_short",ushort); + _cimg_load_cimg_case("ushort",ushort); + _cimg_load_cimg_case("short",short); + _cimg_load_cimg_case("unsigned_int",uint); + _cimg_load_cimg_case("uint",uint); + _cimg_load_cimg_case("int",int); + _cimg_load_cimg_case("unsigned_long",ulong); + _cimg_load_cimg_case("ulong",ulong); + _cimg_load_cimg_case("long",long); + _cimg_load_cimg_case("float",float); + _cimg_load_cimg_case("double",double); + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', cannot read images of pixels coded as '%s'.", + pixel_type(),filename?filename:"(FILE*)",str_pixeltype); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load a sub-image list from a non compressed .cimg file. + CImgList& load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0, + const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) { + return _load_cimg(0,filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1); + } + + static CImgList get_load_cimg(const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0, + const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) { + return CImgList().load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1); + } + + //! Load a sub-image list from a non compressed .cimg file. + CImgList& load_cimg(cimg_std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0, + const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) { + return _load_cimg(file,0,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1); + } + + static CImgList get_load_cimg(cimg_std::FILE *const file, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0, + const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) { + return CImgList().load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1); + } + + CImgList& _load_cimg(cimg_std::FILE *const file, const char *const filename, + const unsigned int n0, const unsigned int n1, + const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0, + const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) { +#define _cimg_load_cimg_case2(Ts,Tss) \ + if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l = 0; l<=nn1; ++l) { \ + j = 0; while ((i=cimg_std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = '\0'; \ + W = H = D = V = 0; \ + if (cimg_std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&V)!=4) \ + throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \ + pixel_type(), filename?filename:("(FILE*)"), W, H, D, V); \ + if (W*H*D*V>0) { \ + if (l=W || y0>=H || z0>=D || v0>=D) cimg_std::fseek(nfile,W*H*D*V*sizeof(Tss),SEEK_CUR); \ + else { \ + const unsigned int \ + nx1 = x1>=W?W-1:x1, \ + ny1 = y1>=H?H-1:y1, \ + nz1 = z1>=D?D-1:z1, \ + nv1 = v1>=V?V-1:v1; \ + CImg raw(1+nx1-x0); \ + CImg &img = data[l-n0]; \ + img.assign(1+nx1-x0,1+ny1-y0,1+nz1-z0,1+nv1-v0); \ + T *ptrd = img.data; \ + const unsigned int skipvb = v0*W*H*D*sizeof(Tss); \ + if (skipvb) cimg_std::fseek(nfile,skipvb,SEEK_CUR); \ + for (unsigned int v=1+nv1-v0; v; --v) { \ + const unsigned int skipzb = z0*W*H*sizeof(Tss); \ + if (skipzb) cimg_std::fseek(nfile,skipzb,SEEK_CUR); \ + for (unsigned int z=1+nz1-z0; z; --z) { \ + const unsigned int skipyb = y0*W*sizeof(Tss); \ + if (skipyb) cimg_std::fseek(nfile,skipyb,SEEK_CUR); \ + for (unsigned int y=1+ny1-y0; y; --y) { \ + const unsigned int skipxb = x0*sizeof(Tss); \ + if (skipxb) cimg_std::fseek(nfile,skipxb,SEEK_CUR); \ + cimg::fread(raw.data,raw.width,nfile); \ + if (endian!=cimg::endianness()) cimg::invert_endianness(raw.data,raw.width); \ + const Tss *ptrs = raw.data; \ + for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++); \ + const unsigned int skipxe = (W-1-nx1)*sizeof(Tss); \ + if (skipxe) cimg_std::fseek(nfile,skipxe,SEEK_CUR); \ + } \ + const unsigned int skipye = (H-1-ny1)*W*sizeof(Tss); \ + if (skipye) cimg_std::fseek(nfile,skipye,SEEK_CUR); \ + } \ + const unsigned int skipze = (D-1-nz1)*W*H*sizeof(Tss); \ + if (skipze) cimg_std::fseek(nfile,skipze,SEEK_CUR); \ + } \ + const unsigned int skipve = (V-1-nv1)*W*H*D*sizeof(Tss); \ + if (skipve) cimg_std::fseek(nfile,skipve,SEEK_CUR); \ + } \ + } \ + } \ + loaded = true; \ + } + + if (!filename && !file) + throw CImgArgumentException("CImgList<%s>::load_cimg() : Cannot load (null) filename.", + pixel_type()); + typedef unsigned char uchar; + typedef unsigned short ushort; + typedef unsigned int uint; + typedef unsigned long ulong; + if (n1::load_cimg() : File '%s', Bad sub-region coordinates [%u->%u] " + "(%u,%u,%u,%u)->(%u,%u,%u,%u).", + pixel_type(),filename?filename:"(FILE*)", + n0,n1,x0,y0,z0,v0,x1,y1,z1,v1); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool loaded = false, endian = cimg::endianness(); + char tmp[256], str_pixeltype[256], str_endian[256]; + unsigned int j, err, N, W, H, D, V; + int i; + j = 0; while((i=cimg_std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = '\0'; + err = cimg_std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Unknow CImg RAW header.", + pixel_type(),filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + const unsigned int nn1 = n1>=N?N-1:n1; + assign(1+nn1-n0); + _cimg_load_cimg_case2("bool",bool); + _cimg_load_cimg_case2("unsigned_char",uchar); + _cimg_load_cimg_case2("uchar",uchar); + _cimg_load_cimg_case2("char",char); + _cimg_load_cimg_case2("unsigned_short",ushort); + _cimg_load_cimg_case2("ushort",ushort); + _cimg_load_cimg_case2("short",short); + _cimg_load_cimg_case2("unsigned_int",uint); + _cimg_load_cimg_case2("uint",uint); + _cimg_load_cimg_case2("int",int); + _cimg_load_cimg_case2("unsigned_long",ulong); + _cimg_load_cimg_case2("ulong",ulong); + _cimg_load_cimg_case2("long",long); + _cimg_load_cimg_case2("float",float); + _cimg_load_cimg_case2("double",double); + if (!loaded) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', cannot read images of pixels coded as '%s'.", + pixel_type(),filename?filename:"(FILE*)",str_pixeltype); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image list from a PAR/REC (Philips) file. + CImgList& load_parrec(const char *const filename) { + if (!filename) + throw CImgArgumentException("CImgList<%s>::load_parrec() : Cannot load (null) filename.", + pixel_type()); + char body[1024], filenamepar[1024], filenamerec[1024]; + const char *ext = cimg::split_filename(filename,body); + if (!cimg::strcmp(ext,"par")) { cimg_std::strcpy(filenamepar,filename); cimg_std::sprintf(filenamerec,"%s.rec",body); } + if (!cimg::strcmp(ext,"PAR")) { cimg_std::strcpy(filenamepar,filename); cimg_std::sprintf(filenamerec,"%s.REC",body); } + if (!cimg::strcmp(ext,"rec")) { cimg_std::strcpy(filenamerec,filename); cimg_std::sprintf(filenamepar,"%s.par",body); } + if (!cimg::strcmp(ext,"REC")) { cimg_std::strcpy(filenamerec,filename); cimg_std::sprintf(filenamepar,"%s.PAR",body); } + cimg_std::FILE *file = cimg::fopen(filenamepar,"r"); + + // Parse header file + CImgList st_slices; + CImgList st_global; + int err; + char line[256] = { 0 }; + do { err=cimg_std::fscanf(file,"%255[^\n]%*c",line); } while (err!=EOF && (line[0]=='#' || line[0]=='.')); + do { + unsigned int sn,sizex,sizey,pixsize; + float rs,ri,ss; + err = cimg_std::fscanf(file,"%u%*u%*u%*u%*u%*u%*u%u%*u%u%u%g%g%g%*[^\n]",&sn,&pixsize,&sizex,&sizey,&ri,&rs,&ss); + if (err==7) { + st_slices.insert(CImg::vector((float)sn,(float)pixsize,(float)sizex,(float)sizey, + ri,rs,ss,0)); + unsigned int i; for (i=0; i::vector(sizex,sizey,sn)); + else { + CImg &vec = st_global[i]; + if (sizex>vec[0]) vec[0] = sizex; + if (sizey>vec[1]) vec[1] = sizey; + vec[2] = sn; + } + st_slices[st_slices.size-1][7] = (float)i; + } + } while (err==7); + + // Read data + cimg_std::FILE *file2 = cimg::fopen(filenamerec,"rb"); + { cimglist_for(st_global,l) { + const CImg& vec = st_global[l]; + insert(CImg(vec[0],vec[1],vec[2])); + }} + + cimglist_for(st_slices,l) { + const CImg& vec = st_slices[l]; + const unsigned int + sn = (unsigned int)vec[0]-1, + pixsize = (unsigned int)vec[1], + sizex = (unsigned int)vec[2], + sizey = (unsigned int)vec[3], + imn = (unsigned int)vec[7]; + const float ri = vec[4], rs = vec[5], ss = vec[6]; + switch (pixsize) { + case 8 : { + CImg buf(sizex,sizey); + cimg::fread(buf.data,sizex*sizey,file2); + if (cimg::endianness()) cimg::invert_endianness(buf.data,sizex*sizey); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + case 16 : { + CImg buf(sizex,sizey); + cimg::fread(buf.data,sizex*sizey,file2); + if (cimg::endianness()) cimg::invert_endianness(buf.data,sizex*sizey); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + case 32 : { + CImg buf(sizex,sizey); + cimg::fread(buf.data,sizex*sizey,file2); + if (cimg::endianness()) cimg::invert_endianness(buf.data,sizex*sizey); + CImg& img = (*this)[imn]; + cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss)); + } break; + default : + cimg::fclose(file); + cimg::fclose(file2); + throw CImgIOException("CImg<%s>::load_parrec() : File '%s', cannot handle image with pixsize = %d bits.", + pixel_type(),filename,pixsize); + } + } + cimg::fclose(file); + cimg::fclose(file2); + if (!size) + throw CImgIOException("CImg<%s>::load_parrec() : File '%s' does not appear to be a valid PAR-REC file.", + pixel_type(),filename); + return *this; + } + + static CImgList get_load_parrec(const char *const filename) { + return CImgList().load_parrec(filename); + } + + //! Load an image sequence from a YUV file. + CImgList& load_yuv(const char *const filename, + const unsigned int sizex, const unsigned int sizey, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, const bool yuv2rgb=true) { + return _load_yuv(0,filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb); + } + + static CImgList get_load_yuv(const char *const filename, + const unsigned int sizex, const unsigned int sizey=1, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, const bool yuv2rgb=true) { + return CImgList().load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb); + } + + //! Load an image sequence from a YUV file. + CImgList& load_yuv(cimg_std::FILE *const file, + const unsigned int sizex, const unsigned int sizey, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, const bool yuv2rgb=true) { + return _load_yuv(file,0,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb); + } + + static CImgList get_load_yuv(cimg_std::FILE *const file, + const unsigned int sizex, const unsigned int sizey=1, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, const bool yuv2rgb=true) { + return CImgList().load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb); + } + + CImgList& _load_yuv(cimg_std::FILE *const file, const char *const filename, + const unsigned int sizex, const unsigned int sizey, + const unsigned int first_frame, const unsigned int last_frame, + const unsigned int step_frame, const bool yuv2rgb) { + if (!filename && !file) + throw CImgArgumentException("CImgList<%s>::load_yuv() : Cannot load (null) filename.", + pixel_type()); + if (sizex%2 || sizey%2) + throw CImgArgumentException("CImgList<%s>::load_yuv() : File '%s', image dimensions along X and Y must be " + "even numbers (given are %ux%u)\n", + pixel_type(),filename?filename:"(FILE*)",sizex,sizey); + if (!sizex || !sizey) + throw CImgArgumentException("CImgList<%s>::load_yuv() : File '%s', given image sequence size (%u,%u) is invalid", + pixel_type(),filename?filename:"(FILE*)",sizex,sizey); + + const unsigned int + nfirst_frame = first_frame tmp(sizex,sizey,1,3), UV(sizex/2,sizey/2,1,2); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); + bool stopflag = false; + int err; + if (nfirst_frame) { + err = cimg_std::fseek(nfile,nfirst_frame*(sizex*sizey + sizex*sizey/2),SEEK_CUR); + if (err) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImgList<%s>::load_yuv() : File '%s' doesn't contain frame number %u " + "(out of range error).", + pixel_type(),filename?filename:"(FILE*)",nfirst_frame); + } + } + unsigned int frame; + for (frame = nfirst_frame; !stopflag && frame<=nlast_frame; frame+=nstep_frame) { + tmp.fill(0); + // *TRY* to read the luminance part, do not replace by cimg::fread ! + err = (int)cimg_std::fread((void*)(tmp.data),1,(size_t)(tmp.width*tmp.height),nfile); + if (err!=(int)(tmp.width*tmp.height)) { + stopflag = true; + if (err>0) + cimg::warn("CImgList<%s>::load_yuv() : File '%s' contains incomplete data," + " or given image dimensions (%u,%u) are incorrect.", + pixel_type(),filename?filename:"(FILE*)",sizex,sizey); + } else { + UV.fill(0); + // *TRY* to read the luminance part, do not replace by cimg::fread ! + err = (int)cimg_std::fread((void*)(UV.data),1,(size_t)(UV.size()),nfile); + if (err!=(int)(UV.size())) { + stopflag = true; + if (err>0) + cimg::warn("CImgList<%s>::load_yuv() : File '%s' contains incomplete data," + " or given image dimensions (%u,%u) are incorrect.", + pixel_type(),filename?filename:"(FILE*)",sizex,sizey); + } else { + cimg_forXY(UV,x,y) { + const int x2 = x*2, y2 = y*2; + tmp(x2,y2,1) = tmp(x2+1,y2,1) = tmp(x2,y2+1,1) = tmp(x2+1,y2+1,1) = UV(x,y,0); + tmp(x2,y2,2) = tmp(x2+1,y2,2) = tmp(x2,y2+1,2) = tmp(x2+1,y2+1,2) = UV(x,y,1); + } + if (yuv2rgb) tmp.YCbCrtoRGB(); + insert(tmp); + if (nstep_frame>1) cimg_std::fseek(nfile,(nstep_frame-1)*(sizex*sizey + sizex*sizey/2),SEEK_CUR); + } + } + } + if (stopflag && nlast_frame!=~0U && frame!=nlast_frame) + cimg::warn("CImgList<%s>::load_yuv() : File '%s', frame %d not reached since only %u frames were found in the file.", + pixel_type(),filename?filename:"(FILE*)",nlast_frame,frame-1,filename); + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Load an image from a video file, using ffmpeg libraries. + // This piece of code has been firstly created by David Starweather (starkdg(at)users(dot)sourceforge(dot)net) + // I modified it afterwards for direct inclusion in the library core. + CImgList& load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false) { + if (!filename) + throw CImgArgumentException("CImgList<%s>::load_ffmpeg() : Cannot load (null) filename.", + pixel_type()); + const unsigned int + nfirst_frame = first_frame1) || (resume && (pixel_format || !pixel_format))) + throw CImgArgumentException("CImg<%s>::load_ffmpeg() : File '%s', reading sub-frames from a video file requires the use of ffmpeg.\n" + "('cimg_use_ffmpeg' must be defined).", + pixel_type(),filename); + return load_ffmpeg_external(filename); +#else + const unsigned int ffmpeg_pixfmt = pixel_format?PIX_FMT_RGB24:PIX_FMT_GRAY8; + avcodec_register_all(); + av_register_all(); + static AVFormatContext *format_ctx = 0; + static AVCodecContext *codec_ctx = 0; + static AVCodec *codec = 0; + static AVFrame *avframe = avcodec_alloc_frame(), *converted_frame = avcodec_alloc_frame(); + static int vstream = 0; + + if (resume) { + if (!format_ctx || !codec_ctx || !codec || !avframe || !converted_frame) + throw CImgArgumentException("CImgList<%s>::load_ffmpeg() : File '%s', cannot resume due to unallocated FFMPEG structures.", + pixel_type(),filename); + } else { + // Open video file, find main video stream and codec. + if (format_ctx) av_close_input_file(format_ctx); + if (av_open_input_file(&format_ctx,filename,0,0,0)!=0) + throw CImgIOException("CImgList<%s>::load_ffmpeg() : File '%s' cannot be opened.", + pixel_type(),filename); + if (!avframe || !converted_frame || av_find_stream_info(format_ctx)<0) { + av_close_input_file(format_ctx); format_ctx = 0; + cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot retrieve stream information.\n" + "Trying with external ffmpeg executable.", + pixel_type(),filename); + return load_ffmpeg_external(filename); + } +#if cimg_debug>=3 + dump_format(format_ctx,0,0,0); +#endif + + // Special command : Return informations on main video stream. + // as a vector 1x4 containing : (nb_frames,width,height,fps). + if (!first_frame && !last_frame && !step_frame) { + for (vstream = 0; vstream<(int)(format_ctx->nb_streams); ++vstream) + if (format_ctx->streams[vstream]->codec->codec_type==CODEC_TYPE_VIDEO) break; + if (vstream==(int)format_ctx->nb_streams) assign(); + else { + CImgList timestamps; + int nb_frames; + AVPacket packet; + // Count frames and store timestamps. + for (nb_frames = 0; av_read_frame(format_ctx,&packet)>=0; av_free_packet(&packet)) + if (packet.stream_index==vstream) { + timestamps.insert(CImg::vector((double)packet.pts)); + ++nb_frames; + } + // Get frame with, height and fps. + const int + framew = format_ctx->streams[vstream]->codec->width, + frameh = format_ctx->streams[vstream]->codec->height; + const float + num = (float)(format_ctx->streams[vstream]->r_frame_rate).num, + den = (float)(format_ctx->streams[vstream]->r_frame_rate).den, + fps = num/den; + // Return infos as a list. + assign(2); + (*this)[0].assign(1,4).fill((T)nb_frames,(T)framew,(T)frameh,(T)fps); + (*this)[1] = timestamps.get_append('y'); + } + av_close_input_file(format_ctx); format_ctx = 0; + return *this; + } + + for (vstream = 0; vstream<(int)(format_ctx->nb_streams) && + format_ctx->streams[vstream]->codec->codec_type!=CODEC_TYPE_VIDEO; ) ++vstream; + if (vstream==(int)format_ctx->nb_streams) { + cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot retrieve video stream.\n" + "Trying with external ffmpeg executable.", + pixel_type(),filename); + av_close_input_file(format_ctx); format_ctx = 0; + return load_ffmpeg_external(filename); + } + codec_ctx = format_ctx->streams[vstream]->codec; + codec = avcodec_find_decoder(codec_ctx->codec_id); + if (!codec) { + cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot find video codec.\n" + "Trying with external ffmpeg executable.", + pixel_type(),filename); + return load_ffmpeg_external(filename); + } + if (avcodec_open(codec_ctx,codec)<0) { // Open codec + cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot open video codec.\n" + "Trying with external ffmpeg executable.", + pixel_type(),filename); + return load_ffmpeg_external(filename); + } + } + + // Read video frames + const unsigned int numBytes = avpicture_get_size(ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height); + uint8_t *const buffer = new uint8_t[numBytes]; + avpicture_fill((AVPicture *)converted_frame,buffer,ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height); + const T foo = (T)0; + AVPacket packet; + for (unsigned int frame = 0, next_frame = nfirst_frame; frame<=nlast_frame && av_read_frame(format_ctx,&packet)>=0; ) { + if (packet.stream_index==(int)vstream) { + int decoded = 0; + avcodec_decode_video(codec_ctx,avframe,&decoded,packet.data,packet.size); + if (decoded) { + if (frame==next_frame) { + SwsContext *c = sws_getContext(codec_ctx->width,codec_ctx->height,codec_ctx->pix_fmt,codec_ctx->width, + codec_ctx->height,ffmpeg_pixfmt,1,0,0,0); + sws_scale(c,avframe->data,avframe->linesize,0,codec_ctx->height,converted_frame->data,converted_frame->linesize); + if (ffmpeg_pixfmt==PIX_FMT_RGB24) { + CImg next_image(*converted_frame->data,3,codec_ctx->width,codec_ctx->height,1,true); + insert(next_image._get_permute_axes("yzvx",foo)); + } else { + CImg next_image(*converted_frame->data,1,codec_ctx->width,codec_ctx->height,1,true); + insert(next_image._get_permute_axes("yzvx",foo)); + } + next_frame+=nstep_frame; + } + ++frame; + } + av_free_packet(&packet); + if (next_frame>nlast_frame) break; + } + } + delete[] buffer; +#endif + return *this; + } + + static CImgList get_load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, const bool pixel_format=true) { + return CImgList().load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format); + } + + //! Load an image from a video file (MPEG,AVI) using the external tool 'ffmpeg'. + CImgList& load_ffmpeg_external(const char *const filename) { + if (!filename) + throw CImgArgumentException("CImgList<%s>::load_ffmpeg_external() : Cannot load (null) filename.", + pixel_type()); + char command[1024], filetmp[512], filetmp2[512]; + cimg_std::FILE *file = 0; + do { + cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand()); + cimg_std::sprintf(filetmp2,"%s_000001.ppm",filetmp); + if ((file=cimg_std::fopen(filetmp2,"rb"))!=0) cimg_std::fclose(file); + } while (file); + cimg_std::sprintf(filetmp2,"%s_%%6d.ppm",filetmp); +#if cimg_OS!=2 + cimg_std::sprintf(command,"%s -i \"%s\" %s >/dev/null 2>&1",cimg::ffmpeg_path(),filename,filetmp2); +#else + cimg_std::sprintf(command,"\"%s -i \"%s\" %s\" >NUL 2>&1",cimg::ffmpeg_path(),filename,filetmp2); +#endif + cimg::system(command,0); + const unsigned int odebug = cimg::exception_mode(); + cimg::exception_mode() = 0; + assign(); + unsigned int i = 1; + for (bool stopflag = false; !stopflag; ++i) { + cimg_std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,i); + CImg img; + try { img.load_pnm(filetmp2); } + catch (CImgException&) { stopflag = true; } + if (img) { insert(img); cimg_std::remove(filetmp2); } + } + cimg::exception_mode() = odebug; + if (is_empty()) + throw CImgIOException("CImgList<%s>::load_ffmpeg_external() : Failed to open image sequence '%s'.\n" + "Check the filename and if the 'ffmpeg' tool is installed on your system.", + pixel_type(),filename); + return *this; + } + + static CImgList get_load_ffmpeg_external(const char *const filename) { + return CImgList().load_ffmpeg_external(filename); + } + + //! Load a gzipped list, using external tool 'gunzip'. + CImgList& load_gzip_external(const char *const filename) { + if (!filename) + throw CImgIOException("CImg<%s>::load_gzip_external() : Cannot load (null) filename.", + pixel_type()); + char command[1024], filetmp[512], body[512]; + const char + *ext = cimg::split_filename(filename,body), + *ext2 = cimg::split_filename(body,0); + cimg_std::FILE *file = 0; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand(),ext2); + else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand()); + } else { + if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand(),ext); + else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand()); + } + if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file); + } while (file); + cimg_std::sprintf(command,"%s -c \"%s\" > %s",cimg::gunzip_path(),filename,filetmp); + cimg::system(command); + if (!(file = cimg_std::fopen(filetmp,"rb"))) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException("CImg<%s>::load_gzip_external() : File '%s' cannot be opened.", + pixel_type(),filename); + } else cimg::fclose(file); + load(filetmp); + cimg_std::remove(filetmp); + return *this; + } + + static CImgList get_load_gzip_external(const char *const filename) { + return CImgList().load_gzip_external(filename); + } + + //! Load a 3D object from a .OFF file. + template + CImgList& load_off(const char *const filename, + CImgList& primitives, CImgList& colors, + const bool invert_faces=false) { + return get_load_off(filename,primitives,colors,invert_faces).transfer_to(*this); + } + + template + static CImgList get_load_off(const char *const filename, + CImgList& primitives, CImgList& colors, + const bool invert_faces=false) { + return CImg().load_off(filename,primitives,colors,invert_faces).get_split('x'); + } + + //! Load a TIFF file. + CImgList& load_tiff(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1) { + const unsigned int + nfirst_frame = first_frame::load_tiff() : File '%s', reading sub-images from a tiff file requires the use of libtiff.\n" + "('cimg_use_tiff' must be defined).", + pixel_type(),filename); + return assign(CImg::get_load_tiff(filename)); +#else + TIFF *tif = TIFFOpen(filename,"r"); + if (tif) { + unsigned int nb_images = 0; + do ++nb_images; while (TIFFReadDirectory(tif)); + if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images)) + cimg::warn("CImgList<%s>::load_tiff() : File '%s' contains %u image(s), specified frame range is [%u,%u] (step %u).", + pixel_type(),filename,nb_images,nfirst_frame,nlast_frame,nstep_frame); + if (nfirst_frame>=nb_images) return assign(); + if (nlast_frame>=nb_images) nlast_frame = nb_images-1; + assign(1+(nlast_frame-nfirst_frame)/nstep_frame); + TIFFSetDirectory(tif,0); +#if cimg_debug>=3 + TIFFSetWarningHandler(0); + TIFFSetErrorHandler(0); +#endif + cimglist_for(*this,l) data[l]._load_tiff(tif,nfirst_frame+l*nstep_frame); + TIFFClose(tif); + } else throw CImgException("CImgList<%s>::load_tiff() : File '%s' cannot be opened.", + pixel_type(),filename); + return *this; +#endif + } + + static CImgList get_load_tiff(const char *const filename, + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1) { + return CImgList().load_tiff(filename,first_frame,last_frame,step_frame); + } + + //! Save an image list into a file. + /** + Depending on the extension of the given filename, a file format is chosen for the output file. + **/ + const CImgList& save(const char *const filename, const int number=-1) const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::save() : File '%s, instance list (%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",size,data); + if (!filename) + throw CImgArgumentException("CImg<%s>::save() : Instance list (%u,%p), specified filename is (null).", + pixel_type(),size,data); + const char *ext = cimg::split_filename(filename); + char nfilename[1024]; + const char *const fn = (number>=0)?cimg::number_filename(filename,number,6,nfilename):filename; +#ifdef cimglist_save_plugin + cimglist_save_plugin(fn); +#endif +#ifdef cimglist_save_plugin1 + cimglist_save_plugin1(fn); +#endif +#ifdef cimglist_save_plugin2 + cimglist_save_plugin2(fn); +#endif +#ifdef cimglist_save_plugin3 + cimglist_save_plugin3(fn); +#endif +#ifdef cimglist_save_plugin4 + cimglist_save_plugin4(fn); +#endif +#ifdef cimglist_save_plugin5 + cimglist_save_plugin5(fn); +#endif +#ifdef cimglist_save_plugin6 + cimglist_save_plugin6(fn); +#endif +#ifdef cimglist_save_plugin7 + cimglist_save_plugin7(fn); +#endif +#ifdef cimglist_save_plugin8 + cimglist_save_plugin8(fn); +#endif +#ifdef cimg_use_tiff + if (!cimg::strcasecmp(ext,"tif") || + !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn); +#endif + if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); + if (!cimg::strcasecmp(ext,"cimg") || !ext[0]) return save_cimg(fn,false); + if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true); + if (!cimg::strcasecmp(ext,"avi") || + !cimg::strcasecmp(ext,"mov") || + !cimg::strcasecmp(ext,"asf") || + !cimg::strcasecmp(ext,"divx") || + !cimg::strcasecmp(ext,"flv") || + !cimg::strcasecmp(ext,"mpg") || + !cimg::strcasecmp(ext,"m1v") || + !cimg::strcasecmp(ext,"m2v") || + !cimg::strcasecmp(ext,"m4v") || + !cimg::strcasecmp(ext,"mjp") || + !cimg::strcasecmp(ext,"mkv") || + !cimg::strcasecmp(ext,"mpe") || + !cimg::strcasecmp(ext,"movie") || + !cimg::strcasecmp(ext,"ogm") || + !cimg::strcasecmp(ext,"qt") || + !cimg::strcasecmp(ext,"rm") || + !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"wmv") || + !cimg::strcasecmp(ext,"xvid") || + !cimg::strcasecmp(ext,"mpeg")) return save_ffmpeg(fn); + if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn); + if (size==1) data[0].save(fn,-1); else cimglist_for(*this,l) data[l].save(fn,l); + return *this; + } + + //! Save an image sequence, using FFMPEG library. + // This piece of code has been originally written by David. G. Starkweather. + const CImgList& save_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int fps=25) const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::save_ffmpeg() : File '%s', instance list (%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",size,data); + if (!filename) + throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : Instance list (%u,%p), specified filename is (null).", + pixel_type(),size,data); + if (!fps) + throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : File '%s', specified framerate is 0.", + pixel_type(),filename); + const unsigned int nlast_frame = last_frame==~0U?size-1:last_frame; + if (first_frame>=size || nlast_frame>=size) + throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : File '%s', specified frames [%u,%u] are out of list range (%u elements).", + pixel_type(),filename,first_frame,last_frame,size); + for (unsigned int ll = first_frame; ll<=nlast_frame; ++ll) if (!data[ll].is_sameXYZ(data[0])) + throw CImgInstanceException("CImgList<%s>::save_ffmpeg() : File '%s', images of the sequence have different dimensions.", + pixel_type(),filename); + +#ifndef cimg_use_ffmpeg + return save_ffmpeg_external(filename,first_frame,last_frame); +#else + avcodec_register_all(); + av_register_all(); + const int + frame_dimx = data[first_frame].dimx(), + frame_dimy = data[first_frame].dimy(), + frame_dimv = data[first_frame].dimv(); + if (frame_dimv!=1 && frame_dimv!=3) + throw CImgInstanceException("CImgList<%s>::save_ffmpeg() : File '%s', image[0] (%u,%u,%u,%u,%p) has not 1 or 3 channels.", + pixel_type(),filename,data[0].width,data[0].height,data[0].depth,data[0].dim,data); + + PixelFormat dest_pxl_fmt = PIX_FMT_YUV420P; + PixelFormat src_pxl_fmt = (frame_dimv == 3)?PIX_FMT_RGB24:PIX_FMT_GRAY8; + + int sws_flags = SWS_FAST_BILINEAR; // Interpolation method (keeping same size images for now). + AVOutputFormat *fmt = 0; + fmt = guess_format(0,filename,0); + if (!fmt) fmt = guess_format("mpeg",0,0); // Default format "mpeg". + if (!fmt) + throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : File '%s', could not determine file format from filename.", + pixel_type(),filename); + + AVFormatContext *oc = 0; + oc = av_alloc_format_context(); + if (!oc) // Failed to allocate format context. + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate structure for format context.", + pixel_type(),filename); + + AVCodec *codec = 0; + AVFrame *picture = 0; + AVFrame *tmp_pict = 0; + oc->oformat = fmt; + cimg_std::sprintf(oc->filename,"%s",filename); + + // Add video stream. + int stream_index = 0; + AVStream *video_str = 0; + if (fmt->video_codec!=CODEC_ID_NONE) { + video_str = av_new_stream(oc,stream_index); + if (!video_str) { // Failed to allocate stream. + av_free(oc); + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate video stream structure.", + pixel_type(),filename); + } + } else { // No codec identified. + av_free(oc); + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', no proper codec identified.", + pixel_type(),filename); + } + + AVCodecContext *c = video_str->codec; + c->codec_id = fmt->video_codec; + c->codec_type = CODEC_TYPE_VIDEO; + c->bit_rate = 400000; + c->width = frame_dimx; + c->height = frame_dimy; + c->time_base.num = 1; + c->time_base.den = fps; + c->gop_size = 12; + c->pix_fmt = dest_pxl_fmt; + if (c->codec_id == CODEC_ID_MPEG2VIDEO) c->max_b_frames = 2; + if (c->codec_id == CODEC_ID_MPEG1VIDEO) c->mb_decision = 2; + + if (av_set_parameters(oc,0)<0) { // Parameters not properly set. + av_free(oc); + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', parameters for avcodec not properly set.", + pixel_type(),filename); + } + + // Open codecs and alloc buffers. + codec = avcodec_find_encoder(c->codec_id); + if (!codec) { // Failed to find codec. + av_free(oc); + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', no codec found.", + pixel_type(),filename); + } + if (avcodec_open(c,codec)<0) // Failed to open codec. + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to open codec.", + pixel_type(),filename); + tmp_pict = avcodec_alloc_frame(); + if (!tmp_pict) { // Failed to allocate memory for tmp_pict frame. + avcodec_close(video_str->codec); + av_free(oc); + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for data buffer.", + pixel_type(),filename); + } + tmp_pict->linesize[0] = (src_pxl_fmt==PIX_FMT_RGB24)?3*frame_dimx:frame_dimx; + tmp_pict->type = FF_BUFFER_TYPE_USER; + int tmp_size = avpicture_get_size(src_pxl_fmt,frame_dimx,frame_dimy); + uint8_t *tmp_buffer = (uint8_t*)av_malloc(tmp_size); + if (!tmp_buffer) { // Failed to allocate memory for tmp buffer. + av_free(tmp_pict); + avcodec_close(video_str->codec); + av_free(oc); + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for data buffer.", + pixel_type(),filename); + } + + // Associate buffer with tmp_pict. + avpicture_fill((AVPicture*)tmp_pict,tmp_buffer,src_pxl_fmt,frame_dimx,frame_dimy); + picture = avcodec_alloc_frame(); + if (!picture) { // Failed to allocate picture frame. + av_free(tmp_pict->data[0]); + av_free(tmp_pict); + avcodec_close(video_str->codec); + av_free(oc); + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for picture frame.", + pixel_type(),filename); + } + + int size = avpicture_get_size(c->pix_fmt,frame_dimx,frame_dimy); + uint8_t *buffer = (uint8_t*)av_malloc(size); + if (!buffer) { // Failed to allocate picture frame buffer. + av_free(picture); + av_free(tmp_pict->data[0]); + av_free(tmp_pict); + avcodec_close(video_str->codec); + av_free(oc); + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for picture frame buffer.", + pixel_type(),filename); + } + + // Associate the buffer with picture. + avpicture_fill((AVPicture*)picture,buffer,c->pix_fmt,frame_dimx,frame_dimy); + + // Open file. + if (!(fmt->flags&AVFMT_NOFILE)) { + if (url_fopen(&oc->pb,filename,URL_WRONLY)<0) + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s' cannot be opened.", + pixel_type(),filename); + } + + if (av_write_header(oc)<0) + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', could not write header.", + pixel_type(),filename); + double video_pts; + SwsContext *img_convert_context = 0; + img_convert_context = sws_getContext(frame_dimx,frame_dimy,src_pxl_fmt, + c->width,c->height,c->pix_fmt,sws_flags,0,0,0); + if (!img_convert_context) { // Failed to get swscale context. + // if (!(fmt->flags & AVFMT_NOFILE)) url_fclose(&oc->pb); + av_free(picture->data); + av_free(picture); + av_free(tmp_pict->data[0]); + av_free(tmp_pict); + avcodec_close(video_str->codec); + av_free(oc); + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%', failed to get conversion context.", + pixel_type(),filename); + } + int ret = 0, out_size; + uint8_t *video_outbuf = 0; + int video_outbuf_size = 1000000; + video_outbuf = (uint8_t*)av_malloc(video_outbuf_size); + if (!video_outbuf) { + // if (!(fmt->flags & AVFMT_NOFILE)) url_fclose(&oc->pb); + av_free(picture->data); + av_free(picture); + av_free(tmp_pict->data[0]); + av_free(tmp_pict); + avcodec_close(video_str->codec); + av_free(oc); + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', memory allocation error.", + pixel_type(),filename); + } + + // Loop through each desired image in list. + for (unsigned int i = first_frame; i<=nlast_frame; ++i) { + CImg currentIm = data[i], red, green, blue, gray; + if (src_pxl_fmt == PIX_FMT_RGB24) { + red = currentIm.get_shared_channel(0); + green = currentIm.get_shared_channel(1); + blue = currentIm.get_shared_channel(2); + cimg_forXY(currentIm,X,Y) { // Assign pizel values to data buffer in interlaced RGBRGB ... format. + tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X] = red(X,Y); + tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X + 1] = green(X,Y); + tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X + 2] = blue(X,Y); + } + } else { + gray = currentIm.get_shared_channel(0); + cimg_forXY(currentIm,X,Y) tmp_pict->data[0][Y*tmp_pict->linesize[0] + X] = gray(X,Y); + } + + if (video_str) video_pts = (video_str->pts.val * video_str->time_base.num)/(video_str->time_base.den); + else video_pts = 0.0; + if (!video_str) break; + if (sws_scale(img_convert_context,tmp_pict->data,tmp_pict->linesize,0,c->height,picture->data,picture->linesize)<0) break; + out_size = avcodec_encode_video(c,video_outbuf,video_outbuf_size,picture); + if (out_size>0) { + AVPacket pkt; + av_init_packet(&pkt); + pkt.pts = av_rescale_q(c->coded_frame->pts,c->time_base,video_str->time_base); + if (c->coded_frame->key_frame) pkt.flags|=PKT_FLAG_KEY; + pkt.stream_index = video_str->index; + pkt.data = video_outbuf; + pkt.size = out_size; + ret = av_write_frame(oc,&pkt); + } else if (out_size<0) break; + if (ret) break; // Error occured in writing frame. + } + + // Close codec. + if (video_str) { + avcodec_close(video_str->codec); + av_free(picture->data[0]); + av_free(picture); + av_free(tmp_pict->data[0]); + av_free(tmp_pict); + } + if (av_write_trailer(oc)<0) + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to write trailer.", + pixel_type(),filename); + av_freep(&oc->streams[stream_index]->codec); + av_freep(&oc->streams[stream_index]); + if (!(fmt->flags&AVFMT_NOFILE)) { + /*if (url_fclose(oc->pb)<0) + throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to close file.", + pixel_type(),filename); + */ + } + av_free(oc); + av_free(video_outbuf); +#endif + return *this; + } + + // Save an image sequence into a YUV file (internal). + const CImgList& _save_yuv(cimg_std::FILE *const file, const char *const filename, const bool rgb2yuv) const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::save_yuv() : File '%s', instance list (%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",size,data); + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_yuv() : Instance list (%u,%p), specified file is (null).", + pixel_type(),size,data); + if ((*this)[0].dimx()%2 || (*this)[0].dimy()%2) + throw CImgInstanceException("CImgList<%s>::save_yuv() : File '%s', image dimensions must be even numbers (current are %ux%u).", + pixel_type(),filename?filename:"(FILE*)",(*this)[0].dimx(),(*this)[0].dimy()); + + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + cimglist_for(*this,l) { + CImg YCbCr((*this)[l]); + if (rgb2yuv) YCbCr.RGBtoYCbCr(); + cimg::fwrite(YCbCr.data,YCbCr.width*YCbCr.height,nfile); + cimg::fwrite(YCbCr.get_resize(YCbCr.width/2, YCbCr.height/2,1,3,3).ptr(0,0,0,1), + YCbCr.width*YCbCr.height/2,nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save an image sequence into a YUV file. + const CImgList& save_yuv(const char *const filename=0, const bool rgb2yuv=true) const { + return _save_yuv(0,filename,rgb2yuv); + } + + //! Save an image sequence into a YUV file. + const CImgList& save_yuv(cimg_std::FILE *const file, const bool rgb2yuv=true) const { + return _save_yuv(file,0,rgb2yuv); + } + + //! Save an image list into a .cimg file. + /** + A CImg RAW file is a simple uncompressed binary file that may be used to save list of CImg images. + \param filename : name of the output file. + \return A reference to the current CImgList instance is returned. + **/ + const CImgList& _save_cimg(cimg_std::FILE *const file, const char *const filename, const bool compression) const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::save_cimg() : File '%s', instance list (%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",size,data); + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_cimg() : Instance list (%u,%p), specified file is (null).", + pixel_type(),size,data); + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little"; + if (cimg_std::strstr(ptype,"unsigned")==ptype) cimg_std::fprintf(nfile,"%u unsigned_%s %s_endian\n",size,ptype+9,etype); + else cimg_std::fprintf(nfile,"%u %s %s_endian\n",size,ptype,etype); + cimglist_for(*this,l) { + const CImg& img = data[l]; + cimg_std::fprintf(nfile,"%u %u %u %u",img.width,img.height,img.depth,img.dim); + if (img.data) { + CImg tmp; + if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp.data,tmp.size()); } + const CImg& ref = cimg::endianness()?tmp:img; + bool compressed = false; + if (compression) { +#ifdef cimg_use_zlib + const unsigned long siz = sizeof(T)*ref.size(); + unsigned long csiz = siz + siz/10 + 16; + Bytef *const cbuf = new Bytef[csiz]; + if (compress(cbuf,&csiz,(Bytef*)ref.data,siz)) { + cimg::warn("CImgList<%s>::save_cimg() : File '%s', failed to save compressed data.\n Data will be saved uncompressed.", + pixel_type(),filename?filename:"(FILE*)"); + compressed = false; + } else { + cimg_std::fprintf(nfile," #%lu\n",csiz); + cimg::fwrite(cbuf,csiz,nfile); + delete[] cbuf; + compressed = true; + } +#else + cimg::warn("CImgList<%s>::save_cimg() : File '%s', cannot save compressed data unless zlib is used " + "('cimg_use_zlib' must be defined).\n Data will be saved uncompressed.", + pixel_type(),filename?filename:"(FILE*)"); + compressed = false; +#endif + } + if (!compressed) { + cimg_std::fputc('\n',nfile); + cimg::fwrite(ref.data,ref.size(),nfile); + } + } else cimg_std::fputc('\n',nfile); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Save an image list into a CImg file (RAW binary file + simple header) + const CImgList& save_cimg(cimg_std::FILE *file, const bool compress=false) const { + return _save_cimg(file,0,compress); + } + + //! Save an image list into a CImg file (RAW binary file + simple header) + const CImgList& save_cimg(const char *const filename, const bool compress=false) const { + return _save_cimg(0,filename,compress); + } + + // Insert the instance image into into an existing .cimg file, at specified coordinates. + const CImgList& _save_cimg(cimg_std::FILE *const file, const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int v0) const { +#define _cimg_save_cimg_case(Ts,Tss) \ + if (!saved && !cimg::strcasecmp(Ts,str_pixeltype)) { \ + for (unsigned int l=0; l::save_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \ + pixel_type(), filename?filename:("(FILE*)"), W, H, D, V); \ + if (W*H*D*V>0) { \ + if (l=W || y0>=H || z0>=D || v0>=D) cimg_std::fseek(nfile,W*H*D*V*sizeof(Tss),SEEK_CUR); \ + else { \ + const CImg& img = (*this)[l-n0]; \ + const T *ptrs = img.data; \ + const unsigned int \ + x1 = x0 + img.width - 1, \ + y1 = y0 + img.height - 1, \ + z1 = z0 + img.depth - 1, \ + v1 = v0 + img.dim - 1, \ + nx1 = x1>=W?W-1:x1, \ + ny1 = y1>=H?H-1:y1, \ + nz1 = z1>=D?D-1:z1, \ + nv1 = v1>=V?V-1:v1; \ + CImg raw(1+nx1-x0); \ + const unsigned int skipvb = v0*W*H*D*sizeof(Tss); \ + if (skipvb) cimg_std::fseek(nfile,skipvb,SEEK_CUR); \ + for (unsigned int v=1+nv1-v0; v; --v) { \ + const unsigned int skipzb = z0*W*H*sizeof(Tss); \ + if (skipzb) cimg_std::fseek(nfile,skipzb,SEEK_CUR); \ + for (unsigned int z=1+nz1-z0; z; --z) { \ + const unsigned int skipyb = y0*W*sizeof(Tss); \ + if (skipyb) cimg_std::fseek(nfile,skipyb,SEEK_CUR); \ + for (unsigned int y=1+ny1-y0; y; --y) { \ + const unsigned int skipxb = x0*sizeof(Tss); \ + if (skipxb) cimg_std::fseek(nfile,skipxb,SEEK_CUR); \ + raw.assign(ptrs, raw.width); \ + ptrs+=img.width; \ + if (endian) cimg::invert_endianness(raw.data,raw.width); \ + cimg::fwrite(raw.data,raw.width,nfile); \ + const unsigned int skipxe = (W-1-nx1)*sizeof(Tss); \ + if (skipxe) cimg_std::fseek(nfile,skipxe,SEEK_CUR); \ + } \ + const unsigned int skipye = (H-1-ny1)*W*sizeof(Tss); \ + if (skipye) cimg_std::fseek(nfile,skipye,SEEK_CUR); \ + } \ + const unsigned int skipze = (D-1-nz1)*W*H*sizeof(Tss); \ + if (skipze) cimg_std::fseek(nfile,skipze,SEEK_CUR); \ + } \ + const unsigned int skipve = (V-1-nv1)*W*H*D*sizeof(Tss); \ + if (skipve) cimg_std::fseek(nfile,skipve,SEEK_CUR); \ + } \ + } \ + } \ + saved = true; \ + } + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::save_cimg() : File '%s', instance list (%u,%p) is empty.", + pixel_type(),filename?filename:"(FILE*)",size,data); + if (!file && !filename) + throw CImgArgumentException("CImg<%s>::save_cimg() : Instance list (%u,%p), specified file is (null).", + pixel_type(),size,data); + typedef unsigned char uchar; + typedef unsigned short ushort; + typedef unsigned int uint; + typedef unsigned long ulong; + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb+"); + bool saved = false, endian = cimg::endianness(); + char tmp[256], str_pixeltype[256], str_endian[256]; + unsigned int j, err, N, W, H, D, V; + int i; + j = 0; while((i=cimg_std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = '\0'; + err = cimg_std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian); + if (err<2) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', Unknow CImg RAW header.", + pixel_type(),filename?filename:"(FILE*)"); + } + if (!cimg::strncasecmp("little",str_endian,6)) endian = false; + else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; + const unsigned int lmax = cimg::min(N,n0+size); + _cimg_save_cimg_case("bool",bool); + _cimg_save_cimg_case("unsigned_char",uchar); + _cimg_save_cimg_case("uchar",uchar); + _cimg_save_cimg_case("char",char); + _cimg_save_cimg_case("unsigned_short",ushort); + _cimg_save_cimg_case("ushort",ushort); + _cimg_save_cimg_case("short",short); + _cimg_save_cimg_case("unsigned_int",uint); + _cimg_save_cimg_case("uint",uint); + _cimg_save_cimg_case("int",int); + _cimg_save_cimg_case("unsigned_long",ulong); + _cimg_save_cimg_case("ulong",ulong); + _cimg_save_cimg_case("long",long); + _cimg_save_cimg_case("float",float); + _cimg_save_cimg_case("double",double); + if (!saved) { + if (!file) cimg::fclose(nfile); + throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', cannot save images of pixels coded as '%s'.", + pixel_type(),filename?filename:"(FILE*)",str_pixeltype); + } + if (!file) cimg::fclose(nfile); + return *this; + } + + //! Insert the instance image into into an existing .cimg file, at specified coordinates. + const CImgList& save_cimg(const char *const filename, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int v0) const { + return _save_cimg(0,filename,n0,x0,y0,z0,v0); + } + + //! Insert the instance image into into an existing .cimg file, at specified coordinates. + const CImgList& save_cimg(cimg_std::FILE *const file, + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int v0) const { + return _save_cimg(file,0,n0,x0,y0,z0,v0); + } + + // Create an empty .cimg file with specified dimensions (internal) + static void _save_empty_cimg(cimg_std::FILE *const file, const char *const filename, + const unsigned int nb, + const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dv) { + cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const unsigned int siz = dx*dy*dz*dv*sizeof(T); + cimg_std::fprintf(nfile,"%u %s\n",nb,pixel_type()); + for (unsigned int i=nb; i; --i) { + cimg_std::fprintf(nfile,"%u %u %u %u\n",dx,dy,dz,dv); + for (unsigned int off=siz; off; --off) cimg_std::fputc(0,nfile); + } + if (!file) cimg::fclose(nfile); + } + + //! Create an empty .cimg file with specified dimensions. + static void save_empty_cimg(const char *const filename, + const unsigned int nb, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dv=1) { + return _save_empty_cimg(0,filename,nb,dx,dy,dz,dv); + } + + //! Create an empty .cimg file with specified dimensions. + static void save_empty_cimg(cimg_std::FILE *const file, + const unsigned int nb, + const unsigned int dx, const unsigned int dy=1, + const unsigned int dz=1, const unsigned int dv=1) { + return _save_empty_cimg(file,0,nb,dx,dy,dz,dv); + } + + //! Save a file in TIFF format. +#ifdef cimg_use_tiff + const CImgList& save_tiff(const char *const filename) const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::save_tiff() : File '%s', instance list (%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",size,data); + if (!filename) + throw CImgArgumentException("CImgList<%s>::save_tiff() : Specified filename is (null) for instance list (%u,%p).", + pixel_type(),size,data); + TIFF *tif = TIFFOpen(filename,"w"); + if (tif) { + for (unsigned int dir=0, l=0; l& img = (*this)[l]; + if (img) { + if (img.depth==1) img._save_tiff(tif,dir++); + else cimg_forZ(img,z) img.get_slice(z)._save_tiff(tif,dir++); + } + } + TIFFClose(tif); + } else + throw CImgException("CImgList<%s>::save_tiff() : File '%s', error while opening stream for tiff file.", + pixel_type(),filename); + return *this; + } +#endif + + //! Save an image list as a gzipped file, using external tool 'gzip'. + const CImgList& save_gzip_external(const char *const filename) const { + if (!filename) + throw CImgIOException("CImg<%s>::save_gzip_external() : Cannot save (null) filename.", + pixel_type()); + char command[1024], filetmp[512], body[512]; + const char + *ext = cimg::split_filename(filename,body), + *ext2 = cimg::split_filename(body,0); + cimg_std::FILE *file; + do { + if (!cimg::strcasecmp(ext,"gz")) { + if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand(),ext2); + else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand()); + } else { + if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand(),ext); + else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/", + cimg::filenamerand()); + } + if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file); + } while (file); + save(filetmp); + cimg_std::sprintf(command,"%s -c %s > \"%s\"",cimg::gzip_path(),filetmp,filename); + cimg::system(command); + file = cimg_std::fopen(filename,"rb"); + if (!file) + throw CImgIOException("CImgList<%s>::save_gzip_external() : File '%s' cannot be saved.", + pixel_type(),filename); + else cimg::fclose(file); + cimg_std::remove(filetmp); + return *this; + } + + //! Save an image list into a OFF file. + template + const CImgList& save_off(const char *const filename, + const CImgList& primitives, const CImgList& colors, const bool invert_faces=false) const { + get_append('x','y').save_off(filename,primitives,colors,invert_faces); + return *this; + } + + //! Save an image list into a OFF file. + template + const CImgList& save_off(cimg_std::FILE *const file, + const CImgList& primitives, const CImgList& colors, const bool invert_faces=false) const { + get_append('x','y').save_off(file,primitives,colors,invert_faces); + return *this; + } + + //! Save an image sequence using the external tool 'ffmpeg'. + const CImgList& save_ffmpeg_external(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const char *const codec="mpeg2video") const { + if (is_empty()) + throw CImgInstanceException("CImgList<%s>::save_ffmpeg_external() : File '%s', instance list (%u,%p) is empty.", + pixel_type(),filename?filename:"(null)",size,data); + if (!filename) + throw CImgArgumentException("CImgList<%s>::save_ffmpeg_external() : Instance list (%u,%p), specified filename is (null).", + pixel_type(),size,data); + char command[1024], filetmp[512], filetmp2[512]; + cimg_std::FILE *file = 0; + const unsigned int nlast_frame = last_frame==~0U?size-1:last_frame; + if (first_frame>=size || nlast_frame>=size) + throw CImgArgumentException("CImgList<%s>::save_ffmpeg_external() : File '%s', specified frames [%u,%u] are out of list range (%u elements).", + pixel_type(),filename,first_frame,last_frame,size); + for (unsigned int ll = first_frame; ll<=nlast_frame; ++ll) if (!data[ll].is_sameXYZ(data[0])) + throw CImgInstanceException("CImgList<%s>::save_ffmpeg_external() : File '%s', all images of the sequence must be of the same dimension.", + pixel_type(),filename); + do { + cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand()); + cimg_std::sprintf(filetmp2,"%s_000001.ppm",filetmp); + if ((file=cimg_std::fopen(filetmp2,"rb"))!=0) cimg_std::fclose(file); + } while (file); + for (unsigned int l = first_frame; l<=nlast_frame; ++l) { + cimg_std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,l+1); + if (data[l].depth>1 || data[l].dim!=3) data[l].get_resize(-100,-100,1,3).save_pnm(filetmp2); + else data[l].save_pnm(filetmp2); + } +#if cimg_OS!=2 + cimg_std::sprintf(command,"ffmpeg -i %s_%%6d.ppm -vcodec %s -sameq -y \"%s\" >/dev/null 2>&1",filetmp,codec,filename); +#else + cimg_std::sprintf(command,"\"ffmpeg -i %s_%%6d.ppm -vcodec %s -sameq -y \"%s\"\" >NUL 2>&1",filetmp,codec,filename); +#endif + cimg::system(command); + file = cimg_std::fopen(filename,"rb"); + if (!file) + throw CImgIOException("CImg<%s>::save_ffmpeg_external() : Failed to save image sequence '%s'.\n\n", + pixel_type(),filename); + else cimg::fclose(file); + cimglist_for(*this,lll) { cimg_std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,lll+1); cimg_std::remove(filetmp2); } + return *this; + } + + }; + + /* + #--------------------------------------------- + # + # Completion of previously declared functions + # + #---------------------------------------------- + */ + +namespace cimg { + + //! Display a dialog box, where a user can click standard buttons. + /** + Up to 6 buttons can be defined in the dialog window. + This function returns when a user clicked one of the button or closed the dialog window. + \param title = Title of the dialog window. + \param msg = Main message displayed inside the dialog window. + \param button1_txt = Label of the 1st button. + \param button2_txt = Label of the 2nd button. + \param button3_txt = Label of the 3rd button. + \param button4_txt = Label of the 4th button. + \param button5_txt = Label of the 5th button. + \param button6_txt = Label of the 6th button. + \param logo = Logo image displayed at the left of the main message. This parameter is optional. + \param centering = Tell to center the dialog window on the screen. + \return The button number (from 0 to 5), or -1 if the dialog window has been closed by the user. + \note If a button text is set to 0, then the corresponding button (and the followings) won't appear in + the dialog box. At least one button is necessary. + **/ + + template + inline int dialog(const char *title, const char *msg, + const char *button1_txt, const char *button2_txt, + const char *button3_txt, const char *button4_txt, + const char *button5_txt, const char *button6_txt, + const CImg& logo, const bool centering = false) { +#if cimg_display!=0 + const unsigned char + black[] = { 0,0,0 }, white[] = { 255,255,255 }, gray[] = { 200,200,200 }, gray2[] = { 150,150,150 }; + + // Create buttons and canvas graphics + CImgList buttons, cbuttons, sbuttons; + if (button1_txt) { buttons.insert(CImg().draw_text(0,0,button1_txt,black,gray,1,13)); + if (button2_txt) { buttons.insert(CImg().draw_text(0,0,button2_txt,black,gray,1,13)); + if (button3_txt) { buttons.insert(CImg().draw_text(0,0,button3_txt,black,gray,1,13)); + if (button4_txt) { buttons.insert(CImg().draw_text(0,0,button4_txt,black,gray,1,13)); + if (button5_txt) { buttons.insert(CImg().draw_text(0,0,button5_txt,black,gray,1,13)); + if (button6_txt) { buttons.insert(CImg().draw_text(0,0,button6_txt,black,gray,1,13)); + }}}}}} + if (!buttons.size) + throw CImgArgumentException("cimg::dialog() : No buttons have been defined. At least one is necessary"); + + unsigned int bw = 0, bh = 0; + cimglist_for(buttons,l) { bw = cimg::max(bw,buttons[l].width); bh = cimg::max(bh,buttons[l].height); } + bw+=8; bh+=8; + if (bw<64) bw=64; + if (bw>128) bw=128; + if (bh<24) bh=24; + if (bh>48) bh=48; + + CImg button(bw,bh,1,3); + button.draw_rectangle(0,0,bw-1,bh-1,gray); + button.draw_line(0,0,bw-1,0,white).draw_line(0,bh-1,0,0,white); + button.draw_line(bw-1,0,bw-1,bh-1,black).draw_line(bw-1,bh-1,0,bh-1,black); + button.draw_line(1,bh-2,bw-2,bh-2,gray2).draw_line(bw-2,bh-2,bw-2,1,gray2); + CImg sbutton(bw,bh,1,3); + sbutton.draw_rectangle(0,0,bw-1,bh-1,gray); + sbutton.draw_line(0,0,bw-1,0,black).draw_line(bw-1,0,bw-1,bh-1,black); + sbutton.draw_line(bw-1,bh-1,0,bh-1,black).draw_line(0,bh-1,0,0,black); + sbutton.draw_line(1,1,bw-2,1,white).draw_line(1,bh-2,1,1,white); + sbutton.draw_line(bw-2,1,bw-2,bh-2,black).draw_line(bw-2,bh-2,1,bh-2,black); + sbutton.draw_line(2,bh-3,bw-3,bh-3,gray2).draw_line(bw-3,bh-3,bw-3,2,gray2); + sbutton.draw_line(4,4,bw-5,4,black,1,0xAAAAAAAA,true).draw_line(bw-5,4,bw-5,bh-5,black,1,0xAAAAAAAA,false); + sbutton.draw_line(bw-5,bh-5,4,bh-5,black,1,0xAAAAAAAA,false).draw_line(4,bh-5,4,4,black,1,0xAAAAAAAA,false); + CImg cbutton(bw,bh,1,3); + cbutton.draw_rectangle(0,0,bw-1,bh-1,black).draw_rectangle(1,1,bw-2,bh-2,gray2).draw_rectangle(2,2,bw-3,bh-3,gray); + cbutton.draw_line(4,4,bw-5,4,black,1,0xAAAAAAAA,true).draw_line(bw-5,4,bw-5,bh-5,black,1,0xAAAAAAAA,false); + cbutton.draw_line(bw-5,bh-5,4,bh-5,black,1,0xAAAAAAAA,false).draw_line(4,bh-5,4,4,black,1,0xAAAAAAAA,false); + + cimglist_for(buttons,ll) { + cbuttons.insert(CImg(cbutton).draw_image(1+(bw-buttons[ll].dimx())/2,1+(bh-buttons[ll].dimy())/2,buttons[ll])); + sbuttons.insert(CImg(sbutton).draw_image((bw-buttons[ll].dimx())/2,(bh-buttons[ll].dimy())/2,buttons[ll])); + buttons[ll] = CImg(button).draw_image((bw-buttons[ll].dimx())/2,(bh-buttons[ll].dimy())/2,buttons[ll]); + } + + CImg canvas; + if (msg) canvas = CImg().draw_text(0,0,msg,black,gray,1,13); + const unsigned int + bwall = (buttons.size-1)*(12+bw) + bw, + w = cimg::max(196U,36+logo.width+canvas.width, 24+bwall), + h = cimg::max(96U,36+canvas.height+bh,36+logo.height+bh), + lx = 12 + (canvas.data?0:((w-24-logo.width)/2)), + ly = (h-12-bh-logo.height)/2, + tx = lx+logo.width+12, + ty = (h-12-bh-canvas.height)/2, + bx = (w-bwall)/2, + by = h-12-bh; + + if (canvas.data) + canvas = CImg(w,h,1,3). + draw_rectangle(0,0,w-1,h-1,gray). + draw_line(0,0,w-1,0,white).draw_line(0,h-1,0,0,white). + draw_line(w-1,0,w-1,h-1,black).draw_line(w-1,h-1,0,h-1,black). + draw_image(tx,ty,canvas); + else + canvas = CImg(w,h,1,3). + draw_rectangle(0,0,w-1,h-1,gray). + draw_line(0,0,w-1,0,white).draw_line(0,h-1,0,0,white). + draw_line(w-1,0,w-1,h-1,black).draw_line(w-1,h-1,0,h-1,black); + if (logo.data) canvas.draw_image(lx,ly,logo); + + unsigned int xbuttons[6]; + cimglist_for(buttons,lll) { xbuttons[lll] = bx+(bw+12)*lll; canvas.draw_image(xbuttons[lll],by,buttons[lll]); } + + // Open window and enter events loop + CImgDisplay disp(canvas,title?title:" ",0,false,centering?true:false); + if (centering) disp.move((CImgDisplay::screen_dimx()-disp.dimx())/2, + (CImgDisplay::screen_dimy()-disp.dimy())/2); + bool stopflag = false, refresh = false; + int oselected = -1, oclicked = -1, selected = -1, clicked = -1; + while (!disp.is_closed && !stopflag) { + if (refresh) { + if (clicked>=0) CImg(canvas).draw_image(xbuttons[clicked],by,cbuttons[clicked]).display(disp); + else { + if (selected>=0) CImg(canvas).draw_image(xbuttons[selected],by,sbuttons[selected]).display(disp); + else canvas.display(disp); + } + refresh = false; + } + disp.wait(15); + if (disp.is_resized) disp.resize(disp); + + if (disp.button&1) { + oclicked = clicked; + clicked = -1; + cimglist_for(buttons,l) + if (disp.mouse_y>=(int)by && disp.mouse_y<(int)(by+bh) && + disp.mouse_x>=(int)xbuttons[l] && disp.mouse_x<(int)(xbuttons[l]+bw)) { + clicked = selected = l; + refresh = true; + } + if (clicked!=oclicked) refresh = true; + } else if (clicked>=0) stopflag = true; + + if (disp.key) { + oselected = selected; + switch (disp.key) { + case cimg::keyESC : selected=-1; stopflag=true; break; + case cimg::keyENTER : if (selected<0) selected = 0; stopflag = true; break; + case cimg::keyTAB : + case cimg::keyARROWRIGHT : + case cimg::keyARROWDOWN : selected = (selected+1)%buttons.size; break; + case cimg::keyARROWLEFT : + case cimg::keyARROWUP : selected = (selected+buttons.size-1)%buttons.size; break; + } + disp.key = 0; + if (selected!=oselected) refresh = true; + } + } + if (!disp) selected = -1; + return selected; +#else + cimg_std::fprintf(cimg_stdout,"<%s>\n\n%s\n\n",title,msg); + return -1+0*(int)(button1_txt-button2_txt+button3_txt-button4_txt+button5_txt-button6_txt+logo.width+(int)centering); +#endif + } + + inline int dialog(const char *title, const char *msg, + const char *button1_txt, const char *button2_txt, const char *button3_txt, + const char *button4_txt, const char *button5_txt, const char *button6_txt, + const bool centering) { + return dialog(title,msg,button1_txt,button2_txt,button3_txt,button4_txt,button5_txt,button6_txt, + CImg::logo40x38(),centering); + } + + // End of cimg:: namespace +} + + // End of cimg_library:: namespace +} + +#ifdef _cimg_redefine_min +#define min(a,b) (((a)<(b))?(a):(b)) +#endif +#ifdef _cimg_redefine_max +#define max(a,b) (((a)>(b))?(a):(b)) +#endif + +#endif diff --git a/src/libs/greycstoration/LICENSE.txt b/src/libs/greycstoration/LICENSE.txt new file mode 100644 index 00000000..a36324de --- /dev/null +++ b/src/libs/greycstoration/LICENSE.txt @@ -0,0 +1,505 @@ + + CeCILL FREE SOFTWARE LICENSE AGREEMENT + + + Notice + +This Agreement is a Free Software license agreement that is the result +of discussions between its authors in order to ensure compliance with +the two main principles guiding its drafting: + + * firstly, compliance with the principles governing the distribution + of Free Software: access to source code, broad rights granted to + users, + * secondly, the election of a governing law, French law, with which + it is conformant, both as regards the law of torts and + intellectual property law, and the protection that it offers to + both authors and holders of the economic rights over software. + +The authors of the CeCILL (for Ce[a] C[nrs] I[nria] L[logiciel] L[ibre]) +license are: + +Commissariat à l'Energie Atomique - CEA, a public scientific, technical +and industrial establishment, having its principal place of business at +31-33 rue de la Fédération, 75752 Paris cedex 15, France. + +Centre National de la Recherche Scientifique - CNRS, a public scientific +and technological establishment, having its principal place of business +at 3 rue Michel-Ange 75794 Paris cedex 16, France. + +Institut National de Recherche en Informatique et en Automatique - +INRIA, a public scientific and technological establishment, having its +principal place of business at Domaine de Voluceau, Rocquencourt, BP +105, 78153 Le Chesnay cedex, France. + + + Preamble + +The purpose of this Free Software license agreement is to grant users +the right to modify and redistribute the software governed by this +license within the framework of an open source distribution model. + +The exercising of these rights is conditional upon certain obligations +for users so as to preserve this status for all subsequent redistributions. + +In consideration of access to the source code and the rights to copy, +modify and redistribute granted by the license, users are provided only +with a limited warranty and the software's author, the holder of the +economic rights, and the successive licensors only have limited liability. + +In this respect, the risks associated with loading, using, modifying +and/or developing or reproducing the software by the user are brought to +the user's attention, given its Free Software status, which may make it +complicated to use, with the result that its use is reserved for +developers and experienced professionals having in-depth computer +knowledge. Users are therefore encouraged to load and test the +Software's suitability as regards their requirements in conditions +enabling the security of their systems and/or data to be ensured and, +more generally, to use and operate it in the same conditions of +security. This Agreement may be freely reproduced and published, +provided it is not altered, and that no provisions are either added or +removed herefrom. + +This Agreement may apply to any or all software for which the holder of +the economic rights decides to submit the use thereof to its provisions. + + + Article 1 - DEFINITIONS + +For the purpose of this Agreement, when the following expressions +commence with a capital letter, they shall have the following meaning: + +Agreement: means this license agreement, and its possible subsequent +versions and annexes. + +Software: means the software in its Object Code and/or Source Code form +and, where applicable, its documentation, "as is" when the Licensee +accepts the Agreement. + +Initial Software: means the Software in its Source Code and possibly its +Object Code form and, where applicable, its documentation, "as is" when +it is first distributed under the terms and conditions of the Agreement. + +Modified Software: means the Software modified by at least one +Contribution. + +Source Code: means all the Software's instructions and program lines to +which access is required so as to modify the Software. + +Object Code: means the binary files originating from the compilation of +the Source Code. + +Holder: means the holder(s) of the economic rights over the Initial +Software. + +Licensee: means the Software user(s) having accepted the Agreement. + +Contributor: means a Licensee having made at least one Contribution. + +Licensor: means the Holder, or any other individual or legal entity, who +distributes the Software under the Agreement. + +Contribution: means any or all modifications, corrections, translations, +adaptations and/or new functions integrated into the Software by any or +all Contributors, as well as any or all Internal Modules. + +Module: means a set of sources files including their documentation that +enables supplementary functions or services in addition to those offered +by the Software. + +External Module: means any or all Modules, not derived from the +Software, so that this Module and the Software run in separate address +spaces, with one calling the other when they are run. + +Internal Module: means any or all Module, connected to the Software so +that they both execute in the same address space. + +GNU GPL: means the GNU General Public License version 2 or any +subsequent version, as published by the Free Software Foundation Inc. + +Parties: mean both the Licensee and the Licensor. + +These expressions may be used both in singular and plural form. + + + Article 2 - PURPOSE + +The purpose of the Agreement is the grant by the Licensor to the +Licensee of a non-exclusive, transferable and worldwide license for the +Software as set forth in Article 5 hereinafter for the whole term of the +protection granted by the rights over said Software. + + + Article 3 - ACCEPTANCE + +3.1 The Licensee shall be deemed as having accepted the terms and +conditions of this Agreement upon the occurrence of the first of the +following events: + + * (i) loading the Software by any or all means, notably, by + downloading from a remote server, or by loading from a physical + medium; + * (ii) the first time the Licensee exercises any of the rights + granted hereunder. + +3.2 One copy of the Agreement, containing a notice relating to the +characteristics of the Software, to the limited warranty, and to the +fact that its use is restricted to experienced users has been provided +to the Licensee prior to its acceptance as set forth in Article 3.1 +hereinabove, and the Licensee hereby acknowledges that it has read and +understood it. + + + Article 4 - EFFECTIVE DATE AND TERM + + + 4.1 EFFECTIVE DATE + +The Agreement shall become effective on the date when it is accepted by +the Licensee as set forth in Article 3.1. + + + 4.2 TERM + +The Agreement shall remain in force for the entire legal term of +protection of the economic rights over the Software. + + + Article 5 - SCOPE OF RIGHTS GRANTED + +The Licensor hereby grants to the Licensee, who accepts, the following +rights over the Software for any or all use, and for the term of the +Agreement, on the basis of the terms and conditions set forth hereinafter. + +Besides, if the Licensor owns or comes to own one or more patents +protecting all or part of the functions of the Software or of its +components, the Licensor undertakes not to enforce the rights granted by +these patents against successive Licensees using, exploiting or +modifying the Software. If these patents are transferred, the Licensor +undertakes to have the transferees subscribe to the obligations set +forth in this paragraph. + + + 5.1 RIGHT OF USE + +The Licensee is authorized to use the Software, without any limitation +as to its fields of application, with it being hereinafter specified +that this comprises: + + 1. permanent or temporary reproduction of all or part of the Software + by any or all means and in any or all form. + + 2. loading, displaying, running, or storing the Software on any or + all medium. + + 3. entitlement to observe, study or test its operation so as to + determine the ideas and principles behind any or all constituent + elements of said Software. This shall apply when the Licensee + carries out any or all loading, displaying, running, transmission + or storage operation as regards the Software, that it is entitled + to carry out hereunder. + + + 5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS + +The right to make Contributions includes the right to translate, adapt, +arrange, or make any or all modifications to the Software, and the right +to reproduce the resulting Software. + +The Licensee is authorized to make any or all Contributions to the +Software provided that it includes an explicit notice that it is the +author of said Contribution and indicates the date of the creation thereof. + + + 5.3 RIGHT OF DISTRIBUTION + +In particular, the right of distribution includes the right to publish, +transmit and communicate the Software to the general public on any or +all medium, and by any or all means, and the right to market, either in +consideration of a fee, or free of charge, one or more copies of the +Software by any means. + +The Licensee is further authorized to distribute copies of the modified +or unmodified Software to third parties according to the terms and +conditions set forth hereinafter. + + + 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION + +The Licensee is authorized to distribute true copies of the Software in +Source Code or Object Code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's + warranty and liability as set forth in Articles 8 and 9, + +and that, in the event that only the Object Code of the Software is +redistributed, the Licensee allows future Licensees unhindered access to +the full Source Code of the Software by indicating how to access it, it +being understood that the additional cost of acquiring the Source Code +shall not exceed the cost of transferring the data. + + + 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE + +When the Licensee makes a Contribution to the Software, the terms and +conditions for the distribution of the Modified Software become subject +to all the provisions of this Agreement. + +The Licensee is authorized to distribute the Modified Software, in +Source Code or Object Code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's + warranty and liability as set forth in Articles 8 and 9, + +and that, in the event that only the Object Code of the Modified +Software is redistributed, the Licensee allows future Licensees +unhindered access to the full Source Code of the Modified Software by +indicating how to access it, it being understood that the additional +cost of acquiring the Source Code shall not exceed the cost of +transferring the data. + + + 5.3.3 DISTRIBUTION OF EXTERNAL MODULES + +When the Licensee has developed an External Module, the terms and +conditions of this Agreement do not apply to said External Module, that +may be distributed under a separate license agreement. + + + 5.3.4 COMPATIBILITY WITH THE GNU GPL + +The Licensee can include a code that is subject to the provisions of one +of the versions of the GNU GPL in the Modified or unmodified Software, +and distribute that entire code under the terms of the same version of +the GNU GPL. + +The Licensee can include the Modified or unmodified Software in a code +that is subject to the provisions of one of the versions of the GNU GPL, +and distribute that entire code under the terms of the same version of +the GNU GPL. + + + Article 6 - INTELLECTUAL PROPERTY + + + 6.1 OVER THE INITIAL SOFTWARE + +The Holder owns the economic rights over the Initial Software. Any or +all use of the Initial Software is subject to compliance with the terms +and conditions under which the Holder has elected to distribute its work +and no one shall be entitled to modify the terms and conditions for the +distribution of said Initial Software. + +The Holder undertakes that the Initial Software will remain ruled at +least by the current license, for the duration set forth in article 4.2. + + + 6.2 OVER THE CONTRIBUTIONS + +A Licensee who develops a Contribution is the owner of the intellectual +property rights over this Contribution as defined by applicable law. + + + 6.3 OVER THE EXTERNAL MODULES + +A Licensee who develops an External Module is the owner of the +intellectual property rights over this External Module as defined by +applicable law and is free to choose the type of agreement that shall +govern its distribution. + + + 6.4 JOINT PROVISIONS + +The Licensee expressly undertakes: + + 1. not to remove, or modify, in any manner, the intellectual property + notices attached to the Software; + + 2. to reproduce said notices, in an identical manner, in the copies + of the Software modified or not. + +The Licensee undertakes not to directly or indirectly infringe the +intellectual property rights of the Holder and/or Contributors on the +Software and to take, where applicable, vis-à-vis its staff, any and all +measures required to ensure respect of said intellectual property rights +of the Holder and/or Contributors. + + + Article 7 - RELATED SERVICES + +7.1 Under no circumstances shall the Agreement oblige the Licensor to +provide technical assistance or maintenance services for the Software. + +However, the Licensor is entitled to offer this type of services. The +terms and conditions of such technical assistance, and/or such +maintenance, shall be set forth in a separate instrument. Only the +Licensor offering said maintenance and/or technical assistance services +shall incur liability therefor. + +7.2 Similarly, any Licensor is entitled to offer to its licensees, under +its sole responsibility, a warranty, that shall only be binding upon +itself, for the redistribution of the Software and/or the Modified +Software, under terms and conditions that it is free to decide. Said +warranty, and the financial terms and conditions of its application, +shall be subject of a separate instrument executed between the Licensor +and the Licensee. + + + Article 8 - LIABILITY + +8.1 Subject to the provisions of Article 8.2, the Licensee shall be +entitled to claim compensation for any direct loss it may have suffered +from the Software as a result of a fault on the part of the relevant +Licensor, subject to providing evidence thereof. + +8.2 The Licensor's liability is limited to the commitments made under +this Agreement and shall not be incurred as a result of in particular: +(i) loss due the Licensee's total or partial failure to fulfill its +obligations, (ii) direct or consequential loss that is suffered by the +Licensee due to the use or performance of the Software, and (iii) more +generally, any consequential loss. In particular the Parties expressly +agree that any or all pecuniary or business loss (i.e. loss of data, +loss of profits, operating loss, loss of customers or orders, +opportunity cost, any disturbance to business activities) or any or all +legal proceedings instituted against the Licensee by a third party, +shall constitute consequential loss and shall not provide entitlement to +any or all compensation from the Licensor. + + + Article 9 - WARRANTY + +9.1 The Licensee acknowledges that the scientific and technical +state-of-the-art when the Software was distributed did not enable all +possible uses to be tested and verified, nor for the presence of +possible defects to be detected. In this respect, the Licensee's +attention has been drawn to the risks associated with loading, using, +modifying and/or developing and reproducing the Software which are +reserved for experienced users. + +The Licensee shall be responsible for verifying, by any or all means, +the product's suitability for its requirements, its good working order, +and for ensuring that it shall not cause damage to either persons or +properties. + +9.2 The Licensor hereby represents, in good faith, that it is entitled +to grant all the rights over the Software (including in particular the +rights set forth in Article 5). + +9.3 The Licensee acknowledges that the Software is supplied "as is" by +the Licensor without any other express or tacit warranty, other than +that provided for in Article 9.2 and, in particular, without any warranty +as to its commercial value, its secured, safe, innovative or relevant +nature. + +Specifically, the Licensor does not warrant that the Software is free +from any error, that it will operate without interruption, that it will +be compatible with the Licensee's own equipment and software +configuration, nor that it will meet the Licensee's requirements. + +9.4 The Licensor does not either expressly or tacitly warrant that the +Software does not infringe any third party intellectual property right +relating to a patent, software or any other property right. Therefore, +the Licensor disclaims any and all liability towards the Licensee +arising out of any or all proceedings for infringement that may be +instituted in respect of the use, modification and redistribution of the +Software. Nevertheless, should such proceedings be instituted against +the Licensee, the Licensor shall provide it with technical and legal +assistance for its defense. Such technical and legal assistance shall be +decided on a case-by-case basis between the relevant Licensor and the +Licensee pursuant to a memorandum of understanding. The Licensor +disclaims any and all liability as regards the Licensee's use of the +name of the Software. No warranty is given as regards the existence of +prior rights over the name of the Software or as regards the existence +of a trademark. + + + Article 10 - TERMINATION + +10.1 In the event of a breach by the Licensee of its obligations +hereunder, the Licensor may automatically terminate this Agreement +thirty (30) days after notice has been sent to the Licensee and has +remained ineffective. + +10.2 A Licensee whose Agreement is terminated shall no longer be +authorized to use, modify or distribute the Software. However, any +licenses that it may have granted prior to termination of the Agreement +shall remain valid subject to their having been granted in compliance +with the terms and conditions hereof. + + + Article 11 - MISCELLANEOUS + + + 11.1 EXCUSABLE EVENTS + +Neither Party shall be liable for any or all delay, or failure to +perform the Agreement, that may be attributable to an event of force +majeure, an act of God or an outside cause, such as defective +functioning or interruptions of the electricity or telecommunications +networks, network paralysis following a virus attack, intervention by +government authorities, natural disasters, water damage, earthquakes, +fire, explosions, strikes and labor unrest, war, etc. + +11.2 Any Failure by either Party, on one or more occasions, to invoke +one or more of the provisions hereof, shall under no circumstances be +interpreted as being a waiver by the interested Party of its right to +invoke said provision(s) subsequently. + +11.3 The Agreement cancels and replaces any or all previous agreements, +whether written or oral, between the Parties and having the same +purpose, and constitutes the entirety of the agreement between said +Parties concerning said purpose. No supplement or modification to the +terms and conditions hereof shall be effective as between the Parties +unless it is made in writing and signed by their duly authorized +representatives. + +11.4 In the event that one or more of the provisions hereof were to +conflict with a current or future applicable act or legislative text, +said act or legislative text shall prevail, and the Parties shall make +the necessary amendments so as to comply with said act or legislative +text. All other provisions shall remain effective. Similarly, invalidity +of a provision of the Agreement, for any reason whatsoever, shall not +cause the Agreement as a whole to be invalid. + + + 11.5 LANGUAGE + +The Agreement is drafted in both French and English and both versions +are deemed authentic. + + + Article 12 - NEW VERSIONS OF THE AGREEMENT + +12.1 Any person is authorized to duplicate and distribute copies of this +Agreement. + +12.2 So as to ensure coherence, the wording of this Agreement is +protected and may only be modified by the authors of the License, who +reserve the right to periodically publish updates or new versions of the +Agreement, each with a separate number. These subsequent versions may +address new issues encountered by Free Software. + +12.3 Any Software distributed under a given version of the Agreement may +only be subsequently distributed under the same version of the Agreement +or a subsequent version, subject to the provisions of Article 5.3.4. + + + Article 13 - GOVERNING LAW AND JURISDICTION + +13.1 The Agreement is governed by French law. The Parties agree to +endeavor to seek an amicable solution to any disagreements or disputes +that may arise during the performance of the Agreement. + +13.2 Failing an amicable solution within two (2) months as from their +occurrence, and unless emergency proceedings are necessary, the +disagreements or disputes shall be referred to the Paris Courts having +jurisdiction, by the more diligent Party. + + +Version 2.0 dated 2005-05-21. diff --git a/src/libs/greycstoration/Makefile.am b/src/libs/greycstoration/Makefile.am new file mode 100644 index 00000000..f7cc569f --- /dev/null +++ b/src/libs/greycstoration/Makefile.am @@ -0,0 +1,20 @@ +METASOURCES = AUTO + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) -w + +noinst_LTLIBRARIES = libgreycstoration.la + +libgreycstoration_la_SOURCES = greycstorationiface.cpp greycstorationwidget.cpp + +libgreycstoration_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +INCLUDES = -I$(top_srcdir)/src/libs/dimg \ + -I$(top_srcdir)/src/libs/dimg/filters \ + -I$(top_srcdir)/src/digikam \ + $(LIBKDCRAW_CFLAGS) \ + $(all_includes) + +digikaminclude_HEADERS = greycstorationiface.h greycstorationwidget.h greycstorationsettings.h + +digikamincludedir = $(includedir)/digikam + diff --git a/src/libs/greycstoration/greycstoration.h b/src/libs/greycstoration/greycstoration.h new file mode 100644 index 00000000..36b2c0e2 --- /dev/null +++ b/src/libs/greycstoration/greycstoration.h @@ -0,0 +1,481 @@ +/* + # + # File : greycstoration.h + # ( C++ header file - CImg plug-in ) + # + # Description : GREYCstoration plug-in allowing easy integration in + # third parties softwares. + # ( http://www.greyc.ensicaen.fr/~dtschump/greycstoration/ ) + # This file is a part of the CImg Library project. + # ( http://cimg.sourceforge.net ) + # + # THIS PLUG-IN IS INTENDED FOR DEVELOPERS ONLY. IT EASES THE INTEGRATION ALGORITHM IN + # THIRD PARTIES SOFTWARES. IF YOU ARE A USER OF GREYCSTORATION, PLEASE LOOK + # AT THE FILE 'greycstoration.cpp' WHICH IS THE SOURCE OF THE COMPLETE + # COMMAND LINE GREYCSTORATION TOOL. + # + # Copyright : David Tschumperle + # ( http://www.greyc.ensicaen.fr/~dtschump/ ) + # + # License : CeCILL v2.0 + # ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html ) + # + # This software is governed by the CeCILL license under French law and + # abiding by the rules of distribution of free software. You can use, + # modify and/ or redistribute the software under the terms of the CeCILL + # license as circulated by CEA, CNRS and INRIA at the following URL + # "http://www.cecill.info". + # + # As a counterpart to the access to the source code and rights to copy, + # modify and redistribute granted by the license, users are provided only + # with a limited warranty and the software's author, the holder of the + # economic rights, and the successive licensors have only limited + # liability. + # + # In this respect, the user's attention is drawn to the risks associated + # with loading, using, modifying and/or developing or reproducing the + # software by the user in light of its specific status of free software, + # that may mean that it is complicated to manipulate, and that also + # therefore means that it is reserved for developers and experienced + # professionals having in-depth computer knowledge. Users are therefore + # encouraged to load and test the software's suitability as regards their + # requirements in conditions enabling the security of their systems and/or + # data to be ensured and, more generally, to use and operate it in the + # same conditions as regards security. + # + # The fact that you are presently reading this means that you have had + # knowledge of the CeCILL license and that you accept its terms. + # +*/ + +#ifndef cimg_plugin_greycstoration +#define cimg_plugin_greycstoration + +//------------------------------------------------------------------------------ +// GREYCstoration parameter structure, storing important informations about +// algorithm parameters and computing threads. +// ** This structure has not to be manipulated by the API user, so please just +// ignore it if you want to ** +//------------------------------------------------------------------------------- +struct _greycstoration_params { + + // Tell if the patch-based algorithm is selected + bool patch_based; + + // Parameters specific to the non-patch regularization algorithm + float amplitude; + float sharpness; + float anisotropy; + float alpha; + float sigma; + float gfact; + float dl; + float da; + float gauss_prec; + unsigned int interpolation; + + // Parameters specific to the patch-based regularization algorithm + unsigned int patch_size; + float sigma_s; + float sigma_p; + unsigned int lookup_size; + + // Non-specific parameters of the algorithms. + CImg *source; + const CImg *mask; + CImg *temporary; + unsigned long *counter; + unsigned int tile; + unsigned int tile_border; + unsigned int thread; + unsigned int nb_threads; + bool fast_approx; + bool is_running; + bool *stop_request; +#if cimg_OS==1 && defined(_PTHREAD_H) + pthread_mutex_t + *mutex; +#elif cimg_OS==2 + HANDLE mutex; +#else + void *mutex; +#endif + + // Default constructor + _greycstoration_params():patch_based(false),amplitude(0),sharpness(0),anisotropy(0),alpha(0),sigma(0),gfact(1), + dl(0),da(0),gauss_prec(0),interpolation(0),patch_size(0), + sigma_s(0),sigma_p(0),lookup_size(0),source(0),mask(0),temporary(0),counter(0),tile(0), + tile_border(0),thread(0),nb_threads(0),fast_approx(false),is_running(false), stop_request(0), mutex(0) {} +}; + +_greycstoration_params greycstoration_params[16]; + +//---------------------------------------------------------- +// Public functions of the GREYCstoration API. +// Use the functions below for integrating GREYCstoration +// in your own C++ code. +//---------------------------------------------------------- + +//! Test if GREYCstoration threads are still running. +bool greycstoration_is_running() const { + return greycstoration_params->is_running; +} + +//! Force the GREYCstoration threads to stop. +CImg& greycstoration_stop() { + if (greycstoration_is_running()) { + *(greycstoration_params->stop_request) = true; + while (greycstoration_params->is_running) cimg::wait(50); + } + return *this; +} + +//! Return the GREYCstoration progress bar indice (between 0 and 100). +float greycstoration_progress() const { + if (!greycstoration_is_running()) return 0.0f; + const unsigned long counter = greycstoration_params->counter?*(greycstoration_params->counter):0; + const float + da = greycstoration_params->da, + factor = greycstoration_params->patch_based?1:(1+360/da); + float maxcounter = 0; + if (greycstoration_params->tile==0) maxcounter = width*height*depth*factor; + else { + const unsigned int + t = greycstoration_params->tile, + b = greycstoration_params->tile_border, + n = (1+(width-1)/t)*(1+(height-1)/t)*(1+(depth-1)/t); + maxcounter = (width*height*depth + n*4*b*(b + t))*factor; + } + return cimg::min(counter*99.9f/maxcounter,99.9f); +} + +//! Run the non-patch version of the GREYCstoration algorithm on the instance image, using a mask. +CImg& greycstoration_run(const CImg& mask, + const float amplitude=60, const float sharpness=0.7f, const float anisotropy=0.3f, + const float alpha=0.6f, const float sigma=1.1f, const float gfact=1.0f, + const float dl=0.8f, const float da=30.0f, + const float gauss_prec=2.0f, const unsigned int interpolation=0, const bool fast_approx=true, + const unsigned int tile=0, const unsigned int tile_border=0, const unsigned int nb_threads=1) { + + if (greycstoration_is_running()) + throw CImgInstanceException("CImg::greycstoration_run() : A GREYCstoration thread is already running on" + " the instance image (%u,%u,%u,%u,%p).",width,height,depth,dim,data); + + else { + if (!mask.is_empty() && !mask.is_sameXY(*this)) + throw CImgArgumentException("CImg<%s>::greycstoration_run() : Given mask (%u,%u,%u,%u,%p) and instance image " + "(%u,%u,%u,%u,%p) have different dimensions.", + pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data,width,height,depth,dim,data); + if (nb_threads>16) cimg::warn("CImg<%s>::greycstoration_run() : Multi-threading mode limited to 16 threads max."); + const unsigned int + ntile = (tile && (tile1 && tile *const temporary = ntile?new CImg(*this):0; + unsigned long *const counter = new unsigned long; + *counter = 0; + bool *const stop_request = new bool; + *stop_request = false; + + for (unsigned int k=0; k<(nthreads?nthreads:1); k++) { + greycstoration_params[k].patch_based = false; + greycstoration_params[k].amplitude = amplitude; + greycstoration_params[k].sharpness = sharpness; + greycstoration_params[k].anisotropy = anisotropy; + greycstoration_params[k].alpha = alpha; + greycstoration_params[k].sigma = sigma; + greycstoration_params[k].gfact = gfact; + greycstoration_params[k].dl = dl; + greycstoration_params[k].da = da; + greycstoration_params[k].gauss_prec = gauss_prec; + greycstoration_params[k].interpolation = interpolation; + greycstoration_params[k].fast_approx = fast_approx; + greycstoration_params[k].source = this; + greycstoration_params[k].mask = &mask; + greycstoration_params[k].temporary = temporary; + greycstoration_params[k].counter = counter; + greycstoration_params[k].tile = ntile; + greycstoration_params[k].tile_border = tile_border; + greycstoration_params[k].thread = k; + greycstoration_params[k].nb_threads = nthreads; + greycstoration_params[k].is_running = true; + greycstoration_params[k].stop_request = stop_request; + if (k) greycstoration_params[k].mutex = greycstoration_params[0].mutex; + else greycstoration_mutex_create(greycstoration_params[0]); + } + if (nthreads) { // Threaded version +#if cimg_OS==1 +#ifdef _PTHREAD_H + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + for (unsigned int k=0; knb_threads; k++) { + pthread_t thread; + const int err = pthread_create(&thread, &attr, greycstoration_thread, (void*)(greycstoration_params+k)); + if (err) throw CImgException("CImg<%s>::greycstoration_run() : pthread_create returned error %d", + pixel_type(), err); + } +#endif +#elif cimg_OS==2 + for (unsigned int k=0; knb_threads; k++) { + unsigned long ThreadID = 0; + CreateThread(0,0,greycstoration_thread,(void*)(greycstoration_params+k),0,&ThreadID); + } +#else + throw CImgInstanceException("CImg::greycstoration_run() : Threads are not supported, please define cimg_OS first."); +#endif + } else greycstoration_thread((void*)greycstoration_params); // Non-threaded version + } + return *this; +} + +//! Run the non-patch version of the GREYCstoration algorithm on the instance image. +CImg& greycstoration_run(const float amplitude=50, const float sharpness=0.7f, const float anisotropy=0.3f, + const float alpha=0.6f, const float sigma=1.1f, const float gfact=1.0f, + const float dl=0.8f, const float da=30.0f, + const float gauss_prec=2.0f, const unsigned int interpolation=0, const bool fast_approx=true, + const unsigned int tile=0, const unsigned int tile_border=0, const unsigned int nb_threads=1) { + static const CImg empty_mask; + return greycstoration_run(empty_mask,amplitude,sharpness,anisotropy,alpha,sigma,gfact,dl,da,gauss_prec, + interpolation,fast_approx,tile,tile_border,nb_threads); +} + +//! Run the patch-based version of the GREYCstoration algorithm on the instance image. +CImg& greycstoration_patch_run(const unsigned int patch_size=5, const float sigma_p=10, const float sigma_s=100, + const unsigned int lookup_size=20, const bool fast_approx=true, + const unsigned int tile=0, const unsigned int tile_border=0, const unsigned int nb_threads=1) { + + static const CImg empty_mask; + if (greycstoration_is_running()) + throw CImgInstanceException("CImg::greycstoration_run() : A GREYCstoration thread is already running on" + " the instance image (%u,%u,%u,%u,%p).",width,height,depth,dim,data); + + else { + if (nb_threads>16) cimg::warn("CImg<%s>::greycstoration_run() : Multi-threading mode limited to 16 threads max."); + const unsigned int + ntile = (tile && (tile1 && tile *const temporary = ntile?new CImg(*this):0; + unsigned long *const counter = new unsigned long; + *counter = 0; + bool *const stop_request = new bool; + *stop_request = false; + + for (unsigned int k=0; k<(nthreads?nthreads:1); k++) { + greycstoration_params[k].patch_based = true; + greycstoration_params[k].patch_size = patch_size; + greycstoration_params[k].sigma_s = sigma_s; + greycstoration_params[k].sigma_p = sigma_p; + greycstoration_params[k].lookup_size = lookup_size; + greycstoration_params[k].source = this; + greycstoration_params[k].mask = &empty_mask; + greycstoration_params[k].temporary = temporary; + greycstoration_params[k].counter = counter; + greycstoration_params[k].tile = ntile; + greycstoration_params[k].tile_border = tile_border; + greycstoration_params[k].thread = k; + greycstoration_params[k].nb_threads = nthreads; + greycstoration_params[k].fast_approx = fast_approx; + greycstoration_params[k].is_running = true; + greycstoration_params[k].stop_request = stop_request; + if (k) greycstoration_params[k].mutex = greycstoration_params[0].mutex; + else greycstoration_mutex_create(greycstoration_params[0]); + } + if (nthreads) { // Threaded version +#if cimg_OS==1 +#ifdef _PTHREAD_H + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + for (unsigned int k=0; knb_threads; k++) { + pthread_t thread; + const int err = pthread_create(&thread, &attr, greycstoration_thread, (void*)(greycstoration_params+k)); + if (err) throw CImgException("CImg<%s>::greycstoration_run() : pthread_create returned error %d", + pixel_type(), err); + } +#endif +#elif cimg_OS==2 + for (unsigned int k=0; knb_threads; k++) { + unsigned long ThreadID = 0; + CreateThread(0,0,greycstoration_thread,(void*)(greycstoration_params+k),0,&ThreadID); + } +#else + throw CImgInstanceException("CImg::greycstoration_run() : Threads support have not been enabled in this version of GREYCstoration."); +#endif + } else greycstoration_thread((void*)greycstoration_params); // Non-threaded version + } + return *this; +} + +//------------------------------------------------------------------------------ +// GREYCstoration private functions. +// Should not be used directly by the API user. +//------------------------------------------------------------------------------- + +static void greycstoration_mutex_create(_greycstoration_params &p) { + if (p.nb_threads>1) { +#if cimg_OS==1 && defined(_PTHREAD_H) + p.mutex = new pthread_mutex_t; + pthread_mutex_init(p.mutex,0); +#elif cimg_OS==2 + p.mutex = CreateMutex(0,FALSE,0); +#endif + } +} + +static void greycstoration_mutex_lock(_greycstoration_params &p) { + if (p.nb_threads>1) { +#if cimg_OS==1 && defined(_PTHREAD_H) + if (p.mutex) pthread_mutex_lock(p.mutex); +#elif cimg_OS==2 + WaitForSingleObject(p.mutex,INFINITE); +#endif + } +} + +static void greycstoration_mutex_unlock(_greycstoration_params &p) { + if (p.nb_threads>1) { +#if cimg_OS==1 && defined(_PTHREAD_H) + if (p.mutex) pthread_mutex_unlock(p.mutex); +#elif cimg_OS==2 + ReleaseMutex(p.mutex); +#endif + } +} + +static void greycstoration_mutex_destroy(_greycstoration_params &p) { + if (p.nb_threads>1) { +#if cimg_OS==1 && defined(_PTHREAD_H) + if (p.mutex) pthread_mutex_destroy(p.mutex); +#elif cimg_OS==2 + CloseHandle(p.mutex); +#endif + p.mutex = 0; + } +} + +#if cimg_OS==1 +static void* greycstoration_thread(void *arg) { +#elif cimg_OS==2 + static DWORD WINAPI greycstoration_thread(void *arg) { +#endif + _greycstoration_params &p = *(_greycstoration_params*)arg; + greycstoration_mutex_lock(p); + const CImg &mask = *(p.mask); + CImg &source = *(p.source); + + if (!p.tile) { + + // Non-tiled version + //------------------ + if (p.patch_based) source.blur_patch(p.patch_size,p.sigma_p,p.sigma_s,p.lookup_size,p.fast_approx); + else source.blur_anisotropic(mask,p.amplitude,p.sharpness,p.anisotropy,p.alpha,p.sigma,p.dl,p.da,p.gauss_prec, + p.interpolation,p.fast_approx,p.gfact); + + } else { + + // Tiled version + //--------------- + CImg &temporary = *(p.temporary); + const bool threed = (source.depth>1); + const unsigned int b = p.tile_border; + unsigned int ctile = 0; + if (threed) { + for (unsigned int z=0; z img = source.get_crop(x-b,y-b,z-b,xe+b,ye+b,ze+b,true); + CImg mask_tile = mask.is_empty()?mask:mask.get_crop(x-b,y-b,z-b,xe+b,ye+b,ze+b,true); + img.greycstoration_params[0] = p; + greycstoration_mutex_unlock(p); + if (p.patch_based) img.blur_patch(p.patch_size,p.sigma_p,p.sigma_s,p.lookup_size,p.fast_approx); + else img.blur_anisotropic(mask_tile,p.amplitude,p.sharpness,p.anisotropy, + p.alpha,p.sigma,p.dl,p.da,p.gauss_prec,p.interpolation,p.fast_approx,p.gfact); + greycstoration_mutex_lock(p); + temporary.draw_image(x,y,z,img.crop(b,b,b,img.width-b,img.height-b,img.depth-b)); + } + } else { + for (unsigned int y=0; y img = source.get_crop(x-b,y-b,xe+b,ye+b,true); + CImg mask_tile = mask.is_empty()?mask:mask.get_crop(x-b,y-b,xe+b,ye+b,true); + img.greycstoration_params[0] = p; + greycstoration_mutex_unlock(p); + if (p.patch_based) img.blur_patch(p.patch_size,p.sigma_p,p.sigma_s,p.lookup_size,p.fast_approx); + else img.blur_anisotropic(mask_tile,p.amplitude,p.sharpness,p.anisotropy, + p.alpha,p.sigma,p.dl,p.da,p.gauss_prec,p.interpolation,p.fast_approx,p.gfact); + temporary.draw_image(x,y,img.crop(b,b,img.width-b,img.height-b)); + greycstoration_mutex_lock(p); + } + } + } + greycstoration_mutex_unlock(p); + + if (!p.thread) { + if (p.nb_threads>1) { + bool stopflag = true; + do { + stopflag = true; + for (unsigned int k=1; kstop_request)) ++(*greycstoration_params->counter); else return *this; +#define cimg_plugin_greycstoration_lock \ + greycstoration_mutex_lock(greycstoration_params[0]); +#define cimg_plugin_greycstoration_unlock \ + greycstoration_mutex_unlock(greycstoration_params[0]); + +#endif diff --git a/src/libs/greycstoration/greycstorationiface.cpp b/src/libs/greycstoration/greycstorationiface.cpp new file mode 100644 index 00000000..32f60514 --- /dev/null +++ b/src/libs/greycstoration/greycstorationiface.cpp @@ -0,0 +1,473 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-12-03 + * Description : Greycstoration interface. + * + * Copyright (C) 2007-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +/** Don't use CImg interface (keyboard/mouse interaction) */ +#define cimg_display 0 +/** Only print debug information on the console */ +#define cimg_debug 1 + +#ifdef HAVE_CONFIG_H +#include +#endif + +// C++ includes. + +#include + +// Local includes. + +#define cimg_plugin "greycstoration.h" +// Unix-like (Linux, Solaris, BSD, MacOSX, Irix,...). +#if defined(unix) || defined(__unix) || defined(__unix__) \ + || defined(linux) || defined(__linux) || defined(__linux__) \ + || defined(sun) || defined(__sun) \ + || defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined __DragonFly__ \ + || defined(__MACOSX__) || defined(__APPLE__) \ + || defined(sgi) || defined(__sgi) \ + || defined(__CYGWIN__) +#include +#endif + +/** Number of children threads used to run Greystoration algorithm + For the moment we use only one thread. See B.K.O #186642 for details. + Multithreading management need to be fixed into CImg. + */ +#define COMPUTATION_THREAD 2 + +/** Uncomment this line if you use future GreycStoration implementation with GFact parameter + */ +#define GREYSTORATION_USING_GFACT 1 + +// Local includes. + +#include "ddebug.h" +#include "greycstorationsettings.h" +#include "greycstorationiface.h" + +// CImg includes. + +#include "CImg.h" + +using namespace cimg_library; + +namespace Digikam +{ + +class GreycstorationIfacePriv +{ + +public: + + GreycstorationIfacePriv() + { + mode = GreycstorationIface::Restore; + gfact = 1.0; + } + + float gfact; + + int mode; // The interface running mode. + + TQImage inPaintingMask; // Mask for inpainting. + + GreycstorationSettings settings; // Current Greycstoraion algorithm settings. + + CImg<> img; // Main image. + CImg mask; // The mask used with inpaint or resize mode +}; + +GreycstorationIface::GreycstorationIface(DImg *orgImage, + GreycstorationSettings settings, + int mode, + int newWidth, int newHeight, + const TQImage& inPaintingMask, + TQObject *parent) + : DImgThreadedFilter(orgImage, parent) +{ + d = new GreycstorationIfacePriv; + d->settings = settings; + d->mode = mode; + d->inPaintingMask = inPaintingMask; + + if (m_orgImage.sixteenBit()) // 16 bits image. + d->gfact = 1.0/256.0; + + if (d->mode == Resize || d->mode == SimpleResize) + { + m_destImage = DImg(newWidth, newHeight, + m_orgImage.sixteenBit(), m_orgImage.hasAlpha()); + DDebug() << "GreycstorationIface::Resize: new size: (" + << newWidth << ", " << newHeight << ")" << endl; + } + else + { + m_destImage = DImg(m_orgImage.width(), m_orgImage.height(), + m_orgImage.sixteenBit(), m_orgImage.hasAlpha()); + } + + initFilter(); +} + +GreycstorationIface::~GreycstorationIface() +{ + delete d; +} + +// We need to re-implemente this method from DImgThreadedFilter class because +// target image size can be different from original if d->mode = Resize. + +void GreycstorationIface::initFilter() +{ + if (m_orgImage.width() && m_orgImage.height()) + { + if (m_parent) + start(); // m_parent is valide, start thread ==> run() + else + startComputation(); // no parent : no using thread. + } + else // No image data + { + if (m_parent) // If parent then send event about a problem. + { + postProgress(0, false, false); + DDebug() << m_name << "::No valid image data !!! ..." << endl; + } + } +} + +void GreycstorationIface::stopComputation() +{ + // Because Greycstoration algorithm run in a child thread, we need + // to stop it before to stop this thread. + if (d->img.greycstoration_is_running()) + { + // If the user abort, we stop the algorithm. + DDebug() << "Stop Greycstoration computation..." << endl; + d->img.greycstoration_stop(); + } + + // And now when stop main loop and clean up all + DImgThreadedFilter::stopComputation(); +} + +void GreycstorationIface::filterImage() +{ + int x, y; + + DDebug() << "GreycstorationIface::Initialization..." << endl; + + // Copy the src image data into a CImg type image with three channels and no alpha. + + uchar* imageData = m_orgImage.bits(); + int imageWidth = m_orgImage.width(); + int imageHeight = m_orgImage.height(); + d->img = CImg<>(imageWidth, imageHeight, 1, 4); + + if (!m_orgImage.sixteenBit()) // 8 bits image. + { + uchar *ptr = imageData; + + for (y = 0; y < imageHeight; y++) + { + for (x = 0; x < imageWidth; x++) + { + d->img(x, y, 0) = ptr[0]; // Blue. + d->img(x, y, 1) = ptr[1]; // Green. + d->img(x, y, 2) = ptr[2]; // Red. + d->img(x, y, 3) = ptr[3]; // Alpha. + ptr += 4; + } + } + } + else // 16 bits image. + { + unsigned short *ptr = (unsigned short *)imageData; + + for (y = 0; y < imageHeight; y++) + { + for (x = 0; x < imageWidth; x++) + { + d->img(x, y, 0) = ptr[0]; // Blue. + d->img(x, y, 1) = ptr[1]; // Green. + d->img(x, y, 2) = ptr[2]; // Red. + d->img(x, y, 3) = ptr[3]; // Alpha. + ptr += 4; + } + } + } + + DDebug() << "GreycstorationIface::Process Computation..." << endl; + + try + { + switch (d->mode) + { + case Restore: + restoration(); + break; + + case InPainting: + inpainting(); + break; + + case Resize: + resize(); + break; + + case SimpleResize: + simpleResize(); + break; + } + } + catch(...) // Everything went wrong. + { + DDebug() << "GreycstorationIface::Error during Greycstoration filter computation!" << endl; + + if (m_parent) + postProgress( 0, false, false ); + + return; + } + + if (m_cancel) + return; + + // Copy CImg onto destination. + + DDebug() << "GreycstorationIface::Finalization..." << endl; + + uchar* newData = m_destImage.bits(); + int newWidth = m_destImage.width(); + int newHeight = m_destImage.height(); + + if (!m_orgImage.sixteenBit()) // 8 bits image. + { + uchar *ptr = newData; + + for (y = 0; y < newHeight; y++) + { + for (x = 0; x < newWidth; x++) + { + // Overwrite RGB values to destination. + ptr[0] = static_cast(d->img(x, y, 0)); // Blue + ptr[1] = static_cast(d->img(x, y, 1)); // Green + ptr[2] = static_cast(d->img(x, y, 2)); // Red + ptr[3] = static_cast(d->img(x, y, 3)); // Alpha + ptr += 4; + } + } + } + else // 16 bits image. + { + unsigned short *ptr = (unsigned short *)newData; + + for (y = 0; y < newHeight; y++) + { + for (x = 0; x < newWidth; x++) + { + // Overwrite RGB values to destination. + ptr[0] = static_cast(d->img(x, y, 0)); // Blue + ptr[1] = static_cast(d->img(x, y, 1)); // Green + ptr[2] = static_cast(d->img(x, y, 2)); // Red + ptr[3] = static_cast(d->img(x, y, 3)); // Alpha + ptr += 4; + } + } + } +} + +void GreycstorationIface::restoration() +{ + for (uint iter = 0 ; !m_cancel && (iter < d->settings.nbIter) ; iter++) + { + // This function will start a thread running one iteration of the GREYCstoration filter. + // It returns immediately, so you can do what you want after (update a progress bar for + // instance). + d->img.greycstoration_run(d->settings.amplitude, + d->settings.sharpness, + d->settings.anisotropy, + d->settings.alpha, + d->settings.sigma, +#ifdef GREYSTORATION_USING_GFACT + d->gfact, +#endif + d->settings.dl, + d->settings.da, + d->settings.gaussPrec, + d->settings.interp, + d->settings.fastApprox, + d->settings.tile, + d->settings.btile, + COMPUTATION_THREAD); + + iterationLoop(iter); + } +} + +void GreycstorationIface::inpainting() +{ + if (!d->inPaintingMask.isNull()) + { + // Copy the inpainting image data into a CImg type image with three channels and no alpha. + + int x, y; + + d->mask = CImg(d->inPaintingMask.width(), d->inPaintingMask.height(), 1, 3); + uchar *ptr = d->inPaintingMask.bits(); + + for (y = 0; y < d->inPaintingMask.height(); y++) + { + for (x = 0; x < d->inPaintingMask.width(); x++) + { + d->mask(x, y, 0) = ptr[2]; // blue. + d->mask(x, y, 1) = ptr[1]; // green. + d->mask(x, y, 2) = ptr[0]; // red. + ptr += 4; + } + } + } + else + { + DDebug() << "Inpainting image: mask is null!" << endl; + m_cancel = true; + return; + } + + for (uint iter=0 ; !m_cancel && (iter < d->settings.nbIter) ; iter++) + { + // This function will start a thread running one iteration of the GREYCstoration filter. + // It returns immediately, so you can do what you want after (update a progress bar for + // instance). + d->img.greycstoration_run(d->mask, + d->settings.amplitude, + d->settings.sharpness, + d->settings.anisotropy, + d->settings.alpha, + d->settings.sigma, +#ifdef GREYSTORATION_USING_GFACT + d->gfact, +#endif + d->settings.dl, + d->settings.da, + d->settings.gaussPrec, + d->settings.interp, + d->settings.fastApprox, + d->settings.tile, + d->settings.btile, + COMPUTATION_THREAD); + + iterationLoop(iter); + } +} + +void GreycstorationIface::resize() +{ + const bool anchor = true; // Anchor original pixels. + const unsigned int init = 5; // Initial estimate (1=block, 3=linear, 5=bicubic). + + int w = m_destImage.width(); + int h = m_destImage.height(); + + d->mask.assign(d->img.dimx(), d->img.dimy(), 1, 1, 255); + + if (!anchor) + d->mask.resize(w, h, 1, 1, 1); + else + d->mask = !d->mask.resize(w, h, 1, 1, 4); + + d->img.resize(w, h, 1, -100, init); + + for (uint iter = 0 ; !m_cancel && (iter < d->settings.nbIter) ; iter++) + { + // This function will start a thread running one iteration of the GREYCstoration filter. + // It returns immediately, so you can do what you want after (update a progress bar for + // instance). + d->img.greycstoration_run(d->mask, + d->settings.amplitude, + d->settings.sharpness, + d->settings.anisotropy, + d->settings.alpha, + d->settings.sigma, +#ifdef GREYSTORATION_USING_GFACT + d->gfact, +#endif + d->settings.dl, + d->settings.da, + d->settings.gaussPrec, + d->settings.interp, + d->settings.fastApprox, + d->settings.tile, + d->settings.btile, + COMPUTATION_THREAD); + + iterationLoop(iter); + } +} + +void GreycstorationIface::simpleResize() +{ + const unsigned int method = 3; // Initial estimate (0, none, 1=block, 3=linear, 4=grid, 5=bicubic). + + int w = m_destImage.width(); + int h = m_destImage.height(); + + while (d->img.dimx() > 2*w && + d->img.dimy() > 2*h) + { + d->img.resize_halfXY(); + } + + d->img.resize(w, h, -100, -100, method); +} + +void GreycstorationIface::iterationLoop(uint iter) +{ + uint mp = 0; + uint p = 0; + + do + { + usleep(100000); + + if (m_parent && !m_cancel) + { + // Update the progress bar in dialog. We simply computes the global + // progression index (including all iterations). + + p = (uint)((iter*100 + d->img.greycstoration_progress())/d->settings.nbIter); + + if (p > mp) + { + postProgress(p); + mp = p; + } + } + } + while (d->img.greycstoration_is_running() && !m_cancel); + + // A delay is require here. I suspect a sync problem between threads + // used by GreycStoration algorithm. + usleep(100000); +} + +} // NameSpace Digikam diff --git a/src/libs/greycstoration/greycstorationiface.h b/src/libs/greycstoration/greycstorationiface.h new file mode 100644 index 00000000..fd7b248b --- /dev/null +++ b/src/libs/greycstoration/greycstorationiface.h @@ -0,0 +1,89 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-12-03 + * Description : Greycstoration interface. + * + * Copyright (C) 2007-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef GREYCSTORATIONIFACE_H +#define GREYCSTORATIONIFACE_H + +// TQt includes. + +#include + +// Local includes. + +#include "dimg.h" +#include "dimgthreadedfilter.h" +#include "greycstorationsettings.h" +#include "digikam_export.h" + +class TQObject; + +namespace Digikam +{ + +class GreycstorationIfacePriv; + +class DIGIKAM_EXPORT GreycstorationIface : public DImgThreadedFilter +{ + +public: + + enum MODE + { + Restore = 0, + InPainting, + Resize, + SimpleResize // Mode to resize image without to use Greycstoration algorithm. + }; + +public: + + GreycstorationIface(DImg *orgImage, + GreycstorationSettings settings, + int mode=Restore, + int newWidth=0, int newHeight=0, + const TQImage& inPaintingMask=TQImage(), + TQObject *parent=0); + + ~GreycstorationIface(); + + void stopComputation(); + +private: + + void initFilter(); + void filterImage(); + + void restoration(); + void inpainting(); + void resize(); + void simpleResize(); + void iterationLoop(uint iter); + +private: + + GreycstorationIfacePriv *d; +}; + +} // NameSpace Digikam + +#endif /* GREYCSTORATIONIFACE_H */ diff --git a/src/libs/greycstoration/greycstorationsettings.h b/src/libs/greycstoration/greycstorationsettings.h new file mode 100644 index 00000000..e2686965 --- /dev/null +++ b/src/libs/greycstoration/greycstorationsettings.h @@ -0,0 +1,144 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-21-07 + * Description : Greycstoration settings container. + * + * Copyright (C) 2007 by Gilles Caulier + * + * For a full settings description, look at this url : + * http://www.greyc.ensicaen.fr/~dtschump/greycstoration/guide.html + * + * For demonstration of settings, look at this url : + * + * http://www.greyc.ensicaen.fr/~dtschump/greycstoration/demonstration.html + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef GREYCSTORATIONSETTINGS_H +#define GREYCSTORATIONSETTINGS_H + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class DIGIKAM_EXPORT GreycstorationSettings +{ + +public: + + enum INTERPOLATION + { + NearestNeighbor = 0, + Linear, + RungeKutta + }; + +public: + + GreycstorationSettings() + { + setRestorationDefaultSettings(); + }; + + ~GreycstorationSettings(){}; + + void setRestorationDefaultSettings() + { + fastApprox = true; + + tile = 256; + btile = 4; + + nbIter = 1; + interp = NearestNeighbor; + + amplitude = 60.0; + sharpness = 0.7; + anisotropy = 0.3; + alpha = 0.6; + sigma = 1.1; + gaussPrec = 2.0; + dl = 0.8; + da = 30.0; + }; + + void setInpaintingDefaultSettings() + { + fastApprox = true; + + tile = 256; + btile = 4; + + nbIter = 30; + interp = NearestNeighbor; + + amplitude = 20.0; + sharpness = 0.3; + anisotropy = 1.0; + alpha = 0.8; + sigma = 2.0; + gaussPrec = 2.0; + dl = 0.8; + da = 30.0; + }; + + void setResizeDefaultSettings() + { + fastApprox = true; + + tile = 256; + btile = 4; + + nbIter = 3; + interp = NearestNeighbor; + + amplitude = 20.0; + sharpness = 0.2; + anisotropy = 0.9; + alpha = 0.1; + sigma = 1.5; + gaussPrec = 2.0; + dl = 0.8; + da = 30.0; + }; + +public: + + bool fastApprox; + + int tile; + int btile; + + uint nbIter; + uint interp; + + float amplitude; + float sharpness; + float anisotropy; + float alpha; + float sigma; + float gaussPrec; + float dl; + float da; +}; + +} // namespace Digikam + +#endif // GREYCSTORATIONSETTINGS_H diff --git a/src/libs/greycstoration/greycstorationwidget.cpp b/src/libs/greycstoration/greycstorationwidget.cpp new file mode 100644 index 00000000..93f25799 --- /dev/null +++ b/src/libs/greycstoration/greycstorationwidget.cpp @@ -0,0 +1,377 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-09-13 + * Description : Greycstoration settings widgets + * + * Copyright (C) 2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include + +// LibKDcraw includes. + +#include +#include + +// Local includes. + +#include "greycstorationwidget.h" +#include "greycstorationwidget.moc" + +using namespace KDcrawIface; + +namespace Digikam +{ + +class GreycstorationWidgetPriv +{ + +public: + + GreycstorationWidgetPriv() + { + parent = 0; + + advancedPage = 0; + alphaInput = 0; + alphaLabel = 0; + amplitudeInput = 0; + amplitudeLabel = 0; + anisotropyInput = 0; + anisotropyLabel = 0; + btileInput = 0; + btileLabel = 0; + daInput = 0; + daLabel = 0; + dlInput = 0; + dlLabel = 0; + fastApproxCBox = 0; + gaussianPrecInput = 0; + gaussianPrecLabel = 0; + generalPage = 0; + interpolationBox = 0; + interpolationLabel = 0; + iterationInput = 0; + iterationLabel = 0; + sharpnessInput = 0; + sharpnessLabel = 0; + sigmaInput = 0; + sigmaLabel = 0; + tileInput = 0; + tileLabel = 0; + } + + TQLabel *alphaLabel; + TQLabel *amplitudeLabel; + TQLabel *anisotropyLabel; + TQLabel *btileLabel; + TQLabel *daLabel; + TQLabel *dlLabel; + TQLabel *gaussianPrecLabel; + TQLabel *interpolationLabel; + TQLabel *iterationLabel; + TQLabel *sharpnessLabel; + TQLabel *sigmaLabel; + TQLabel *tileLabel; + + TQWidget *advancedPage; + TQWidget *generalPage; + + TQCheckBox *fastApproxCBox; + + TQTabWidget *parent; + + RComboBox *interpolationBox; + + RDoubleNumInput *alphaInput; + RDoubleNumInput *amplitudeInput; + RDoubleNumInput *anisotropyInput; + RDoubleNumInput *daInput; + RDoubleNumInput *dlInput; + RDoubleNumInput *gaussianPrecInput; + RDoubleNumInput *sharpnessInput; + RDoubleNumInput *sigmaInput; + + RIntNumInput *btileInput; + RIntNumInput *iterationInput; + RIntNumInput *tileInput; +}; + +GreycstorationWidget::GreycstorationWidget(TQTabWidget *parent) + : TQObject(parent) +{ + d = new GreycstorationWidgetPriv; + d->parent = parent; + + // ------------------------------------------------------------- + + d->generalPage = new TQWidget( parent ); + TQGridLayout* grid1 = new TQGridLayout(d->generalPage, 6, 2, KDialog::spacingHint()); + parent->addTab( d->generalPage, i18n("General") ); + + d->sharpnessLabel = new TQLabel(i18n("Detail preservation:"), d->generalPage); + d->sharpnessInput = new RDoubleNumInput(d->generalPage); + d->sharpnessInput->setPrecision(2); + d->sharpnessInput->setRange(0.01, 1.0, 0.1); + TQWhatsThis::add( d->sharpnessInput, i18n("

Preservation of details to set the sharpening level " + "of the small features in the target image. " + "Higher values leave details sharp.")); + grid1->addMultiCellWidget(d->sharpnessLabel, 0, 0, 0, 0); + grid1->addMultiCellWidget(d->sharpnessInput, 0, 0, 1, 1); + + d->anisotropyLabel = new TQLabel(i18n("Anisotropy:"), d->generalPage); + d->anisotropyInput = new RDoubleNumInput(d->generalPage); + d->anisotropyInput->setPrecision(2); + d->anisotropyInput->setRange(0.0, 1.0, 0.1); + TQWhatsThis::add( d->anisotropyInput, i18n("

Anisotropic (directional) modifier of the details. " + "Keep it small for Gaussian noise.")); + grid1->addMultiCellWidget(d->anisotropyLabel, 1, 1, 0, 0); + grid1->addMultiCellWidget(d->anisotropyInput, 1, 1, 1, 1); + + d->amplitudeLabel = new TQLabel(i18n("Smoothing:"), d->generalPage); + d->amplitudeInput = new RDoubleNumInput(d->generalPage); + d->amplitudeInput->setPrecision(2); + d->amplitudeInput->setRange(0.01, 500.0, 0.1); + TQWhatsThis::add( d->amplitudeInput, i18n("

Total smoothing power: if the Detail Factor sets the relative " + "smoothing and the Anisotropy Factor the direction, " + "the Smoothing Factor sets the overall effect.")); + grid1->addMultiCellWidget(d->amplitudeLabel, 2, 2, 0, 0); + grid1->addMultiCellWidget(d->amplitudeInput, 2, 2, 1, 1); + + d->sigmaLabel = new TQLabel(i18n("Regularity:"), d->generalPage); + d->sigmaInput = new RDoubleNumInput(d->generalPage); + d->sigmaInput->setPrecision(2); + d->sigmaInput->setRange(0.0, 10.0, 0.1); + TQWhatsThis::add( d->sigmaInput, i18n("

This value controls the evenness of smoothing to the image. " + "Do not use a high value here, or the " + "target image will be completely blurred.")); + grid1->addMultiCellWidget(d->sigmaLabel, 3, 3, 0, 0); + grid1->addMultiCellWidget(d->sigmaInput, 3, 3, 1, 1); + + d->iterationLabel = new TQLabel(i18n("Iterations:"), d->generalPage); + d->iterationInput = new RIntNumInput(d->generalPage); + d->iterationInput->setRange(1, 5000, 1); + TQWhatsThis::add( d->iterationInput, i18n("

Sets the number of times the filter is applied to " + "the image.")); + grid1->addMultiCellWidget(d->iterationLabel, 4, 4, 0, 0); + grid1->addMultiCellWidget(d->iterationInput, 4, 4, 1, 1); + + d->alphaLabel = new TQLabel(i18n("Noise:"), d->generalPage); + d->alphaInput = new RDoubleNumInput(d->generalPage); + d->alphaInput->setPrecision(2); + d->alphaInput->setRange(0.01, 1.0, 0.1); + TQWhatsThis::add( d->alphaInput, i18n("

Sets the noise scale.")); + grid1->addMultiCellWidget(d->alphaLabel, 5, 5, 0, 0); + grid1->addMultiCellWidget(d->alphaInput, 5, 5, 1, 1); + grid1->setRowStretch(6, 10); + + // ------------------------------------------------------------- + + d->advancedPage = new TQWidget( parent ); + TQGridLayout* grid2 = new TQGridLayout(d->advancedPage, 6, 2, KDialog::spacingHint()); + parent->addTab( d->advancedPage, i18n("Advanced Settings") ); + + d->daLabel = new TQLabel(i18n("Angular step:"), d->advancedPage); + d->daInput = new RDoubleNumInput(d->advancedPage); + d->daInput->setPrecision(2); + d->daInput->setRange(0.0, 90.0, 1.0); + TQWhatsThis::add( d->daInput, i18n("

Set here the angular integration step (in degrees) " + "analogous to anisotropy.")); + grid2->addMultiCellWidget(d->daLabel, 0, 0, 0, 0); + grid2->addMultiCellWidget(d->daInput, 0, 0, 1, 1); + + d->dlLabel = new TQLabel(i18n("Integral step:"), d->advancedPage); + d->dlInput = new RDoubleNumInput(d->advancedPage); + d->dlInput->setPrecision(2); + d->dlInput->setRange(0.0, 1.0, 0.1); + TQWhatsThis::add( d->dlInput, i18n("

Set here the spatial integral step.")); + grid2->addMultiCellWidget(d->dlLabel, 1, 1, 0, 0); + grid2->addMultiCellWidget(d->dlInput, 1, 1, 1, 1); + + d->gaussianPrecLabel = new TQLabel(i18n("Gaussian:"), d->advancedPage); + d->gaussianPrecInput = new RDoubleNumInput(d->advancedPage); + d->gaussianPrecInput->setPrecision(2); + d->gaussianPrecInput->setRange(0.01, 20.0, 0.01); + TQWhatsThis::add( d->gaussianPrecInput, i18n("

Set here the precision of the Gaussian function.")); + grid2->addMultiCellWidget(d->gaussianPrecLabel, 2, 2, 0, 0); + grid2->addMultiCellWidget(d->gaussianPrecInput, 2, 2, 1, 1); + + d->tileLabel = new TQLabel(i18n("Tile size:"), d->advancedPage); + d->tileInput = new RIntNumInput(d->advancedPage); + d->tileInput->setRange(0, 2000, 1); + TQWhatsThis::add( d->tileInput, i18n("

Sets the tile size.")); + grid2->addMultiCellWidget(d->tileLabel, 3, 3, 0, 0); + grid2->addMultiCellWidget(d->tileInput, 3, 3, 1, 1); + + d->btileLabel = new TQLabel(i18n("Tile border:"), d->advancedPage); + d->btileInput = new RIntNumInput(d->advancedPage); + d->btileInput->setRange(1, 20, 1); + TQWhatsThis::add( d->btileInput, i18n("

Sets the size of each tile border.")); + grid2->addMultiCellWidget(d->btileLabel, 4, 4, 0, 0); + grid2->addMultiCellWidget(d->btileInput, 4, 4, 1, 1); + + d->interpolationLabel = new TQLabel(i18n("Interpolation:"), d->advancedPage); + d->interpolationBox = new RComboBox(d->advancedPage); + d->interpolationBox->insertItem( i18n("Nearest Neighbor"), GreycstorationSettings::NearestNeighbor ); + d->interpolationBox->insertItem( i18n("Linear"), GreycstorationSettings::Linear ); + d->interpolationBox->insertItem( i18n("Runge-Kutta"), GreycstorationSettings::RungeKutta); + TQWhatsThis::add( d->interpolationBox, i18n("

Select the right interpolation method for the " + "desired image quality.")); + grid2->addMultiCellWidget(d->interpolationLabel, 5, 5, 0, 0); + grid2->addMultiCellWidget(d->interpolationBox, 5, 5, 1, 1); + + d->fastApproxCBox = new TQCheckBox(i18n("Fast approximation"), d->advancedPage); + TQWhatsThis::add( d->fastApproxCBox, i18n("

Enable fast approximation when rendering images.")); + grid2->addMultiCellWidget(d->fastApproxCBox, 6, 6, 0, 1); +} + +GreycstorationWidget::~GreycstorationWidget() +{ + delete d; +} + +void GreycstorationWidget::setEnabled(bool b) +{ + d->generalPage->setEnabled(b); + d->advancedPage->setEnabled(b); + d->parent->setTabEnabled(d->generalPage, b); + d->parent->setTabEnabled(d->advancedPage, b); +} + +void GreycstorationWidget::setSettings(GreycstorationSettings settings) +{ + blockSignals(true); + d->alphaInput->setValue(settings.alpha); + d->amplitudeInput->setValue(settings.amplitude); + d->anisotropyInput->setValue(settings.anisotropy); + d->btileInput->setValue(settings.btile); + d->daInput->setValue(settings.da); + d->dlInput->setValue(settings.dl); + d->fastApproxCBox->setChecked(settings.fastApprox); + d->gaussianPrecInput->setValue(settings.gaussPrec); + d->interpolationBox->setCurrentItem(settings.interp); + d->iterationInput->setValue(settings.nbIter); + d->sharpnessInput->setValue(settings.sharpness); + d->sigmaInput->setValue(settings.sigma); + d->tileInput->setValue(settings.tile); + blockSignals(false); +} + +void GreycstorationWidget::setDefaultSettings(GreycstorationSettings settings) +{ + blockSignals(true); + d->alphaInput->setDefaultValue(settings.alpha); + d->amplitudeInput->setDefaultValue(settings.amplitude); + d->anisotropyInput->setDefaultValue(settings.anisotropy); + d->btileInput->setDefaultValue(settings.btile); + d->daInput->setDefaultValue(settings.da); + d->dlInput->setDefaultValue(settings.dl); + d->fastApproxCBox->setChecked(settings.fastApprox); + d->gaussianPrecInput->setDefaultValue(settings.gaussPrec); + d->interpolationBox->setDefaultItem(settings.interp); + d->iterationInput->setDefaultValue(settings.nbIter); + d->sharpnessInput->setDefaultValue(settings.sharpness); + d->sigmaInput->setDefaultValue(settings.sigma); + d->tileInput->setDefaultValue(settings.tile); + blockSignals(false); +} + +GreycstorationSettings GreycstorationWidget::getSettings() +{ + GreycstorationSettings settings; + + settings.fastApprox = d->fastApproxCBox->isChecked(); + settings.interp = d->interpolationBox->currentItem(); + settings.amplitude = d->amplitudeInput->value(); + settings.sharpness = d->sharpnessInput->value(); + settings.anisotropy = d->anisotropyInput->value(); + settings.alpha = d->alphaInput->value(); + settings.sigma = d->sigmaInput->value(); + settings.gaussPrec = d->gaussianPrecInput->value(); + settings.dl = d->dlInput->value(); + settings.da = d->daInput->value(); + settings.nbIter = d->iterationInput->value(); + settings.tile = d->tileInput->value(); + settings.btile = d->btileInput->value(); + + return settings; +} + +bool GreycstorationWidget::loadSettings(TQFile& file, const TQString& header) +{ + TQTextStream stream( &file ); + + if (stream.readLine() != header) + return false; + + blockSignals(true); + + GreycstorationSettings settings; + settings.fastApprox = stream.readLine().toInt(); + settings.interp = stream.readLine().toInt(); + settings.amplitude = stream.readLine().toDouble(); + settings.sharpness = stream.readLine().toDouble(); + settings.anisotropy = stream.readLine().toDouble(); + settings.alpha = stream.readLine().toDouble(); + settings.sigma = stream.readLine().toDouble(); + settings.gaussPrec = stream.readLine().toDouble(); + settings.dl = stream.readLine().toDouble(); + settings.da = stream.readLine().toDouble(); + settings.nbIter = stream.readLine().toInt(); + settings.tile = stream.readLine().toInt(); + settings.btile = stream.readLine().toInt(); + setSettings(settings); + + blockSignals(false); + return true; +} + +void GreycstorationWidget::saveSettings(TQFile& file, const TQString& header) +{ + GreycstorationSettings settings = getSettings(); + TQTextStream stream( &file ); + stream << header << "\n"; + stream << settings.fastApprox << "\n"; + stream << settings.interp << "\n"; + stream << settings.amplitude << "\n"; + stream << settings.sharpness << "\n"; + stream << settings.anisotropy << "\n"; + stream << settings.alpha << "\n"; + stream << settings.sigma << "\n"; + stream << settings.gaussPrec << "\n"; + stream << settings.dl << "\n"; + stream << settings.da << "\n"; + stream << settings.nbIter << "\n"; + stream << settings.tile << "\n"; + stream << settings.btile << "\n"; +} + +} // NameSpace Digikam diff --git a/src/libs/greycstoration/greycstorationwidget.h b/src/libs/greycstoration/greycstorationwidget.h new file mode 100644 index 00000000..22797071 --- /dev/null +++ b/src/libs/greycstoration/greycstorationwidget.h @@ -0,0 +1,70 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-09-13 + * Description : Greycstoration settings widgets + * + * Copyright (C) 2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef GREYCSTORATION_WIDGET_H +#define GREYCSTORATION_WIDGET_H + +// TQt includes. + +#include +#include +#include + +// Local includes. + +#include "digikam_export.h" +#include "greycstorationsettings.h" + +class TQTabWidget; + +namespace Digikam +{ + +class GreycstorationWidgetPriv; + +class DIGIKAM_EXPORT GreycstorationWidget : public TQObject +{ + TQ_OBJECT + + +public: + + GreycstorationWidget(TQTabWidget *parent); + ~GreycstorationWidget(); + + void setSettings(GreycstorationSettings settings); + void setDefaultSettings(GreycstorationSettings settings); + GreycstorationSettings getSettings(); + + bool loadSettings(TQFile& file, const TQString& header); + void saveSettings(TQFile& file, const TQString& header); + + void setEnabled(bool); + +private: + + GreycstorationWidgetPriv* d; +}; + +} // NameSpace Digikam + +#endif /* GREYCSTORATION_WIDGET_H */ diff --git a/src/libs/histogram/Makefile.am b/src/libs/histogram/Makefile.am new file mode 100644 index 00000000..56ea96f6 --- /dev/null +++ b/src/libs/histogram/Makefile.am @@ -0,0 +1,16 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libhistogram.la + +libhistogram_la_SOURCES = imagehistogram.cpp + +libhistogram_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +INCLUDES = -I$(top_srcdir)/src/libs/dimg \ + -I$(top_srcdir)/src/digikam \ + $(LIBKDCRAW_CFLAGS) \ + $(all_includes) + +digikaminclude_HEADERS = imagehistogram.h + +digikamincludedir = $(includedir)/digikam diff --git a/src/libs/histogram/imagehistogram.cpp b/src/libs/histogram/imagehistogram.cpp new file mode 100644 index 00000000..bee55624 --- /dev/null +++ b/src/libs/histogram/imagehistogram.cpp @@ -0,0 +1,548 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-07-21 + * Description : image histogram manipulation methods. + * + * Copyright (C) 2004-2007 by Gilles Caulier + * + * Some code parts are inspired from gimp 2.0 + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// C++ includes. + +#include +#include +#include + +// TQt includes. + +#include +#include + +// KDE includes. + +#include + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "imagehistogram.h" + +namespace Digikam +{ + +class ImageHistogramPriv +{ + +public: + + // Using a structure instead a class is more fast + // (access with memset() and bytes manipulation). + + struct double_packet + { + double value; + double red; + double green; + double blue; + double alpha; + }; + +public: + + ImageHistogramPriv() + { + parent = 0; + imageData = 0; + histogram = 0; + runningFlag = true; + } + + /** The histogram data.*/ + struct double_packet *histogram; + + /** Image information.*/ + uchar *imageData; + uint imageWidth; + uint imageHeight; + + /** Numbers of histogram segments dependaing of image bytes depth*/ + int histoSegments; + + /** To post event from thread to parent.*/ + TQObject *parent; + + /** Used to stop thread during calculations.*/ + bool runningFlag; +}; + +ImageHistogram::ImageHistogram(const DImg& image, TQObject *parent) + : TQThread() +{ + setup(image.bits(), image.width(), image.height(), image.sixteenBit(), parent); +} + +ImageHistogram::ImageHistogram(uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits, TQObject *parent) + : TQThread() +{ + setup(i_data, i_w, i_h, i_sixteenBits, parent); +} + +void ImageHistogram::setup(uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits, TQObject *parent) +{ + d = new ImageHistogramPriv; + d->imageData = i_data; + d->imageWidth = i_w; + d->imageHeight = i_h; + d->parent = parent; + d->histoSegments = i_sixteenBits ? 65536 : 256; + + if (d->imageData && d->imageWidth && d->imageHeight) + { + if (d->parent) + start(); + else + calcHistogramValues(); + } + else + { + if (d->parent) + postProgress(false, false); + } +} + +ImageHistogram::~ImageHistogram() +{ + stopCalcHistogramValues(); + + if (d->histogram) + delete [] d->histogram; + + delete d; +} + +int ImageHistogram::getHistogramSegment(void) +{ + return d->histoSegments; +} + +void ImageHistogram::postProgress(bool starting, bool success) +{ + EventData *eventData = new EventData(); + eventData->starting = starting; + eventData->success = success; + eventData->histogram = this; + TQApplication::postEvent(d->parent, new TQCustomEvent(TQEvent::User, eventData)); +} + +void ImageHistogram::stopCalcHistogramValues(void) +{ + d->runningFlag = false; + wait(); +} + +// List of threaded operations. + +void ImageHistogram::run() +{ + calcHistogramValues(); +} + +void ImageHistogram::calcHistogramValues() +{ + uint i; + int max; + + if (d->parent) + postProgress(true, false); + + d->histogram = new ImageHistogramPriv::double_packet[d->histoSegments]; + memset(d->histogram, 0, d->histoSegments*sizeof(ImageHistogramPriv::double_packet)); + + if ( !d->histogram ) + { + DWarning() << ("HistogramWidget::calcHistogramValues: Unable to allocate memory!") << endl; + + if (d->parent) + postProgress(false, false); + + return; + } + + memset(d->histogram, 0, d->histoSegments*sizeof(struct ImageHistogramPriv::double_packet)); + + if (d->histoSegments == 65536) // 16 bits image. + { + unsigned short blue, green, red, alpha; + unsigned short *data = (unsigned short*)d->imageData; + + for (i = 0 ; (i < d->imageHeight*d->imageWidth*4) && d->runningFlag ; i+=4) + { + blue = data[ i ]; + green = data[i+1]; + red = data[i+2]; + alpha = data[i+3]; + + d->histogram[blue].blue++; + d->histogram[green].green++; + d->histogram[red].red++; + d->histogram[alpha].alpha++; + + max = (blue > green) ? blue : green; + + if (red > max) + d->histogram[red].value++; + else + d->histogram[max].value++; + } + } + else // 8 bits images. + { + uchar blue, green, red, alpha; + uchar *data = d->imageData; + + for (i = 0 ; (i < d->imageHeight*d->imageWidth*4) && d->runningFlag ; i+=4) + { + blue = data[ i ]; + green = data[i+1]; + red = data[i+2]; + alpha = data[i+3]; + + d->histogram[blue].blue++; + d->histogram[green].green++; + d->histogram[red].red++; + d->histogram[alpha].alpha++; + + max = (blue > green) ? blue : green; + + if (red > max) + d->histogram[red].value++; + else + d->histogram[max].value++; + } + } + + if (d->parent && d->runningFlag) + postProgress(false, true); +} + +double ImageHistogram::getCount(int channel, int start, int end) +{ + int i; + double count = 0.0; + + if ( !d->histogram || start < 0 || + end > d->histoSegments-1 || start > end ) + return 0.0; + + switch(channel) + { + case ImageHistogram::ValueChannel: + for (i = start ; i <= end ; i++) + count += d->histogram[i].value; + break; + + case ImageHistogram::RedChannel: + for (i = start ; i <= end ; i++) + count += d->histogram[i].red; + break; + + case ImageHistogram::GreenChannel: + for (i = start ; i <= end ; i++) + count += d->histogram[i].green; + break; + + case ImageHistogram::BlueChannel: + for (i = start ; i <= end ; i++) + count += d->histogram[i].blue; + break; + + case ImageHistogram::AlphaChannel: + for (i = start ; i <= end ; i++) + count += d->histogram[i].alpha; + break; + + default: + return 0.0; + break; + } + + return count; +} + +double ImageHistogram::getPixels() +{ + if ( !d->histogram ) + return 0.0; + + return(d->imageWidth * d->imageHeight); +} + +double ImageHistogram::getMean(int channel, int start, int end) +{ + int i; + double mean = 0.0; + double count; + + if ( !d->histogram || start < 0 || + end > d->histoSegments-1 || start > end ) + return 0.0; + + switch(channel) + { + case ImageHistogram::ValueChannel: + for (i = start ; i <= end ; i++) + mean += i * d->histogram[i].value; + break; + + case ImageHistogram::RedChannel: + for (i = start ; i <= end ; i++) + mean += i * d->histogram[i].red; + break; + + case ImageHistogram::GreenChannel: + for (i = start ; i <= end ; i++) + mean += i * d->histogram[i].green; + break; + + case ImageHistogram::BlueChannel: + for (i = start ; i <= end ; i++) + mean += i * d->histogram[i].blue; + break; + + case ImageHistogram::AlphaChannel: + for (i = start ; i <= end ; i++) + mean += i * d->histogram[i].alpha; + break; + + default: + return 0.0; + break; + } + + count = getCount(channel, start, end); + + if (count > 0.0) + return mean / count; + + return mean; +} + +int ImageHistogram::getMedian(int channel, int start, int end) +{ + int i; + double sum = 0.0; + double count; + + if ( !d->histogram || start < 0 || + end > d->histoSegments-1 || start > end ) + return 0; + + count = getCount(channel, start, end); + + switch(channel) + { + case ImageHistogram::ValueChannel: + for (i = start ; i <= end ; i++) + { + sum += d->histogram[i].value; + if (sum * 2 > count) return i; + } + break; + + case ImageHistogram::RedChannel: + for (i = start ; i <= end ; i++) + { + sum += d->histogram[i].red; + if (sum * 2 > count) return i; + } + break; + + case ImageHistogram::GreenChannel: + for (i = start ; i <= end ; i++) + { + sum += d->histogram[i].green; + if (sum * 2 > count) return i; + } + break; + + case ImageHistogram::BlueChannel: + for (i = start ; i <= end ; i++) + { + sum += d->histogram[i].blue; + if (sum * 2 > count) return i; + } + break; + + case ImageHistogram::AlphaChannel: + for (i = start ; i <= end ; i++) + { + sum += d->histogram[i].alpha; + if (sum * 2 > count) return i; + } + break; + + default: + return 0; + break; + } + + return -1; +} + +double ImageHistogram::getStdDev(int channel, int start, int end) +{ + int i; + double dev = 0.0; + double count; + double mean; + + if ( !d->histogram || start < 0 || + end > d->histoSegments-1 || start > end ) + return 0.0; + + mean = getMean(channel, start, end); + count = getCount(channel, start, end); + + if (count == 0.0) + count = 1.0; + + switch(channel) + { + case ImageHistogram::ValueChannel: + for (i = start ; i <= end ; i++) + dev += (i - mean) * (i - mean) * d->histogram[i].value; + break; + + case ImageHistogram::RedChannel: + for (i = start ; i <= end ; i++) + dev += (i - mean) * (i - mean) * d->histogram[i].red; + break; + + case ImageHistogram::GreenChannel: + for (i = start ; i <= end ; i++) + dev += (i - mean) * (i - mean) * d->histogram[i].green; + break; + + case ImageHistogram::BlueChannel: + for (i = start ; i <= end ; i++) + dev += (i - mean) * (i - mean) * d->histogram[i].blue; + break; + + case ImageHistogram::AlphaChannel: + for (i = start ; i <= end ; i++) + dev += (i - mean) * (i - mean) * d->histogram[i].alpha; + break; + + default: + return 0.0; + break; + } + + return sqrt(dev / count); +} + +double ImageHistogram::getValue(int channel, int bin) +{ + double value; + + if ( !d->histogram || bin < 0 || bin > d->histoSegments-1 ) + return 0.0; + + switch(channel) + { + case ImageHistogram::ValueChannel: + value = d->histogram[bin].value; + break; + + case ImageHistogram::RedChannel: + value = d->histogram[bin].red; + break; + + case ImageHistogram::GreenChannel: + value = d->histogram[bin].green; + break; + + case ImageHistogram::BlueChannel: + value = d->histogram[bin].blue; + break; + + case ImageHistogram::AlphaChannel: + value = d->histogram[bin].alpha; + break; + + default: + return 0.0; + break; + } + + return value; +} + +double ImageHistogram::getMaximum(int channel) +{ + double max = 0.0; + int x; + + if ( !d->histogram ) + return 0.0; + + switch(channel) + { + case ImageHistogram::ValueChannel: + for (x = 0 ; x < d->histoSegments ; x++) + if (d->histogram[x].value > max) + max = d->histogram[x].value; + break; + + case ImageHistogram::RedChannel: + for (x = 0 ; x < d->histoSegments ; x++) + if (d->histogram[x].red > max) + max = d->histogram[x].red; + break; + + case ImageHistogram::GreenChannel: + for (x = 0 ; x < d->histoSegments ; x++) + if (d->histogram[x].green > max) + max = d->histogram[x].green; + break; + + case ImageHistogram::BlueChannel: + for (x = 0 ; x < d->histoSegments ; x++) + if (d->histogram[x].blue > max) + max = d->histogram[x].blue; + break; + + case ImageHistogram::AlphaChannel: + for (x = 0 ; x < d->histoSegments ; x++) + if (d->histogram[x].alpha > max) + max = d->histogram[x].alpha; + break; + + default: + return 0.0; + break; + } + + return max; +} + +} // NameSpace Digikam + diff --git a/src/libs/histogram/imagehistogram.h b/src/libs/histogram/imagehistogram.h new file mode 100644 index 00000000..6beb4919 --- /dev/null +++ b/src/libs/histogram/imagehistogram.h @@ -0,0 +1,113 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-07-21 + * Description : image histogram manipulation methods. + * + * Copyright (C) 2004-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + + +#ifndef IMAGEHISTOGRAM_H +#define IMAGEHISTOGRAM_H + +// TQt includes. + +#include + +// Local includes. + +#include "digikam_export.h" + +class TQObject; + +namespace Digikam +{ + +class ImageHistogramPriv; +class DImg; + +class DIGIKAM_EXPORT ImageHistogram : public TQThread +{ + +public: + +enum HistogramChannelType +{ + ValueChannel = 0, + RedChannel, + GreenChannel, + BlueChannel, + AlphaChannel +}; + +class EventData +{ +public: + + EventData() + { + starting = false; + success = false; + histogram = 0; + } + + bool starting; + bool success; + ImageHistogram *histogram; +}; + +public: + + ImageHistogram(uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits, TQObject *parent=0); + + ImageHistogram(const DImg& image, TQObject *parent=0); + ~ImageHistogram(); + + void setup(uchar *i_data, uint i_w, uint i_h, bool i_sixteenBits, TQObject *parent); + + /** Method to stop threaded computations.*/ + void stopCalcHistogramValues(void); + + /** Methods for to manipulate the histogram data.*/ + double getCount(int channel, int start, int end); + double getMean(int channel, int start, int end); + double getPixels(); + double getStdDev(int channel, int start, int end); + double getValue(int channel, int bin); + double getMaximum(int channel); + + int getHistogramSegment(void); + int getMedian(int channel, int start, int end); + +private: + + ImageHistogramPriv* d; + +private: + + void calcHistogramValues(); + void postProgress(bool starting, bool success); + +protected: + + virtual void run(); +}; + +} // NameSpace Digikam + +#endif /* IMAGEHISTOGRAM_H */ diff --git a/src/libs/imageproperties/Makefile.am b/src/libs/imageproperties/Makefile.am new file mode 100644 index 00000000..2c4f021a --- /dev/null +++ b/src/libs/imageproperties/Makefile.am @@ -0,0 +1,51 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libimagepropertiesshowfoto.la libimagepropertiesdigikam.la \ + libimagepropertiescamgui.la + +# Image Properties SideBar for Camera GUI. + +libimagepropertiescamgui_la_SOURCES = imagepropertiessidebarcamgui.cpp cameraitempropertiestab.cpp + +libimagepropertiescamgui_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +# Image Properties SideBar for Showfoto (without digiKam database support). + +libimagepropertiesshowfoto_la_SOURCES = imagepropertiessidebar.cpp navigatebarwidget.cpp \ + imagepropertiesmetadatatab.cpp imagepropertiescolorstab.cpp \ + imagepropertiestab.cpp navigatebartab.cpp + +libimagepropertiesshowfoto_la_LIBADD = $(top_builddir)/src/libs/widgets/libwidgets.la \ + $(top_builddir)/src/libs/dmetadata/libdmetadata.la \ + $(top_builddir)/src/libs/dimg/libdimg.la \ + $(top_builddir)/src/libs/threadimageio/libthreadimageio.la \ + $(top_builddir)/src/libs/histogram/libhistogram.la + +libimagepropertiesshowfoto_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +# Image Properties SideBar for digiKam Main interface and Image Editor (digiKam database support). + +libimagepropertiesdigikam_la_SOURCES = imagedescedittab.cpp imagepropertiessidebar.cpp \ + imagepropertiessidebardb.cpp \ + talbumlistview.cpp imagepropertiesmetadatatab.cpp \ + imagepropertiescolorstab.cpp \ + navigatebarwidget.cpp imagepropertiestab.cpp navigatebartab.cpp + +libimagepropertiesdigikam_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +INCLUDES = -I$(top_srcdir)/src/libs/histogram \ + -I$(top_srcdir)/src/libs/themeengine \ + -I$(top_srcdir)/src/libs/dmetadata \ + -I$(top_srcdir)/src/libs/widgets/common \ + -I$(top_srcdir)/src/libs/widgets/iccprofiles \ + -I$(top_srcdir)/src/libs/widgets/metadata \ + -I$(top_srcdir)/src/libs/dialogs \ + -I$(top_srcdir)/src/libs/dimg \ + -I$(top_srcdir)/src/libs/threadimageio \ + -I$(top_srcdir)/src/utilities/cameragui \ + -I$(top_srcdir)/src/utilities/batch \ + -I$(top_srcdir)/src/digikam \ + $(LIBKEXIV2_CFLAGS) \ + $(LIBKDCRAW_CFLAGS) \ + $(all_includes) + diff --git a/src/libs/imageproperties/cameraitempropertiestab.cpp b/src/libs/imageproperties/cameraitempropertiestab.cpp new file mode 100644 index 00000000..8593e9c7 --- /dev/null +++ b/src/libs/imageproperties/cameraitempropertiestab.cpp @@ -0,0 +1,555 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-02-08 + * Description : A tab to display camera item information + * + * Copyright (C) 2006-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dmetadata.h" +#include "gpiteminfo.h" +#include "navigatebarwidget.h" +#include "cameraitempropertiestab.h" +#include "cameraitempropertiestab.moc" + +namespace Digikam +{ + +class CameraItemPropertiesTabPriv +{ +public: + + CameraItemPropertiesTabPriv() + { + title = 0; + file = 0; + folder = 0; + date = 0; + size = 0; + isReadable = 0; + isWritable = 0; + mime = 0; + dimensions = 0; + newFileName = 0; + downloaded = 0; + settingsArea = 0; + title2 = 0; + make = 0; + model = 0; + photoDate = 0; + aperture = 0; + focalLength = 0; + exposureTime = 0; + sensitivity = 0; + exposureMode = 0; + flash = 0; + whiteBalance = 0; + labelFile = 0; + labelFolder = 0; + labelFileIsReadable = 0; + labelFileIsWritable = 0; + labelFileDate = 0; + labelFileSize = 0; + labelImageMime = 0; + labelImageDimensions = 0; + labelNewFileName = 0; + labelAlreadyDownloaded = 0; + labelPhotoMake = 0; + labelPhotoModel = 0; + labelPhotoDateTime = 0; + labelPhotoAperture = 0; + labelPhotoFocalLength = 0; + labelPhotoExposureTime = 0; + labelPhotoSensitivity = 0; + labelPhotoExposureMode = 0; + labelPhotoFlash = 0; + labelPhotoWhiteBalance = 0; + } + + TQLabel *title; + TQLabel *file; + TQLabel *folder; + TQLabel *date; + TQLabel *size; + TQLabel *isReadable; + TQLabel *isWritable; + TQLabel *mime; + TQLabel *dimensions; + TQLabel *newFileName; + TQLabel *downloaded; + + TQLabel *title2; + TQLabel *make; + TQLabel *model; + TQLabel *photoDate; + TQLabel *aperture; + TQLabel *focalLength; + TQLabel *exposureTime; + TQLabel *sensitivity; + TQLabel *exposureMode; + TQLabel *flash; + TQLabel *whiteBalance; + + TQFrame *settingsArea; + + KSqueezedTextLabel *labelFile; + KSqueezedTextLabel *labelFolder; + KSqueezedTextLabel *labelFileIsReadable; + KSqueezedTextLabel *labelFileIsWritable; + KSqueezedTextLabel *labelFileDate; + KSqueezedTextLabel *labelFileSize; + KSqueezedTextLabel *labelImageMime; + KSqueezedTextLabel *labelImageDimensions; + KSqueezedTextLabel *labelNewFileName; + KSqueezedTextLabel *labelAlreadyDownloaded; + + KSqueezedTextLabel *labelPhotoMake; + KSqueezedTextLabel *labelPhotoModel; + KSqueezedTextLabel *labelPhotoDateTime; + KSqueezedTextLabel *labelPhotoAperture; + KSqueezedTextLabel *labelPhotoFocalLength; + KSqueezedTextLabel *labelPhotoExposureTime; + KSqueezedTextLabel *labelPhotoSensitivity; + KSqueezedTextLabel *labelPhotoExposureMode; + KSqueezedTextLabel *labelPhotoFlash; + KSqueezedTextLabel *labelPhotoWhiteBalance; +}; + +CameraItemPropertiesTab::CameraItemPropertiesTab(TQWidget* parent, bool navBar) + : NavigateBarTab(parent) +{ + d = new CameraItemPropertiesTabPriv; + + setupNavigateBar(navBar); + + TQScrollView *sv = new TQScrollView(this); + sv->viewport()->setBackgroundMode(TQt::PaletteBackground); + sv->setResizePolicy(TQScrollView::AutoOneFit); + sv->setFrameStyle(TQFrame::NoFrame); + + d->settingsArea = new TQFrame(sv->viewport()); + d->settingsArea->setFrameStyle( TQFrame::StyledPanel | TQFrame::Sunken ); + d->settingsArea->setLineWidth( style().pixelMetric(TQStyle::PM_DefaultFrameWidth, this) ); + + sv->addChild(d->settingsArea); + m_navigateBarLayout->addWidget(sv); + + // -------------------------------------------------- + + TQGridLayout *settingsLayout = new TQGridLayout(d->settingsArea, 27, 1, KDialog::spacingHint(), 0); + + d->title = new TQLabel(i18n("Camera File Properties"), d->settingsArea); + d->file = new TQLabel(i18n("File:"), d->settingsArea); + d->folder = new TQLabel(i18n("Folder:"), d->settingsArea); + d->date = new TQLabel(i18n("Date:"), d->settingsArea); + d->size = new TQLabel(i18n("Size:"), d->settingsArea); + d->isReadable = new TQLabel(i18n("Readable:"), d->settingsArea); + d->isWritable = new TQLabel(i18n("Writable:"), d->settingsArea); + d->mime = new TQLabel(i18n("Type:"), d->settingsArea); + d->dimensions = new TQLabel(i18n("Dimensions:"), d->settingsArea); + d->newFileName = new TQLabel(i18n("New Name:"), d->settingsArea); + d->downloaded = new TQLabel(i18n("Downloaded:"), d->settingsArea); + + KSeparator *line = new KSeparator(TQt::Horizontal, d->settingsArea); + d->title2 = new TQLabel(i18n("Photograph Properties"), d->settingsArea); + d->make = new TQLabel(i18n("Make:"), d->settingsArea); + d->model = new TQLabel(i18n("Model:"), d->settingsArea); + d->photoDate = new TQLabel(i18n("Created:"), d->settingsArea); + d->aperture = new TQLabel(i18n("Aperture:"), d->settingsArea); + d->focalLength = new TQLabel(i18n("Focal:"), d->settingsArea); + d->exposureTime = new TQLabel(i18n("Exposure:"), d->settingsArea); + d->sensitivity = new TQLabel(i18n("Sensitivity:"), d->settingsArea); + d->exposureMode = new TQLabel(i18n("Mode/Program:"), d->settingsArea); + d->flash = new TQLabel(i18n("Flash:"), d->settingsArea); + d->whiteBalance = new TQLabel(i18n("White balance:"), d->settingsArea); + + d->labelFile = new KSqueezedTextLabel(0, d->settingsArea); + d->labelFolder = new KSqueezedTextLabel(0, d->settingsArea); + d->labelFileDate = new KSqueezedTextLabel(0, d->settingsArea); + d->labelFileSize = new KSqueezedTextLabel(0, d->settingsArea); + d->labelFileIsReadable = new KSqueezedTextLabel(0, d->settingsArea); + d->labelFileIsWritable = new KSqueezedTextLabel(0, d->settingsArea); + d->labelImageMime = new KSqueezedTextLabel(0, d->settingsArea); + d->labelImageDimensions = new KSqueezedTextLabel(0, d->settingsArea); + d->labelNewFileName = new KSqueezedTextLabel(0, d->settingsArea); + d->labelAlreadyDownloaded = new KSqueezedTextLabel(0, d->settingsArea); + + d->labelPhotoMake = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoModel = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoDateTime = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoAperture = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoFocalLength = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoExposureTime = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoSensitivity = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoExposureMode = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoFlash = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoWhiteBalance = new KSqueezedTextLabel(0, d->settingsArea); + + int hgt = fontMetrics().height()-2; + d->title->setAlignment(TQt::AlignCenter); + d->file->setMaximumHeight(hgt); + d->folder->setMaximumHeight(hgt); + d->date->setMaximumHeight(hgt); + d->size->setMaximumHeight(hgt); + d->isReadable->setMaximumHeight(hgt); + d->isWritable->setMaximumHeight(hgt); + d->mime->setMaximumHeight(hgt); + d->dimensions->setMaximumHeight(hgt); + d->newFileName->setMaximumHeight(hgt); + d->downloaded->setMaximumHeight(hgt); + d->labelFile->setMaximumHeight(hgt); + d->labelFolder->setMaximumHeight(hgt); + d->labelFileDate->setMaximumHeight(hgt); + d->labelFileSize->setMaximumHeight(hgt); + d->labelFileIsReadable->setMaximumHeight(hgt); + d->labelFileIsWritable->setMaximumHeight(hgt); + d->labelImageMime->setMaximumHeight(hgt); + d->labelImageDimensions->setMaximumHeight(hgt); + d->labelNewFileName->setMaximumHeight(hgt); + d->labelAlreadyDownloaded->setMaximumHeight(hgt); + + d->title2->setAlignment(TQt::AlignCenter); + d->make->setMaximumHeight(hgt); + d->model->setMaximumHeight(hgt); + d->photoDate->setMaximumHeight(hgt); + d->aperture->setMaximumHeight(hgt); + d->focalLength->setMaximumHeight(hgt); + d->exposureTime->setMaximumHeight(hgt); + d->sensitivity->setMaximumHeight(hgt); + d->exposureMode->setMaximumHeight(hgt); + d->flash->setMaximumHeight(hgt); + d->whiteBalance->setMaximumHeight(hgt); + d->labelPhotoMake->setMaximumHeight(hgt); + d->labelPhotoModel->setMaximumHeight(hgt); + d->labelPhotoDateTime->setMaximumHeight(hgt); + d->labelPhotoAperture->setMaximumHeight(hgt); + d->labelPhotoFocalLength->setMaximumHeight(hgt); + d->labelPhotoExposureTime->setMaximumHeight(hgt); + d->labelPhotoSensitivity->setMaximumHeight(hgt); + d->labelPhotoExposureMode->setMaximumHeight(hgt); + d->labelPhotoFlash->setMaximumHeight(hgt); + d->labelPhotoWhiteBalance->setMaximumHeight(hgt); + + // -------------------------------------------------- + + settingsLayout->addMultiCellWidget(d->title, 0, 0, 0, 1); + settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(), + TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 1, 1, 0, 1); + settingsLayout->addMultiCellWidget(d->file, 2, 2, 0, 0); + settingsLayout->addMultiCellWidget(d->labelFile, 2, 2, 1, 1); + settingsLayout->addMultiCellWidget(d->folder, 3, 3, 0, 0); + settingsLayout->addMultiCellWidget(d->labelFolder, 3, 3, 1, 1); + settingsLayout->addMultiCellWidget(d->date, 4, 4, 0, 0); + settingsLayout->addMultiCellWidget(d->labelFileDate, 4, 4, 1, 1); + settingsLayout->addMultiCellWidget(d->size, 5, 5, 0, 0); + settingsLayout->addMultiCellWidget(d->labelFileSize, 5, 5, 1, 1); + settingsLayout->addMultiCellWidget(d->isReadable, 6, 6, 0, 0); + settingsLayout->addMultiCellWidget(d->labelFileIsReadable, 6, 6, 1, 1); + settingsLayout->addMultiCellWidget(d->isWritable, 7, 7, 0, 0); + settingsLayout->addMultiCellWidget(d->labelFileIsWritable, 7, 7, 1, 1); + settingsLayout->addMultiCellWidget(d->mime, 8, 8, 0, 0); + settingsLayout->addMultiCellWidget(d->labelImageMime, 8, 8, 1, 1); + settingsLayout->addMultiCellWidget(d->dimensions, 9, 9, 0, 0); + settingsLayout->addMultiCellWidget(d->labelImageDimensions, 9, 9, 1, 1); + settingsLayout->addMultiCellWidget(d->newFileName, 10, 10, 0, 0); + settingsLayout->addMultiCellWidget(d->labelNewFileName, 10, 10, 1, 1); + settingsLayout->addMultiCellWidget(d->downloaded, 11, 11, 0, 0); + settingsLayout->addMultiCellWidget(d->labelAlreadyDownloaded, 11, 11, 1, 1); + + settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(), + TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 12, 12, 0, 1); + settingsLayout->addMultiCellWidget(line, 13, 13, 0, 1); + settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(), + TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 14, 14, 0, 1); + + settingsLayout->addMultiCellWidget(d->title2, 15, 15, 0, 1); + settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(), + TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 16, 16, 0, 1); + settingsLayout->addMultiCellWidget(d->make, 17, 17, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoMake, 17, 17, 1, 1); + settingsLayout->addMultiCellWidget(d->model, 18, 18, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoModel, 18, 18, 1, 1); + settingsLayout->addMultiCellWidget(d->photoDate, 19, 19, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoDateTime, 19, 19, 1, 1); + settingsLayout->addMultiCellWidget(d->aperture, 20, 20, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoAperture, 20, 20, 1, 1); + settingsLayout->addMultiCellWidget(d->focalLength, 21, 21, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoFocalLength, 21, 21, 1, 1); + settingsLayout->addMultiCellWidget(d->exposureTime, 22, 22, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoExposureTime, 22, 22, 1, 1); + settingsLayout->addMultiCellWidget(d->sensitivity, 23, 23, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoSensitivity, 23, 23, 1, 1); + settingsLayout->addMultiCellWidget(d->exposureMode, 24, 24, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoExposureMode, 24, 24, 1, 1); + settingsLayout->addMultiCellWidget(d->flash, 25, 25, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoFlash, 25, 25, 1, 1); + settingsLayout->addMultiCellWidget(d->whiteBalance, 26, 26, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoWhiteBalance, 26, 26, 1, 1); + settingsLayout->setRowStretch(27, 10); + settingsLayout->setColStretch(1, 10); +} + +CameraItemPropertiesTab::~CameraItemPropertiesTab() +{ + delete d; +} + +void CameraItemPropertiesTab::setCurrentItem(const GPItemInfo* itemInfo, + const TQString &newFileName, const TQByteArray& exifData, + const KURL ¤tURL) +{ + if (!itemInfo) + { + d->labelFile->setText(TQString()); + d->labelFolder->setText(TQString()); + d->labelFileIsReadable->setText(TQString()); + d->labelFileIsWritable->setText(TQString()); + d->labelFileDate->setText(TQString()); + d->labelFileSize->setText(TQString()); + d->labelImageMime->setText(TQString()); + d->labelImageDimensions->setText(TQString()); + d->labelNewFileName->setText(TQString()); + d->labelAlreadyDownloaded->setText(TQString()); + + d->labelPhotoMake->setText(TQString()); + d->labelPhotoModel->setText(TQString()); + d->labelPhotoDateTime->setText(TQString()); + d->labelPhotoAperture->setText(TQString()); + d->labelPhotoFocalLength->setText(TQString()); + d->labelPhotoExposureTime->setText(TQString()); + d->labelPhotoSensitivity->setText(TQString()); + d->labelPhotoExposureMode->setText(TQString()); + d->labelPhotoFlash->setText(TQString()); + d->labelPhotoWhiteBalance->setText(TQString()); + + setEnabled(false); + return; + } + + setEnabled(true); + + TQString str; + TQString unknown(i18n("unknown")); + + // -- Camera file system information ------------------------------------------ + + d->labelFile->setText(itemInfo->name); + d->labelFolder->setText(itemInfo->folder); + + if (itemInfo->readPermissions < 0) + str = unknown; + else if (itemInfo->readPermissions == 0) + str = i18n("No"); + else + str = i18n("Yes"); + + d->labelFileIsReadable->setText(str); + + if (itemInfo->writePermissions < 0) + str = unknown; + else if (itemInfo->writePermissions == 0) + str = i18n("No"); + else + str = i18n("Yes"); + + d->labelFileIsWritable->setText(str); + + TQDateTime date; + date.setTime_t(itemInfo->mtime); + d->labelFileDate->setText(TDEGlobal::locale()->formatDateTime(date, true, true)); + + str = i18n("%1 (%2)").arg(TDEIO::convertSize(itemInfo->size)) + .arg(TDEGlobal::locale()->formatNumber(itemInfo->size, 0)); + d->labelFileSize->setText(str); + + // -- Image Properties -------------------------------------------------- + + d->labelImageMime->setText( (itemInfo->mime == TQString("image/x-raw")) ? + i18n("RAW Image") : KMimeType::mimeType(itemInfo->mime)->comment() ); + + TQString mpixels; + TQSize dims; + if (itemInfo->width == -1 && itemInfo->height == -1 && !currentURL.isEmpty()) + { + // delayed loading to list faster from UMSCamera + if (itemInfo->mime == TQString("image/x-raw")) + { + DMetadata metaData(currentURL.path()); + dims = metaData.getImageDimensions(); + } + else + { + KFileMetaInfo meta(currentURL.path()); + if (meta.isValid()) + { + if (meta.containsGroup("Jpeg EXIF Data")) + dims = meta.group("Jpeg EXIF Data").item("Dimensions").value().toSize(); + else if (meta.containsGroup("General")) + dims = meta.group("General").item("Dimensions").value().toSize(); + else if (meta.containsGroup("Technical")) + dims = meta.group("Technical").item("Dimensions").value().toSize(); + } + } + } + else + { + // if available (GPCamera), take dimensions directly from itemInfo + dims = TQSize(itemInfo->width, itemInfo->height); + } + mpixels.setNum(dims.width()*dims.height()/1000000.0, 'f', 2); + str = (!dims.isValid()) ? unknown : i18n("%1x%2 (%3Mpx)") + .arg(dims.width()).arg(dims.height()).arg(mpixels); + d->labelImageDimensions->setText(str); + + // -- Download information ------------------------------------------ + + d->labelNewFileName->setText(newFileName.isEmpty() ? i18n("unchanged") : newFileName); + + if (itemInfo->downloaded == GPItemInfo::DownloadUnknow) + str = unknown; + else if (itemInfo->downloaded == GPItemInfo::DownloadedYes) + str = i18n("Yes"); + else + str = i18n("No"); + + d->labelAlreadyDownloaded->setText(str); + + // -- Photograph information ------------------------------------------ + // NOTA: If something is changed here, please updated albumfiletip section too. + + TQString unavailable(i18n("unavailable")); + DMetadata metaData; + metaData.setExif(exifData); + PhotoInfoContainer photoInfo = metaData.getPhotographInformations(); + + if (photoInfo.isEmpty()) + { + d->title2->hide(); + d->make->hide(); + d->model->hide(); + d->photoDate->hide(); + d->aperture->hide(); + d->focalLength->hide(); + d->exposureTime->hide(); + d->sensitivity->hide(); + d->exposureMode->hide(); + d->flash->hide(); + d->whiteBalance->hide(); + d->labelPhotoMake->hide(); + d->labelPhotoModel->hide(); + d->labelPhotoDateTime->hide(); + d->labelPhotoAperture->hide(); + d->labelPhotoFocalLength->hide(); + d->labelPhotoExposureTime->hide(); + d->labelPhotoSensitivity->hide(); + d->labelPhotoExposureMode->hide(); + d->labelPhotoFlash->hide(); + d->labelPhotoWhiteBalance->hide(); + } + else + { + d->title2->show(); + d->make->show(); + d->model->show(); + d->photoDate->show(); + d->aperture->show(); + d->focalLength->show(); + d->exposureTime->show(); + d->sensitivity->show(); + d->exposureMode->show(); + d->flash->show(); + d->whiteBalance->show(); + d->labelPhotoMake->show(); + d->labelPhotoModel->show(); + d->labelPhotoDateTime->show(); + d->labelPhotoAperture->show(); + d->labelPhotoFocalLength->show(); + d->labelPhotoExposureTime->show(); + d->labelPhotoSensitivity->show(); + d->labelPhotoExposureMode->show(); + d->labelPhotoFlash->show(); + d->labelPhotoWhiteBalance->show(); + } + + d->labelPhotoMake->setText(photoInfo.make.isEmpty() ? unavailable : photoInfo.make); + d->labelPhotoModel->setText(photoInfo.model.isEmpty() ? unavailable : photoInfo.model); + + if (photoInfo.dateTime.isValid()) + { + str = TDEGlobal::locale()->formatDateTime(photoInfo.dateTime, true, true); + d->labelPhotoDateTime->setText(str); + } + else + d->labelPhotoDateTime->setText(unavailable); + + d->labelPhotoAperture->setText(photoInfo.aperture.isEmpty() ? unavailable : photoInfo.aperture); + + if (photoInfo.focalLength35mm.isEmpty()) + d->labelPhotoFocalLength->setText(photoInfo.focalLength.isEmpty() ? unavailable : photoInfo.focalLength); + else + { + str = i18n("%1 (35mm: %2)").arg(photoInfo.focalLength).arg(photoInfo.focalLength35mm); + d->labelPhotoFocalLength->setText(str); + } + + d->labelPhotoExposureTime->setText(photoInfo.exposureTime.isEmpty() ? unavailable : photoInfo.exposureTime); + d->labelPhotoSensitivity->setText(photoInfo.sensitivity.isEmpty() ? unavailable : i18n("%1 ISO").arg(photoInfo.sensitivity)); + + if (photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty()) + d->labelPhotoExposureMode->setText(unavailable); + else if (!photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty()) + d->labelPhotoExposureMode->setText(photoInfo.exposureMode); + else if (photoInfo.exposureMode.isEmpty() && !photoInfo.exposureProgram.isEmpty()) + d->labelPhotoExposureMode->setText(photoInfo.exposureProgram); + else + { + str = TQString("%1 / %2").arg(photoInfo.exposureMode).arg(photoInfo.exposureProgram); + d->labelPhotoExposureMode->setText(str); + } + + d->labelPhotoFlash->setText(photoInfo.flash.isEmpty() ? unavailable : photoInfo.flash); + d->labelPhotoWhiteBalance->setText(photoInfo.whiteBalance.isEmpty() ? unavailable : photoInfo.whiteBalance); +} + +} // NameSpace Digikam diff --git a/src/libs/imageproperties/cameraitempropertiestab.h b/src/libs/imageproperties/cameraitempropertiestab.h new file mode 100644 index 00000000..badf4a70 --- /dev/null +++ b/src/libs/imageproperties/cameraitempropertiestab.h @@ -0,0 +1,69 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-02-08 + * Description : A tab to display camera item information + * + * Copyright (C) 2006-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef CAMERAITEMPROPERTIESTAB_H +#define CAMERAITEMPROPERTIESTAB_H + +// TQt includes. + +#include +#include + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" +#include "navigatebartab.h" + +namespace Digikam +{ + +class GPItemInfo; +class CameraItemPropertiesTabPriv; + +class DIGIKAM_EXPORT CameraItemPropertiesTab : public NavigateBarTab +{ + TQ_OBJECT + + +public: + + CameraItemPropertiesTab(TQWidget* parent, bool navBar=true); + ~CameraItemPropertiesTab(); + + void setCurrentItem(const GPItemInfo* itemInfo=0, + const TQString &newFileName=TQString(), + const TQByteArray& exifData=TQByteArray(), + const KURL ¤tURL = KURL()); + +private: + + CameraItemPropertiesTabPriv* d; +}; + +} // NameSpace Digikam + +#endif /* CAMERAITEMPROPERTIESTAB_H */ diff --git a/src/libs/imageproperties/imagedescedittab.cpp b/src/libs/imageproperties/imagedescedittab.cpp new file mode 100644 index 00000000..f79b1d7c --- /dev/null +++ b/src/libs/imageproperties/imagedescedittab.cpp @@ -0,0 +1,1763 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2003-03-09 + * Description : Captions, Tags, and Rating properties editor + * + * Copyright (C) 2003-2005 by Renchi Raju + * Copyright (C) 2003-2009 by Gilles Caulier + * Copyright (C) 2006-2009 by Marcel Wiesweg + * Copyright (C) 2009 by Andi Clemens + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dmetadata.h" +#include "kdatetimeedit.h" +#include "albumiconitem.h" +#include "albumdb.h" +#include "album.h" +#include "albumsettings.h" +#include "albumlister.h" +#include "albumthumbnailloader.h" +#include "tageditdlg.h" +#include "navigatebarwidget.h" +#include "ratingwidget.h" +#include "talbumlistview.h" +#include "tagfilterview.h" +#include "imageinfo.h" +#include "imageattributeswatch.h" +#include "metadatahub.h" +#include "statusprogressbar.h" +#include "searchtextbar.h" +#include "imagedescedittab.h" +#include "imagedescedittab.moc" + +namespace Digikam +{ + +class ImageDescEditTabPriv +{ + +public: + + ImageDescEditTabPriv() + { + modified = false; + ignoreImageAttributesWatch = false; + recentTagsBtn = 0; + commentsEdit = 0; + tagsSearchBar = 0; + dateTimeEdit = 0; + tagsView = 0; + ratingWidget = 0; + ABCMenu = 0; + assignedTagsBtn = 0; + applyBtn = 0; + revertBtn = 0; + newTagEdit = 0; + toggleAutoTags = TagFilterView::NoToggleAuto; + } + + bool modified; + bool ignoreImageAttributesWatch; + + TQToolButton *recentTagsBtn; + TQToolButton *assignedTagsBtn; + TQToolButton *revertBtn; + + TQPopupMenu *ABCMenu; + + TQPushButton *applyBtn; + + TQPushButton *moreButton; + TQPopupMenu *moreMenu; + + KTextEdit *commentsEdit; + + KDateTimeEdit *dateTimeEdit; + + SearchTextBar *tagsSearchBar; + SearchTextBar *newTagEdit; + + TQPtrList currInfos; + + TAlbumListView *tagsView; + + RatingWidget *ratingWidget; + + TagFilterView::ToggleAutoTags toggleAutoTags; + + MetadataHub hub; +}; + +ImageDescEditTab::ImageDescEditTab(TQWidget *parent, bool navBar) + : NavigateBarTab(parent) +{ + d = new ImageDescEditTabPriv; + + setupNavigateBar(navBar); + + TQScrollView *sv = new TQScrollView(this); + sv->viewport()->setBackgroundMode(TQt::PaletteBackground); + sv->setResizePolicy(TQScrollView::AutoOneFit); + sv->setFrameStyle(TQFrame::NoFrame); + + TQWidget *settingsArea = new TQWidget(sv->viewport()); + sv->addChild(settingsArea); + m_navigateBarLayout->addWidget(sv); + + TQGridLayout *settingsLayout = new TQGridLayout(settingsArea, 6, 1, + KDialog::spacingHint(), KDialog::spacingHint()); + + // Captions/Date/Rating view ----------------------------------- + + TQVBox *commentsBox = new TQVBox(settingsArea); + new TQLabel(i18n("Caption:"), commentsBox); + d->commentsEdit = new KTextEdit(commentsBox); + d->commentsEdit->setTextFormat(TQTextEdit::PlainText); + d->commentsEdit->setCheckSpellingEnabled(true); + d->commentsEdit->setFixedHeight(100); + + TQHBox *dateBox = new TQHBox(settingsArea); + new TQLabel(i18n("Date:"), dateBox); + d->dateTimeEdit = new KDateTimeEdit(dateBox, "datepicker"); + + TQHBox *ratingBox = new TQHBox(settingsArea); + new TQLabel(i18n("Rating:"), ratingBox); + d->ratingWidget = new RatingWidget(ratingBox); + + // Tags view --------------------------------------------------- + + d->newTagEdit = new SearchTextBar(settingsArea, "ImageDescEditTabNewTagEdit", i18n("Enter new tag here...")); + TQWhatsThis::add(d->newTagEdit, i18n("Enter here the text used to create new tags. " + "'/' can be used here to create a hierarchy of tags. " + "',' can be used here to create more than one hierarchy at the same time.")); + + d->tagsView = new TAlbumListView(settingsArea); + + TQHBox *tagsSearch = new TQHBox(settingsArea); + tagsSearch->setSpacing(KDialog::spacingHint()); + + d->tagsSearchBar = new SearchTextBar(tagsSearch, "ImageDescEditTabTagsSearchBar"); + + d->assignedTagsBtn = new TQToolButton(tagsSearch); + TQToolTip::add(d->assignedTagsBtn, i18n("Tags already assigned")); + d->assignedTagsBtn->setIconSet(kapp->iconLoader()->loadIcon("tag-assigned", + TDEIcon::NoGroup, TDEIcon::SizeSmall, + TDEIcon::DefaultState, 0, true)); + d->assignedTagsBtn->setToggleButton(true); + + d->recentTagsBtn = new TQToolButton(tagsSearch); + TQPopupMenu *popupMenu = new TQPopupMenu(d->recentTagsBtn); + TQToolTip::add(d->recentTagsBtn, i18n("Recent Tags")); + d->recentTagsBtn->setIconSet(kapp->iconLoader()->loadIcon("tag-recents", + TDEIcon::NoGroup, TDEIcon::SizeSmall, + TDEIcon::DefaultState, 0, true)); + d->recentTagsBtn->setUsesBigPixmap(false); + d->recentTagsBtn->setPopup(popupMenu); + d->recentTagsBtn->setPopupDelay(1); + + // Buttons ----------------------------------------- + + TQHBox *buttonsBox = new TQHBox(settingsArea); + buttonsBox->setSpacing(KDialog::spacingHint()); + + d->revertBtn = new TQToolButton(buttonsBox); + d->revertBtn->setIconSet(SmallIcon("reload_page")); + TQToolTip::add(d->revertBtn, i18n("Revert all changes")); + d->revertBtn->setEnabled(false); + + d->applyBtn = new TQPushButton(i18n("Apply"), buttonsBox); + d->applyBtn->setIconSet(SmallIcon("button_ok")); + d->applyBtn->setEnabled(false); + TQToolTip::add(d->applyBtn, i18n("Apply all changes to images")); + buttonsBox->setStretchFactor(d->applyBtn, 10); + + d->moreButton = new TQPushButton(i18n("More"), buttonsBox); + d->moreMenu = new TQPopupMenu(this); + d->moreButton->setPopup(d->moreMenu); + + // -------------------------------------------------- + + settingsLayout->addMultiCellWidget(commentsBox, 0, 0, 0, 1); + settingsLayout->addMultiCellWidget(dateBox, 1, 1, 0, 1); + settingsLayout->addMultiCellWidget(ratingBox, 2, 2, 0, 1); + settingsLayout->addMultiCellWidget(d->newTagEdit, 3, 3, 0, 1); + settingsLayout->addMultiCellWidget(d->tagsView, 4, 4, 0, 1); + settingsLayout->addMultiCellWidget(tagsSearch, 5, 5, 0, 1); + settingsLayout->addMultiCellWidget(buttonsBox, 6, 6, 0, 1); + settingsLayout->setRowStretch(4, 10); + + // -------------------------------------------------- + + connect(d->tagsView, TQ_SIGNAL(signalProgressBarMode(int, const TQString&)), + this, TQ_SIGNAL(signalProgressBarMode(int, const TQString&))); + + connect(d->tagsView, TQ_SIGNAL(signalProgressValue(int)), + this, TQ_SIGNAL(signalProgressValue(int))); + + connect(popupMenu, TQ_SIGNAL(activated(int)), + this, TQ_SLOT(slotRecentTagsMenuActivated(int))); + + connect(d->tagsView, TQ_SIGNAL(signalItemStateChanged(TAlbumCheckListItem *)), + this, TQ_SLOT(slotItemStateChanged(TAlbumCheckListItem *))); + + connect(d->commentsEdit, TQ_SIGNAL(textChanged()), + this, TQ_SLOT(slotCommentChanged())); + + connect(d->dateTimeEdit, TQ_SIGNAL(dateTimeChanged(const TQDateTime& )), + this, TQ_SLOT(slotDateTimeChanged(const TQDateTime&))); + + connect(d->ratingWidget, TQ_SIGNAL(signalRatingChanged(int)), + this, TQ_SLOT(slotRatingChanged(int))); + + connect(d->tagsView, TQ_SIGNAL(rightButtonClicked(TQListViewItem*, const TQPoint &, int)), + this, TQ_SLOT(slotRightButtonClicked(TQListViewItem*, const TQPoint&, int))); + + connect(d->tagsSearchBar, TQ_SIGNAL(signalTextChanged(const TQString&)), + this, TQ_SLOT(slotTagsSearchChanged(const TQString&))); + + connect(this, TQ_SIGNAL(signalTagFilterMatch(bool)), + d->tagsSearchBar, TQ_SLOT(slotSearchResult(bool))); + + connect(d->assignedTagsBtn, TQ_SIGNAL(toggled(bool)), + this, TQ_SLOT(slotAssignedTagsToggled(bool))); + + connect(d->newTagEdit->lineEdit(), TQ_SIGNAL(returnPressed(const TQString&)), + this, TQ_SLOT(slotCreateNewTag())); + + connect(d->applyBtn, TQ_SIGNAL(clicked()), + this, TQ_SLOT(slotApplyAllChanges())); + + connect(d->revertBtn, TQ_SIGNAL(clicked()), + this, TQ_SLOT(slotRevertAllChanges())); + + connect(d->moreMenu, TQ_SIGNAL(aboutToShow()), + this, TQ_SLOT(slotMoreMenu())); + + // Initialize --------------------------------------------- + + d->commentsEdit->installEventFilter(this); + d->dateTimeEdit->installEventFilter(this); + d->ratingWidget->installEventFilter(this); + d->tagsView->installEventFilter(this); + updateRecentTags(); + + // Connect to album manager ----------------------------- + + AlbumManager* man = AlbumManager::instance(); + + connect(man, TQ_SIGNAL(signalAlbumAdded(Album*)), + this, TQ_SLOT(slotAlbumAdded(Album*))); + + connect(man, TQ_SIGNAL(signalAlbumDeleted(Album*)), + this, TQ_SLOT(slotAlbumDeleted(Album*))); + + connect(man, TQ_SIGNAL(signalAlbumRenamed(Album*)), + this, TQ_SLOT(slotAlbumRenamed(Album*))); + + connect(man, TQ_SIGNAL(signalAlbumsCleared()), + this, TQ_SLOT(slotAlbumsCleared())); + + connect(man, TQ_SIGNAL(signalAlbumIconChanged(Album*)), + this, TQ_SLOT(slotAlbumIconChanged(Album*))); + + connect(man, TQ_SIGNAL(signalTAlbumMoved(TAlbum*, TAlbum*)), + this, TQ_SLOT(slotAlbumMoved(TAlbum*, TAlbum*))); + + // Connect to thumbnail loader ----------------------------- + + AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance(); + + connect(loader, TQ_SIGNAL(signalThumbnail(Album *, const TQPixmap&)), + this, TQ_SLOT(slotGotThumbnailFromIcon(Album *, const TQPixmap&))); + + connect(loader, TQ_SIGNAL(signalFailed(Album *)), + this, TQ_SLOT(slotThumbnailLost(Album *))); + + connect(loader, TQ_SIGNAL(signalReloadThumbnails()), + this, TQ_SLOT(slotReloadThumbnails())); + + // Connect to attribute watch ------------------------------ + + ImageAttributesWatch *watch = ImageAttributesWatch::instance(); + + connect(watch, TQ_SIGNAL(signalImageTagsChanged(TQ_LLONG)), + this, TQ_SLOT(slotImageTagsChanged(TQ_LLONG))); + + connect(watch, TQ_SIGNAL(signalImagesChanged(int)), + this, TQ_SLOT(slotImagesChanged(int))); + + connect(watch, TQ_SIGNAL(signalImageRatingChanged(TQ_LLONG)), + this, TQ_SLOT(slotImageRatingChanged(TQ_LLONG))); + + connect(watch, TQ_SIGNAL(signalImageDateChanged(TQ_LLONG)), + this, TQ_SLOT(slotImageDateChanged(TQ_LLONG))); + + connect(watch, TQ_SIGNAL(signalImageCaptionChanged(TQ_LLONG)), + this, TQ_SLOT(slotImageCaptionChanged(TQ_LLONG))); + + // -- read config --------------------------------------------------------- + + TDEConfig* config = kapp->config(); + config->setGroup("Tag List View"); + d->toggleAutoTags = (TagFilterView::ToggleAutoTags)(config->readNumEntry("Toggle Auto Tags", + TagFilterView::NoToggleAuto)); +} + +ImageDescEditTab::~ImageDescEditTab() +{ + slotChangingItems(); + + /* + AlbumList tList = AlbumManager::instance()->allTAlbums(); + for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it) + { + (*it)->removeExtraData(this); + } + */ + + TDEConfig* config = kapp->config(); + config->setGroup("Tag List View"); + config->writeEntry("Toggle Auto Tags", (int)(d->toggleAutoTags)); + config->sync(); + + delete d; +} + +bool ImageDescEditTab::singleSelection() const +{ + return (d->currInfos.count() == 1); +} + +void ImageDescEditTab::slotChangingItems() +{ + if (!d->modified) + return; + + if (d->currInfos.isEmpty()) + return; + + if (!AlbumSettings::instance()->getApplySidebarChangesDirectly()) + { + KDialogBase *dialog = new KDialogBase(i18n("Apply changes?"), + KDialogBase::Yes | KDialogBase::No, + KDialogBase::Yes, KDialogBase::No, + this, "applyChanges", + true, true, + KStdGuiItem::yes(), KStdGuiItem::discard()); + + int changedFields = 0; + if (d->hub.commentChanged()) + changedFields++; + if (d->hub.dateTimeChanged()) + changedFields++; + if (d->hub.ratingChanged()) + changedFields++; + if (d->hub.tagsChanged()) + changedFields++; + + TQString text; + if (changedFields == 1) + { + if (d->hub.commentChanged()) + text = i18n("

You have edited the comment of the image. ", + "

You have edited the comment of %n images. ", + d->currInfos.count()); + else if (d->hub.dateTimeChanged()) + text = i18n("

You have edited the date of the image. ", + "

You have edited the date of %n images. ", + d->currInfos.count()); + else if (d->hub.ratingChanged()) + text = i18n("

You have edited the rating of the image. ", + "

You have edited the rating of %n images. ", + d->currInfos.count()); + else if (d->hub.tagsChanged()) + text = i18n("

You have edited the tags of the image. ", + "

You have edited the tags of %n images. ", + d->currInfos.count()); + + text += i18n("Do you want to apply your changes?

"); + } + else + { + text = i18n("

You have edited the metadata of the image:

    ", + "

    You have edited the metadata of %n images:

      ", + d->currInfos.count()); + + if (d->hub.commentChanged()) + text += i18n("
    • comment
    • "); + if (d->hub.dateTimeChanged()) + text += i18n("
    • date
    • "); + if (d->hub.ratingChanged()) + text += i18n("
    • rating
    • "); + if (d->hub.tagsChanged()) + text += i18n("
    • tags
    • "); + + text += "

    "; + + text += i18n("Do you want to apply your changes?

    "); + } + + bool alwaysApply = false; + int returnCode = KMessageBox::createKMessageBox + (dialog, TQMessageBox::Information, + text, TQStringList(), + i18n("Always apply changes without confirmation"), + &alwaysApply, KMessageBox::Notify); + + if (alwaysApply) + AlbumSettings::instance()->setApplySidebarChangesDirectly(true); + + if (returnCode == KDialogBase::User1) + return; + // otherwise apply + } + + slotApplyAllChanges(); +} + +void ImageDescEditTab::slotApplyAllChanges() +{ + if (!d->modified) + return; + + if (d->currInfos.isEmpty()) + return; + + bool progressInfo = (d->currInfos.count() > 1); + emit signalProgressBarMode(StatusProgressBar::ProgressBarMode, + i18n("Applying changes to images. Please wait...")); + MetadataWriteSettings writeSettings = MetadataHub::defaultWriteSettings(); + + // debugging - use this to indicate reentry from event loop (kapp->processEvents) + // remove before final release + if (d->ignoreImageAttributesWatch) + { + DWarning() << "ImageDescEditTab::slotApplyAllChanges(): re-entering from event loop!" << endl; + } + + // we are now changing attributes ourselves + d->ignoreImageAttributesWatch = true; + AlbumLister::instance()->blockSignals(true); + AlbumManager::instance()->albumDB()->beginTransaction(); + int i=0; + for (ImageInfo *info = d->currInfos.first(); info; info = d->currInfos.next()) + { + // apply to database + d->hub.write(info); + // apply to file metadata + d->hub.write(info->filePath(), MetadataHub::FullWrite, writeSettings); + + emit signalProgressValue((int)((i++/(float)d->currInfos.count())*100.0)); + if (progressInfo) + kapp->processEvents(); + } + AlbumLister::instance()->blockSignals(false); + AlbumManager::instance()->albumDB()->commitTransaction(); + + d->ignoreImageAttributesWatch = false; + + emit signalProgressBarMode(StatusProgressBar::TextMode, TQString()); + + d->modified = false; + d->hub.resetChanged(); + d->applyBtn->setEnabled(false); + d->revertBtn->setEnabled(false); + + updateRecentTags(); + updateTagsView(); +} + +void ImageDescEditTab::slotRevertAllChanges() +{ + if (!d->modified) + return; + + if (d->currInfos.isEmpty()) + return; + + setInfos(d->currInfos); +} + +void ImageDescEditTab::setItem(ImageInfo *info) +{ + slotChangingItems(); + TQPtrList list; + if (info) + list.append(info); + setInfos(list); +} + +void ImageDescEditTab::setItems(TQPtrList infos) +{ + slotChangingItems(); + setInfos(infos); +} + +void ImageDescEditTab::setInfos(TQPtrList infos) +{ + if (infos.isEmpty()) + { + d->hub = MetadataHub(); + d->commentsEdit->blockSignals(true); + d->commentsEdit->clear(); + d->commentsEdit->blockSignals(false); + d->currInfos.clear(); + setEnabled(false); + return; + } + + setEnabled(true); + d->currInfos = infos; + d->modified = false; + d->hub = MetadataHub(); + d->applyBtn->setEnabled(false); + d->revertBtn->setEnabled(false); + + for (ImageInfo *info = d->currInfos.first(); info; info = d->currInfos.next()) + { + d->hub.load(info); + } + + updateComments(); + updateRating(); + updateDate(); + updateTagsView(); +} + +void ImageDescEditTab::slotReadFromFileMetadataToDatabase() +{ + emit signalProgressBarMode(StatusProgressBar::ProgressBarMode, + i18n("Reading metadata from files. Please wait...")); + + d->ignoreImageAttributesWatch = true; + AlbumManager::instance()->albumDB()->beginTransaction(); + int i=0; + for (ImageInfo *info = d->currInfos.first(); info; info = d->currInfos.next()) + { + // A batch operation: a hub for each single file, not the common hub + MetadataHub fileHub(MetadataHub::NewTagsImport); + // read in from DMetadata + fileHub.load(info->filePath()); + // write out to database + fileHub.write(info); + + emit signalProgressValue((int)((i++/(float)d->currInfos.count())*100.0)); + kapp->processEvents(); + } + AlbumManager::instance()->albumDB()->commitTransaction(); + d->ignoreImageAttributesWatch = false; + + emit signalProgressBarMode(StatusProgressBar::TextMode, TQString()); + + // reload everything + setInfos(d->currInfos); +} + +void ImageDescEditTab::slotWriteToFileMetadataFromDatabase() +{ + emit signalProgressBarMode(StatusProgressBar::ProgressBarMode, + i18n("Writing metadata to files. Please wait...")); + MetadataWriteSettings writeSettings = MetadataHub::defaultWriteSettings(); + + int i=0; + for (ImageInfo *info = d->currInfos.first(); info; info = d->currInfos.next()) + { + MetadataHub fileHub; + // read in from database + fileHub.load(info); + // write out to file DMetadata + fileHub.write(info->filePath()); + + emit signalProgressValue((int)((i++/(float)d->currInfos.count())*100.0)); + kapp->processEvents(); + } + + emit signalProgressBarMode(StatusProgressBar::TextMode, TQString()); +} + +bool ImageDescEditTab::eventFilter(TQObject *, TQEvent *e) +{ + if ( e->type() == TQEvent::KeyPress ) + { + TQKeyEvent *k = (TQKeyEvent *)e; + if (k->state() == TQt::ControlButton && + (k->key() == TQt::Key_Enter || k->key() == TQt::Key_Return)) + { + emit signalNextItem(); + return true; + } + else if (k->state() == TQt::ShiftButton && + (k->key() == TQt::Key_Enter || k->key() == TQt::Key_Return)) + { + emit signalPrevItem(); + return true; + } + + return false; + } + + return false; +} + +void ImageDescEditTab::populateTags() +{ + d->tagsView->clear(); + + AlbumList tList = AlbumManager::instance()->allTAlbums(); + for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it) + { + TAlbum *tag = (TAlbum*)(*it); + slotAlbumAdded(tag); + } + + d->tagsView->loadViewState(); +} + +void ImageDescEditTab::slotItemStateChanged(TAlbumCheckListItem *item) +{ + TagFilterView::ToggleAutoTags oldAutoTags = d->toggleAutoTags; + + switch(d->toggleAutoTags) + { + case TagFilterView::Children: + d->toggleAutoTags = TagFilterView::NoToggleAuto; + toggleChildTags(item->album(), item->isOn()); + d->toggleAutoTags = oldAutoTags; + break; + case TagFilterView::Parents: + d->toggleAutoTags = TagFilterView::NoToggleAuto; + toggleParentTags(item->album(), item->isOn()); + d->toggleAutoTags = oldAutoTags; + break; + case TagFilterView::ChildrenAndParents: + d->toggleAutoTags = TagFilterView::NoToggleAuto; + toggleChildTags(item->album(), item->isOn()); + toggleParentTags(item->album(), item->isOn()); + d->toggleAutoTags = oldAutoTags; + break; + default: + break; + } + + d->hub.setTag(item->album(), item->isOn()); + + d->tagsView->blockSignals(true); + item->setStatus(d->hub.tagStatus(item->album())); + d->tagsView->blockSignals(false); + + slotModified(); +} + +void ImageDescEditTab::slotCommentChanged() +{ + // we cannot trust that the text actually changed + // (there are bogus signals caused by spell checking, see bug 141663) + // so we have to check before marking the metadata as modified + if (d->hub.comment() == d->commentsEdit->text()) + return; + + d->hub.setComment(d->commentsEdit->text()); + setMetadataWidgetStatus(d->hub.commentStatus(), d->commentsEdit); + slotModified(); +} + +void ImageDescEditTab::slotDateTimeChanged(const TQDateTime& dateTime) +{ + d->hub.setDateTime(dateTime); + setMetadataWidgetStatus(d->hub.dateTimeStatus(), d->dateTimeEdit); + slotModified(); +} + +void ImageDescEditTab::slotRatingChanged(int rating) +{ + d->hub.setRating(rating); + // no handling for MetadataDisjoint needed for rating, + // we set it to 0 when disjoint, see below + slotModified(); +} + +void ImageDescEditTab::slotModified() +{ + d->modified = true; + d->applyBtn->setEnabled(true); + d->revertBtn->setEnabled(true); +} + +void ImageDescEditTab::assignRating(int rating) +{ + d->ratingWidget->setRating(rating); +} + +void ImageDescEditTab::updateTagsView() +{ + d->tagsView->blockSignals(true); + + TQListViewItemIterator it( d->tagsView); + while (it.current()) + { + TAlbumCheckListItem* tItem = dynamic_cast(it.current()); + if (tItem) + tItem->setStatus(d->hub.tagStatus(tItem->album())); + ++it; + } + + // The condition is a temporary fix not to destroy name filtering on image change. + // See comments in these methods. + if (d->assignedTagsBtn->isOn()) + slotAssignedTagsToggled(d->assignedTagsBtn->isOn()); + + d->tagsView->blockSignals(false); +} + +void ImageDescEditTab::updateComments() +{ + d->commentsEdit->blockSignals(true); + d->commentsEdit->setText(d->hub.comment()); + setMetadataWidgetStatus(d->hub.commentStatus(), d->commentsEdit); + d->commentsEdit->blockSignals(false); +} + +void ImageDescEditTab::updateRating() +{ + d->ratingWidget->blockSignals(true); + if (d->hub.ratingStatus() == MetadataHub::MetadataDisjoint) + d->ratingWidget->setRating(0); + else + d->ratingWidget->setRating(d->hub.rating()); + d->ratingWidget->blockSignals(false); +} + +void ImageDescEditTab::updateDate() +{ + d->dateTimeEdit->blockSignals(true); + d->dateTimeEdit->setDateTime(d->hub.dateTime()); + setMetadataWidgetStatus(d->hub.dateTimeStatus(), d->dateTimeEdit); + d->dateTimeEdit->blockSignals(false); +} + +void ImageDescEditTab::setMetadataWidgetStatus(int status, TQWidget *widget) +{ + if (status == MetadataHub::MetadataDisjoint) + { + // For text widgets: Set text color to color of disabled text + TQPalette palette = widget->palette(); + palette.setColor(TQColorGroup::Text, palette.color(TQPalette::Disabled, TQColorGroup::Text)); + widget->setPalette(palette); + } + else + { + widget->unsetPalette(); + } +} + +void ImageDescEditTab::slotRightButtonClicked(TQListViewItem *item, const TQPoint &, int ) +{ + TAlbum *album; + + if (!item) + { + album = AlbumManager::instance()->findTAlbum(0); + } + else + { + TAlbumCheckListItem* viewItem = dynamic_cast(item); + + if(!viewItem) + album = AlbumManager::instance()->findTAlbum(0); + else + album = viewItem->album(); + } + + if(!album) + return; + + d->ABCMenu = new TQPopupMenu; + + connect(d->ABCMenu, TQ_SIGNAL( aboutToShow() ), + this, TQ_SLOT( slotABCContextMenu() )); + + TDEPopupMenu popmenu(this); + popmenu.insertTitle(SmallIcon("digikam"), i18n("Tags")); + popmenu.insertItem(SmallIcon("tag-new"), i18n("New Tag..."), 10); + popmenu.insertItem(SmallIcon("tag-addressbook"), i18n("Create Tag From AddressBook"), d->ABCMenu); + + if (!album->isRoot()) + { + popmenu.insertItem(SmallIcon("tag-properties"), i18n("Edit Tag Properties..."), 11); + popmenu.insertItem(SmallIcon("tag-reset"), i18n("Reset Tag Icon"), 13); + popmenu.insertSeparator(-1); + popmenu.insertItem(SmallIcon("tag-delete"), i18n("Delete Tag"), 12); + } + + popmenu.insertSeparator(-1); + + TQPopupMenu selectTagsMenu; + selectTagsMenu.insertItem(i18n("All Tags"), 14); + if (!album->isRoot()) + { + selectTagsMenu.insertSeparator(-1); + selectTagsMenu.insertItem(i18n("Children"), 17); + selectTagsMenu.insertItem(i18n("Parents"), 19); + } + popmenu.insertItem(i18n("Select"), &selectTagsMenu); + + TQPopupMenu deselectTagsMenu; + deselectTagsMenu.insertItem(i18n("All Tags"), 15); + if (!album->isRoot()) + { + deselectTagsMenu.insertSeparator(-1); + deselectTagsMenu.insertItem(i18n("Children"), 18); + deselectTagsMenu.insertItem(i18n("Parents"), 20); + } + popmenu.insertItem(i18n("Deselect"), &deselectTagsMenu); + + popmenu.insertItem(i18n("Invert Selection"), 16); + popmenu.insertSeparator(-1); + + TQPopupMenu toggleAutoMenu; + toggleAutoMenu.setCheckable(true); + toggleAutoMenu.insertItem(i18n("None"), 21); + toggleAutoMenu.insertSeparator(-1); + toggleAutoMenu.insertItem(i18n("Children"), 22); + toggleAutoMenu.insertItem(i18n("Parents"), 23); + toggleAutoMenu.insertItem(i18n("Both"), 24); + toggleAutoMenu.setItemChecked(21 + d->toggleAutoTags, true); + popmenu.insertItem(i18n("Toggle Auto"), &toggleAutoMenu); + + TagFilterView::ToggleAutoTags oldAutoTags = d->toggleAutoTags; + + int choice = popmenu.exec((TQCursor::pos())); + switch( choice ) + { + case 10: // New Tag. + { + tagNew(album); + break; + } + case 11: // Edit Tag Properties. + { + if (!album->isRoot()) + tagEdit(album); + break; + } + case 12: // Delete Tag. + { + if (!album->isRoot()) + tagDelete(album); + break; + } + case 13: // Reset Tag Icon. + { + TQString errMsg; + AlbumManager::instance()->updateTAlbumIcon(album, TQString("tag"), 0, errMsg); + break; + } + case 14: // Select All Tags. + { + d->toggleAutoTags = TagFilterView::NoToggleAuto; + TQListViewItemIterator it(d->tagsView, TQListViewItemIterator::NotChecked); + while (it.current()) + { + TAlbumCheckListItem* item = dynamic_cast(it.current()); + if (item->isVisible()) + item->setOn(true); + ++it; + } + d->toggleAutoTags = oldAutoTags; + break; + } + case 15: // Deselect All Tags. + { + d->toggleAutoTags = TagFilterView::NoToggleAuto; + TQListViewItemIterator it(d->tagsView, TQListViewItemIterator::Checked); + while (it.current()) + { + TAlbumCheckListItem* item = dynamic_cast(it.current()); + if (item->isVisible()) + item->setOn(false); + ++it; + } + d->toggleAutoTags = oldAutoTags; + break; + } + case 16: // Invert All Tags Selection. + { + d->toggleAutoTags = TagFilterView::NoToggleAuto; + TQListViewItemIterator it(d->tagsView); + while (it.current()) + { + TAlbumCheckListItem* item = dynamic_cast(it.current()); + if (item->isVisible()) + item->setOn(!item->isOn()); + ++it; + } + d->toggleAutoTags = oldAutoTags; + break; + } + case 17: // Select Child Tags. + { + d->toggleAutoTags = TagFilterView::NoToggleAuto; + toggleChildTags(album, true); + TAlbumCheckListItem *item = (TAlbumCheckListItem*)album->extraData(d->tagsView); + item->setOn(true); + d->toggleAutoTags = oldAutoTags; + break; + } + case 18: // Deselect Child Tags. + { + d->toggleAutoTags = TagFilterView::NoToggleAuto; + toggleChildTags(album, false); + TAlbumCheckListItem *item = (TAlbumCheckListItem*)album->extraData(d->tagsView); + item->setOn(false); + d->toggleAutoTags = oldAutoTags; + break; + } + case 19: // Select Parent Tags. + { + d->toggleAutoTags = TagFilterView::NoToggleAuto; + toggleParentTags(album, true); + TAlbumCheckListItem *item = (TAlbumCheckListItem*)album->extraData(d->tagsView); + item->setOn(true); + d->toggleAutoTags = oldAutoTags; + break; + } + case 20: // Deselect Parent Tags. + { + d->toggleAutoTags = TagFilterView::NoToggleAuto; + toggleParentTags(album, false); + TAlbumCheckListItem *item = (TAlbumCheckListItem*)album->extraData(d->tagsView); + item->setOn(false); + d->toggleAutoTags = oldAutoTags; + break; + } + case 21: // No toggle auto tags. + { + d->toggleAutoTags = TagFilterView::NoToggleAuto; + break; + } + case 22: // Toggle auto Children tags. + { + d->toggleAutoTags = TagFilterView::Children; + break; + } + case 23: // Toggle auto Parents tags. + { + d->toggleAutoTags = TagFilterView::Parents; + break; + } + case 24: // Toggle auto Children and Parents tags. + { + d->toggleAutoTags = TagFilterView::ChildrenAndParents; + break; + } + default: + break; + } + + if ( choice > 100 ) + { + tagNew(album, d->ABCMenu->text( choice ), "tag-people" ); + } + + delete d->ABCMenu; + d->ABCMenu = 0; +} + +void ImageDescEditTab::slotABCContextMenu() +{ + d->ABCMenu->clear(); + + int counter = 100; + TDEABC::AddressBook* ab = TDEABC::StdAddressBook::self(); + TQStringList names; + for ( TDEABC::AddressBook::Iterator it = ab->begin(); it != ab->end(); ++it ) + { + names.push_back(it->formattedName()); + } + + qHeapSort(names); + + for ( TQStringList::Iterator it = names.begin(); it != names.end(); ++it ) + { + TQString name = *it; + if ( !name.isNull() ) + d->ABCMenu->insertItem( name, ++counter ); + } + + if (counter == 100) + { + d->ABCMenu->insertItem( i18n("No AddressBook Entries Found"), ++counter ); + d->ABCMenu->setItemEnabled( counter, false ); + } +} + +void ImageDescEditTab::slotMoreMenu() +{ + d->moreMenu->clear(); + + if (singleSelection()) + { + d->moreMenu->insertItem(i18n("Read metadata from file to database"), this, TQ_SLOT(slotReadFromFileMetadataToDatabase())); + int writeActionId = d->moreMenu->insertItem(i18n("Write metadata to each file"), this, TQ_SLOT(slotWriteToFileMetadataFromDatabase())); + // we do not need a "Write to file" action here because the apply button will do just that + // if selection is a single file. + // Adding the option will confuse users: Does the apply button not write to file? + // Removing the option will confuse users: There is not option to write to file! (not visible in single selection) + // Disabling will confuse users: Why is it disabled? + d->moreMenu->setItemEnabled(writeActionId, false); + } + else + { + // We need to make clear that this action is different from the Apply button, + // which saves the same changes to all files. These batch operations operate on each single file. + d->moreMenu->insertItem(i18n("Read metadata from each file to database"), this, TQ_SLOT(slotReadFromFileMetadataToDatabase())); + d->moreMenu->insertItem(i18n("Write metadata to each file"), this, TQ_SLOT(slotWriteToFileMetadataFromDatabase())); + } +} + +void ImageDescEditTab::tagNew(TAlbum* parAlbum, const TQString& _title, const TQString& _icon) const +{ + if (!parAlbum) + return; + + TQString title = _title; + TQString icon = _icon; + + if (title.isNull()) + { + if (!TagEditDlg::tagCreate(kapp->activeWindow(), parAlbum, title, icon)) + return; + } + + TQMap errMap; + AlbumList tList = TagEditDlg::createTAlbum(parAlbum, title, icon, errMap); + TagEditDlg::showtagsListCreationError(kapp->activeWindow(), errMap); + + for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it) + { + TAlbumCheckListItem* item = (TAlbumCheckListItem*)(*it)->extraData(d->tagsView); + if (item) + { + item->setOn(true); + d->tagsView->setSelected(item, true); + d->tagsView->ensureItemVisible(item); + } + } +} + +void ImageDescEditTab::tagDelete(TAlbum *album) +{ + if (!album || album->isRoot()) + return; + + AlbumManager *albumMan = AlbumManager::instance(); + + if (album == albumMan->currentAlbum() || + album->isAncestorOf(albumMan->currentAlbum())) + { + KMessageBox::error(this, i18n("You are currently viewing items in the " + "tag '%1' that you are about to delete. " + "You will need to apply change first " + "if you want to delete the tag." ) + .arg(album->title())); + return; + } + + // find number of subtags + int children = 0; + AlbumIterator iter(album); + while(iter.current()) + { + children++; + ++iter; + } + + if(children) + { + int result = KMessageBox::warningContinueCancel(this, + i18n("Tag '%1' has one subtag. " + "Deleting this will also delete " + "the subtag. " + "Do you want to continue?", + "Tag '%1' has %n subtags. " + "Deleting this will also delete " + "the subtags. " + "Do you want to continue?", + children).arg(album->title())); + + if(result != KMessageBox::Continue) + return; + } + + TQString message; + LLongList assignedItems = albumMan->albumDB()->getItemIDsInTag(album->id()); + if (!assignedItems.isEmpty()) + { + message = i18n("Tag '%1' is assigned to one item. " + "Do you want to continue?", + "Tag '%1' is assigned to %n items. " + "Do you want to continue?", + assignedItems.count()).arg(album->title()); + } + else + { + message = i18n("Delete '%1' tag?").arg(album->title()); + } + + int result = KMessageBox::warningContinueCancel(this, message, + i18n("Delete Tag"), + KGuiItem(i18n("Delete"), + "edit-delete")); + + if (result == KMessageBox::Continue) + { + TQString errMsg; + if (!albumMan->deleteTAlbum(album, errMsg)) + KMessageBox::error(this, errMsg); + } +} + +void ImageDescEditTab::tagEdit(TAlbum* album) +{ + if (!album || album->isRoot()) + return; + + TQString title; + TQString icon; + + if (!TagEditDlg::tagEdit(kapp->activeWindow(), album, title, icon)) + return; + + AlbumManager *albumMan = AlbumManager::instance(); + if (album->title() != title) + { + TQString errMsg; + if (!albumMan->renameTAlbum(album, title, errMsg)) + { + KMessageBox::error(this, errMsg); + return; + } + } + + if (album->icon() != icon) + { + TQString errMsg; + if (!albumMan->updateTAlbumIcon(album, icon, 0, errMsg)) + { + KMessageBox::error(this, errMsg); + } + } +} + +void ImageDescEditTab::slotAlbumAdded(Album* a) +{ + if (!a || a->type() != Album::TAG) + return; + + TAlbumCheckListItem* viewItem = 0; + + TAlbum* tag = dynamic_cast(a); + if (!tag) + return; + + if (tag->isRoot()) + { + viewItem = new TAlbumCheckListItem(d->tagsView, tag); + } + else + { + TAlbumCheckListItem* parent = (TAlbumCheckListItem*)(tag->parent()->extraData(d->tagsView)); + if (!parent) + { + DWarning() << k_funcinfo << "Failed to find parent for Tag " << tag->title() + << endl; + return; + } + + viewItem = new TAlbumCheckListItem(parent, tag); + d->tagsSearchBar->lineEdit()->completionObject()->addItem(tag->title()); + d->newTagEdit->lineEdit()->completionObject()->addItem(tag->tagPath()); + d->newTagEdit->lineEdit()->completionObject()->addItem(tag->tagPath().remove(0, 1)); // without root "/" + } + + if (viewItem) + { + // commenting this out due to the issues described in bug 148166. + // viewItem->setOpen(true); + setTagThumbnail(tag); + } +} + +void ImageDescEditTab::slotAlbumDeleted(Album* a) +{ + if (!a || a->isRoot() || a->type() != Album::TAG) + return; + + TAlbum* album = (TAlbum*)a; + + d->tagsSearchBar->lineEdit()->completionObject()->removeItem(album->title()); + d->newTagEdit->lineEdit()->completionObject()->removeItem(album->tagPath()); + d->newTagEdit->lineEdit()->completionObject()->removeItem(album->tagPath().remove(0, 1)); // without root "/" + TAlbumCheckListItem* viewItem = (TAlbumCheckListItem*)album->extraData(d->tagsView); + delete viewItem; + album->removeExtraData(this); + d->hub.setTag(album, false, MetadataHub::MetadataDisjoint); +} + +void ImageDescEditTab::slotAlbumsCleared() +{ + d->tagsView->clear(); + d->tagsSearchBar->lineEdit()->completionObject()->clear(); + d->newTagEdit->lineEdit()->completionObject()->clear(); +} + +void ImageDescEditTab::slotAlbumIconChanged(Album* a) +{ + if (!a || a->isRoot() || a->type() != Album::TAG) + return; + + setTagThumbnail((TAlbum *)a); +} + +void ImageDescEditTab::slotAlbumMoved(TAlbum* tag, TAlbum* newParent) +{ + if (!tag || !newParent) + return; + + TAlbumCheckListItem* item = (TAlbumCheckListItem*)tag->extraData(d->tagsView); + if (!item) + return; + + if (item->parent()) + { + TQListViewItem* oldPItem = item->parent(); + oldPItem->takeItem(item); + } + else + { + d->tagsView->takeItem(item); + } + + TAlbumCheckListItem* newPItem = (TAlbumCheckListItem*)newParent->extraData(d->tagsView); + if (newPItem) + newPItem->insertItem(item); + else + d->tagsView->insertItem(item); +} + +void ImageDescEditTab::slotAlbumRenamed(Album* album) +{ + if (!album || album->isRoot() || album->type() != Album::TAG) + return; + + TAlbum* tag = (TAlbum*)album; + d->tagsSearchBar->lineEdit()->completionObject()->addItem(tag->title()); + d->newTagEdit->lineEdit()->completionObject()->addItem(tag->tagPath()); + d->newTagEdit->lineEdit()->completionObject()->addItem(tag->tagPath().remove(0, 1)); // without root "/" + slotTagsSearchChanged(d->tagsSearchBar->lineEdit()->text()); + TAlbumCheckListItem* item = (TAlbumCheckListItem*)(tag->extraData(d->tagsView)); + if (item) + item->refresh(); +} + +void ImageDescEditTab::toggleChildTags(TAlbum *album, bool b) +{ + if (!album) + return; + + AlbumIterator it(album); + while ( it.current() ) + { + TAlbum *ta = (TAlbum*)it.current(); + TAlbumCheckListItem *item = (TAlbumCheckListItem*)(ta->extraData(d->tagsView)); + if (item) + if (item->isVisible()) + item->setOn(b); + ++it; + } +} + +void ImageDescEditTab::toggleParentTags(TAlbum *album, bool b) +{ + if (!album) + return; + + TQListViewItemIterator it(d->tagsView); + while (it.current()) + { + TAlbumCheckListItem* item = dynamic_cast(it.current()); + if (item->isVisible()) + { + if (!item->album()) + continue; + if (item->album() == album->parent()) + { + item->setOn(b); + toggleParentTags(item->album() , b); + } + } + ++it; + } +} + +void ImageDescEditTab::setTagThumbnail(TAlbum *album) +{ + if(!album) + return; + + TAlbumCheckListItem* item = (TAlbumCheckListItem*)album->extraData(d->tagsView); + + if(!item) + return; + + AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance(); + TQPixmap icon; + if (!loader->getTagThumbnail(album, icon)) + { + if (icon.isNull()) + { + item->setPixmap(0, loader->getStandardTagIcon(album)); + } + else + { + TQPixmap blendedIcon = loader->blendIcons(loader->getStandardTagIcon(), icon); + item->setPixmap(0, blendedIcon); + } + } +} + +void ImageDescEditTab::slotGotThumbnailFromIcon(Album *album, const TQPixmap& thumbnail) +{ + if(!album || album->type() != Album::TAG) + return; + + // update item in tags tree + TAlbumCheckListItem* item = (TAlbumCheckListItem*)album->extraData(d->tagsView); + if(!item) + return; + + AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance(); + TQPixmap blendedIcon = loader->blendIcons(loader->getStandardTagIcon(), thumbnail); + item->setPixmap(0, blendedIcon); + + // update item in recent tags popup menu, if found there in + TQPopupMenu *menu = d->recentTagsBtn->popup(); + if (menu->indexOf(album->id()) != -1) + { + menu->changeItem(album->id(), thumbnail, menu->text(album->id())); + } +} + +void ImageDescEditTab::slotThumbnailLost(Album *) +{ + // we already set the standard icon before loading +} + +void ImageDescEditTab::slotReloadThumbnails() +{ + AlbumList tList = AlbumManager::instance()->allTAlbums(); + for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it) + { + TAlbum* tag = (TAlbum*)(*it); + setTagThumbnail(tag); + } +} + +void ImageDescEditTab::slotImageTagsChanged(TQ_LLONG imageId) +{ + // don't lose modifications + if (d->ignoreImageAttributesWatch || d->modified) + return; + + reloadForMetadataChange(imageId); +} + +void ImageDescEditTab::slotImagesChanged(int albumId) +{ + if (d->ignoreImageAttributesWatch || d->modified) + return; + + Album *a = AlbumManager::instance()->findAlbum(albumId); + if (d->currInfos.isEmpty() || !a || a->isRoot() || a->type() != Album::TAG) + return; + + setInfos(d->currInfos); +} + +void ImageDescEditTab::slotImageRatingChanged(TQ_LLONG imageId) +{ + if (d->ignoreImageAttributesWatch || d->modified) + return; + + reloadForMetadataChange(imageId); +} + +void ImageDescEditTab::slotImageCaptionChanged(TQ_LLONG imageId) +{ + if (d->ignoreImageAttributesWatch || d->modified) + return; + + reloadForMetadataChange(imageId); +} + +void ImageDescEditTab::slotImageDateChanged(TQ_LLONG imageId) +{ + if (d->ignoreImageAttributesWatch || d->modified) + return; + + reloadForMetadataChange(imageId); +} + +// private common code for above methods +void ImageDescEditTab::reloadForMetadataChange(TQ_LLONG imageId) +{ + if (d->currInfos.isEmpty()) + return; + + if (singleSelection()) + { + if (d->currInfos.first()->id() == imageId) + setInfos(d->currInfos); + } + else + { + // if image id is in our list, update + for (ImageInfo *info = d->currInfos.first(); info; info = d->currInfos.next()) + { + if (info->id() == imageId) + { + setInfos(d->currInfos); + return; + } + } + } +} + +void ImageDescEditTab::updateRecentTags() +{ + TQPopupMenu *menu = d->recentTagsBtn->popup(); + menu->clear(); + + AlbumManager* albumMan = AlbumManager::instance(); + IntList recentTags = albumMan->albumDB()->getRecentlyAssignedTags(); + + if (recentTags.isEmpty()) + { + menu->insertItem(i18n("No Recently Assigned Tags"), 0); + menu->setItemEnabled(0, false); + } + else + { + for (IntList::const_iterator it = recentTags.begin(); + it != recentTags.end(); ++it) + { + TAlbum* album = albumMan->findTAlbum(*it); + if (album) + { + AlbumThumbnailLoader *loader = AlbumThumbnailLoader::instance(); + TQPixmap icon; + if (!loader->getTagThumbnail(album, icon)) + { + if (icon.isNull()) + { + icon = loader->getStandardTagIcon(album, AlbumThumbnailLoader::SmallerSize); + } + } + TQString text = album->title() + " (" + ((TAlbum*)album->parent())->prettyURL() + ')'; + menu->insertItem(icon, text, album->id()); + } + } + } +} + +void ImageDescEditTab::slotRecentTagsMenuActivated(int id) +{ + AlbumManager* albumMan = AlbumManager::instance(); + + if (id > 0) + { + TAlbum* album = albumMan->findTAlbum(id); + if (album) + { + TAlbumCheckListItem* viewItem = (TAlbumCheckListItem*)album->extraData(d->tagsView); + if (viewItem) + { + viewItem->setOn(true); + d->tagsView->setSelected(viewItem, true); + d->tagsView->ensureItemVisible(viewItem); + } + } + } +} + +void ImageDescEditTab::slotTagsSearchChanged(const TQString& filter) +{ + if (filter.isEmpty()) + { + d->tagsView->collapseView(FolderView::OmitRoot); + return; + } + + //TODO: this will destroy assigned-tags filtering. Unify in one method. + TQString search = filter.lower(); + + bool atleastOneMatch = false; + + AlbumList tList = AlbumManager::instance()->allTAlbums(); + for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it) + { + TAlbum* tag = (TAlbum*)(*it); + + // don't touch the root Tag + if (tag->isRoot()) + continue; + + bool match = tag->title().lower().contains(search); + bool doesExpand = false; + if (!match) + { + // check if any of the parents match the search + Album* parent = tag->parent(); + while (parent && !parent->isRoot()) + { + if (parent->title().lower().contains(search)) + { + match = true; + break; + } + + parent = parent->parent(); + } + } + + if (!match) + { + // check if any of the children match the search + AlbumIterator it(tag); + while (it.current()) + { + if ((*it)->title().lower().contains(search)) + { + match = true; + doesExpand = true; + break; + } + ++it; + } + } + + TAlbumCheckListItem* viewItem = (TAlbumCheckListItem*)(tag->extraData(d->tagsView)); + + if (match) + { + atleastOneMatch = true; + + if (viewItem) + { + viewItem->setVisible(true); + viewItem->setOpen(doesExpand); + } + } + else + { + if (viewItem) + { + viewItem->setVisible(false); + viewItem->setOpen(false); + } + } + } + + if (search.isEmpty()) + { + TAlbum* root = AlbumManager::instance()->findTAlbum(0); + TAlbumCheckListItem* rootItem = (TAlbumCheckListItem*)(root->extraData(d->tagsView)); + if (rootItem) + rootItem->setText(0, root->title()); + } + else + { + TAlbum* root = AlbumManager::instance()->findTAlbum(0); + TAlbumCheckListItem* rootItem = (TAlbumCheckListItem*)(root->extraData(d->tagsView)); + if (rootItem) + rootItem->setText(0, i18n("Found Tags")); + } + + emit signalTagFilterMatch(atleastOneMatch); +} + +void ImageDescEditTab::slotAssignedTagsToggled(bool t) +{ + //TODO: this will destroy name filtering. Unify in one method. + TQListViewItemIterator it(d->tagsView); + while (it.current()) + { + TAlbumCheckListItem* item = dynamic_cast(it.current()); + TAlbum *tag = item->album(); + if (tag) + { + if (!tag->isRoot()) + { + if (t) + { + MetadataHub::TagStatus status = d->hub.tagStatus(item->album()); + bool tagAssigned = (status == MetadataHub::MetadataAvailable && status.hasTag) + || status == MetadataHub::MetadataDisjoint; + item->setVisible(tagAssigned); + + if (tagAssigned) + { + Album* parent = tag->parent(); + while (parent && !parent->isRoot()) + { + TAlbumCheckListItem *pitem = (TAlbumCheckListItem*)parent->extraData(d->tagsView); + pitem->setVisible(true); + parent = parent->parent(); + } + } + } + else + { + item->setVisible(true); + } + } + } + ++it; + } + + // correct visibilities afterwards: + // As TQListViewItem::setVisible works recursively on all it's children + // we have to correct this + if (t) + { + it = d->tagsView; + while (it.current()) + { + TAlbumCheckListItem* item = dynamic_cast(it.current()); + TAlbum *tag = item->album(); + if (tag) + { + if (!tag->isRoot()) + { + // only if the current item is not marked as tagged, check all children + MetadataHub::TagStatus status = d->hub.tagStatus(item->album()); + bool tagAssigned = (status == MetadataHub::MetadataAvailable && status.hasTag) + || status == MetadataHub::MetadataDisjoint; + if (!tagAssigned) + { + bool somethingIsSet = false; + TQListViewItem* nextSibling = (*it)->nextSibling(); + TQListViewItemIterator tmpIt = it; + ++tmpIt; + while (*tmpIt != nextSibling ) + { + TAlbumCheckListItem* tmpItem = dynamic_cast(tmpIt.current()); + MetadataHub::TagStatus tmpStatus = d->hub.tagStatus(tmpItem->album()); + bool tmpTagAssigned = (tmpStatus == MetadataHub::MetadataAvailable && tmpStatus.hasTag) + || tmpStatus == MetadataHub::MetadataDisjoint; + if(tmpTagAssigned) + { + somethingIsSet = true; + } + ++tmpIt; + } + if (!somethingIsSet) + { + item->setVisible(false); + } + } + } + } + ++it; + } + } + + TAlbum *root = AlbumManager::instance()->findTAlbum(0); + TAlbumCheckListItem *rootItem = (TAlbumCheckListItem*)(root->extraData(d->tagsView)); + if (rootItem) + { + if (t) + rootItem->setText(0, i18n("Assigned Tags")); + else + rootItem->setText(0, root->title()); + } +} + +void ImageDescEditTab::refreshTagsView() +{ + d->tagsView->refresh(); +} + +void ImageDescEditTab::slotCreateNewTag() +{ + TQString tagStr = d->newTagEdit->text(); + if (tagStr.isEmpty()) return; + + TAlbum *mainRootAlbum = 0; + TAlbumCheckListItem* item = dynamic_cast(d->tagsView->selectedItem()); + if (item) + mainRootAlbum = item->album(); + + TQMap errMap; + AlbumList tList = TagEditDlg::createTAlbum(mainRootAlbum, tagStr, TQString("tag"), errMap); + + for (AlbumList::iterator it = tList.begin(); it != tList.end(); ++it) + { + TAlbumCheckListItem* item = (TAlbumCheckListItem*)(*it)->extraData(d->tagsView); + if (item) + { + item->setOn(true); + d->tagsView->ensureItemVisible(item); + } + } + + d->newTagEdit->lineEdit()->clear(); +} + +} // NameSpace Digikam diff --git a/src/libs/imageproperties/imagedescedittab.h b/src/libs/imageproperties/imagedescedittab.h new file mode 100644 index 00000000..617bd59b --- /dev/null +++ b/src/libs/imageproperties/imagedescedittab.h @@ -0,0 +1,145 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2003-03-09 + * Description : Captions, Tags, and Rating properties editor + * + * Copyright (C) 2003-2005 by Renchi Raju + * Copyright (C) 2003-2009 by Gilles Caulier + * Copyright (C) 2006-2009 by Marcel Wiesweg + * Copyright (C) 2009 by Andi Clemens + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef IMAGEDESCEDITTAB_H +#define IMAGEDESCEDITTAB_H + +// TQt includes. + +#include +#include +#include + +// Local includes. + +#include "digikam_export.h" +#include "navigatebartab.h" +#include "albummanager.h" + +class TQListViewItem; + +namespace Digikam +{ +class TAlbumCheckListItem; +class ImageInfo; +class ImageDescEditTabPriv; + +class DIGIKAM_EXPORT ImageDescEditTab : public NavigateBarTab +{ + TQ_OBJECT + + +public: + + ImageDescEditTab(TQWidget *parent, bool navBar=true); + ~ImageDescEditTab(); + + void assignRating(int rating); + void setItem(ImageInfo *info=0); + void setItems(TQPtrList infos); + void populateTags(); + void refreshTagsView(); + +signals: + + void signalProgressBarMode(int, const TQString&); + void signalProgressValue(int); + void signalTagFilterMatch(bool); + +protected: + + bool eventFilter(TQObject *o, TQEvent *e); + +private: + + void setInfos(TQPtrList infos); + + void updateTagsView(); + void updateComments(); + void updateRating(); + void updateDate(); + void updateRecentTags(); + + void tagNew(TAlbum* parAlbum, const TQString& _title=TQString(), const TQString& _icon=TQString()) const; + void tagEdit(TAlbum* album); + void tagDelete(TAlbum *album); + + void toggleChildTags(TAlbum *album, bool b); + void toggleParentTags(TAlbum *album, bool b); + + void setTagThumbnail(TAlbum *album); + + bool singleSelection() const; + void setMetadataWidgetStatus(int status, TQWidget *widget); + void reloadForMetadataChange(TQ_LLONG imageId); + +private slots: + + void slotApplyAllChanges(); + void slotCreateNewTag(); + void slotRevertAllChanges(); + void slotChangingItems(); + void slotItemStateChanged(TAlbumCheckListItem *); + void slotCommentChanged(); + void slotDateTimeChanged(const TQDateTime& dateTime); + void slotRatingChanged(int rating); + void slotModified(); + void slotRightButtonClicked(TQListViewItem *, const TQPoint &, int); + void slotTagsSearchChanged(const TQString&); + + void slotAlbumAdded(Album* a); + void slotAlbumDeleted(Album* a); + void slotAlbumIconChanged(Album* a); + void slotAlbumRenamed(Album* a); + void slotAlbumsCleared(); + void slotAlbumMoved(TAlbum* tag, TAlbum* newParent); + + void slotABCContextMenu(); + void slotGotThumbnailFromIcon(Album *album, const TQPixmap& thumbnail); + void slotThumbnailLost(Album *album); + void slotReloadThumbnails(); + + void slotImageTagsChanged(TQ_LLONG imageId); + void slotImagesChanged(int albumId); + void slotImageRatingChanged(TQ_LLONG imageId); + void slotImageDateChanged(TQ_LLONG imageId); + void slotImageCaptionChanged(TQ_LLONG imageId); + + void slotRecentTagsMenuActivated(int); + void slotAssignedTagsToggled(bool); + + void slotMoreMenu(); + void slotReadFromFileMetadataToDatabase(); + void slotWriteToFileMetadataFromDatabase(); + +private: + + ImageDescEditTabPriv* d; +}; + +} // NameSpace Digikam + +#endif // IMAGEDESCEDITTAB_H diff --git a/src/libs/imageproperties/imagepropertiescolorstab.cpp b/src/libs/imageproperties/imagepropertiescolorstab.cpp new file mode 100644 index 00000000..af85953b --- /dev/null +++ b/src/libs/imageproperties/imagepropertiescolorstab.cpp @@ -0,0 +1,799 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-11-17 + * Description : a tab to display colors information of images + * + * Copyright (C) 2004-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// C++ includes. + +#include + +// TQt includes. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "imagehistogram.h" +#include "histogramwidget.h" +#include "colorgradientwidget.h" +#include "navigatebarwidget.h" +#include "sharedloadsavethread.h" +#include "iccprofilewidget.h" +#include "cietonguewidget.h" +#include "imagepropertiescolorstab.h" +#include "imagepropertiescolorstab.moc" + +namespace Digikam +{ + +class ImagePropertiesColorsTabPriv +{ +public: + + enum MetadataTab + { + HISTOGRAM=0, + ICCPROFILE + }; + + ImagePropertiesColorsTabPriv() + { + imageLoaderThread = 0; + tab = 0; + channelCB = 0; + colorsCB = 0; + renderingCB = 0; + scaleBG = 0; + regionBG = 0; + minInterv = 0; + maxInterv = 0; + labelMeanValue = 0; + labelPixelsValue = 0; + labelStdDevValue = 0; + labelCountValue = 0; + labelMedianValue = 0; + labelPercentileValue = 0; + labelColorDepth = 0; + labelAlphaChannel = 0; + + iccProfileWidget = 0; + hGradient = 0; + histogramWidget = 0; + imageLoaderThread = 0; + + inLoadingProcess = false; + } + + bool inLoadingProcess; + + TQComboBox *channelCB; + TQComboBox *colorsCB; + TQComboBox *renderingCB; + + TQHButtonGroup *scaleBG; + TQHButtonGroup *regionBG; + + TQSpinBox *minInterv; + TQSpinBox *maxInterv; + + TQLabel *labelMeanValue; + TQLabel *labelPixelsValue; + TQLabel *labelStdDevValue; + TQLabel *labelCountValue; + TQLabel *labelMedianValue; + TQLabel *labelPercentileValue; + TQLabel *labelColorDepth; + TQLabel *labelAlphaChannel; + + TQString currentFilePath; + LoadingDescription currentLoadingDescription; + + TQRect selectionArea; + + TQByteArray embedded_profile; + + KTabWidget *tab; + + DImg image; + DImg imageSelection; + + ICCProfileWidget *iccProfileWidget; + ColorGradientWidget *hGradient; + HistogramWidget *histogramWidget; + SharedLoadSaveThread *imageLoaderThread; +}; + +ImagePropertiesColorsTab::ImagePropertiesColorsTab(TQWidget* parent, bool navBar) + : NavigateBarTab(parent) +{ + d = new ImagePropertiesColorsTabPriv; + + setupNavigateBar(navBar); + d->tab = new KTabWidget(this); + m_navigateBarLayout->addWidget(d->tab); + + // Histogram tab area ----------------------------------------------------- + + TQScrollView *sv = new TQScrollView(d->tab); + sv->viewport()->setBackgroundMode(TQt::PaletteBackground); + sv->setResizePolicy(TQScrollView::AutoOneFit); + sv->setFrameStyle(TQFrame::NoFrame); + + TQWidget* histogramPage = new TQWidget(sv->viewport()); + TQGridLayout *topLayout = new TQGridLayout(histogramPage, 8, 3, + KDialog::spacingHint(), KDialog::spacingHint()); + sv->addChild(histogramPage); + + TQLabel *label1 = new TQLabel(i18n("Channel:"), histogramPage); + label1->setAlignment ( TQt::AlignRight | TQt::AlignVCenter ); + d->channelCB = new TQComboBox( false, histogramPage ); + d->channelCB->insertItem( i18n("Luminosity") ); + d->channelCB->insertItem( i18n("Red") ); + d->channelCB->insertItem( i18n("Green") ); + d->channelCB->insertItem( i18n("Blue") ); + d->channelCB->insertItem( i18n("Alpha") ); + d->channelCB->insertItem( i18n("Colors") ); + TQWhatsThis::add( d->channelCB, i18n("

    Select the histogram channel to display here:

    " + "Luminosity: Display luminosity (perceived brightness) values.

    " + "Red: Display the red image channel.

    " + "Green: Display the green image channel.

    " + "Blue: Display the blue image channel.

    " + "Alpha: Display the alpha image channel. " + "This channel corresponds to the transparency value and " + "is supported by some image formats such as PNG or TIFF.

    " + "Colors: Display all color channel values at the same time.")); + + d->scaleBG = new TQHButtonGroup(histogramPage); + d->scaleBG->setExclusive(true); + d->scaleBG->setFrameShape(TQFrame::NoFrame); + d->scaleBG->setInsideMargin( 0 ); + TQWhatsThis::add( d->scaleBG, i18n("

    Select the histogram scale here.

    " + "If the image's maximal values are small, you can use the linear scale.

    " + "Logarithmic scale can be used when the maximal values are big; " + "if it is used, all values (small and large) will be visible on the " + "graph.")); + + TQPushButton *linHistoButton = new TQPushButton( d->scaleBG ); + TQToolTip::add( linHistoButton, i18n( "

    Linear" ) ); + d->scaleBG->insert(linHistoButton, HistogramWidget::LinScaleHistogram); + TDEGlobal::dirs()->addResourceType("histogram-lin", TDEGlobal::dirs()->kde_default("data") + "digikam/data"); + TQString directory = TDEGlobal::dirs()->findResourceDir("histogram-lin", "histogram-lin.png"); + linHistoButton->setPixmap( TQPixmap( directory + "histogram-lin.png" ) ); + linHistoButton->setToggleButton(true); + + TQPushButton *logHistoButton = new TQPushButton( d->scaleBG ); + TQToolTip::add( logHistoButton, i18n( "

    Logarithmic" ) ); + d->scaleBG->insert(logHistoButton, HistogramWidget::LogScaleHistogram); + TDEGlobal::dirs()->addResourceType("histogram-log", TDEGlobal::dirs()->kde_default("data") + "digikam/data"); + directory = TDEGlobal::dirs()->findResourceDir("histogram-log", "histogram-log.png"); + logHistoButton->setPixmap( TQPixmap( directory + "histogram-log.png" ) ); + logHistoButton->setToggleButton(true); + + TQLabel *label10 = new TQLabel(i18n("Colors:"), histogramPage); + label10->setAlignment ( TQt::AlignRight | TQt::AlignVCenter ); + d->colorsCB = new TQComboBox( false, histogramPage ); + d->colorsCB->insertItem( i18n("Red") ); + d->colorsCB->insertItem( i18n("Green") ); + d->colorsCB->insertItem( i18n("Blue") ); + d->colorsCB->setEnabled( false ); + TQWhatsThis::add( d->colorsCB, i18n("

    Select the main color displayed with Colors Channel mode here:

    " + "Red: Draw the red image channel in the foreground.

    " + "Green: Draw the green image channel in the foreground.

    " + "Blue: Draw the blue image channel in the foreground.

    ")); + + d->regionBG = new TQHButtonGroup(histogramPage); + d->regionBG->setExclusive(true); + d->regionBG->setFrameShape(TQFrame::NoFrame); + d->regionBG->setInsideMargin( 0 ); + d->regionBG->hide(); + TQWhatsThis::add( d->regionBG, i18n("

    Select from which region the histogram will be computed here:

    " + "Full Image: Compute histogram using the full image.

    " + "Selection: Compute histogram using the current image " + "selection.")); + + TQPushButton *fullImageButton = new TQPushButton( d->regionBG ); + TQToolTip::add( fullImageButton, i18n( "

    Full Image" ) ); + d->regionBG->insert(fullImageButton, HistogramWidget::FullImageHistogram); + TDEGlobal::dirs()->addResourceType("image-full", TDEGlobal::dirs()->kde_default("data") + "digikam/data"); + directory = TDEGlobal::dirs()->findResourceDir("image-full", "image-full.png"); + fullImageButton->setPixmap( TQPixmap( directory + "image-full.png" ) ); + fullImageButton->setToggleButton(true); + + TQPushButton *SelectionImageButton = new TQPushButton( d->regionBG ); + TQToolTip::add( SelectionImageButton, i18n( "

    Selection" ) ); + d->regionBG->insert(SelectionImageButton, HistogramWidget::ImageSelectionHistogram); + TDEGlobal::dirs()->addResourceType("image-selection", TDEGlobal::dirs()->kde_default("data") + "digikam/data"); + directory = TDEGlobal::dirs()->findResourceDir("image-selection", "image-selection.png"); + SelectionImageButton->setPixmap( TQPixmap( directory + "image-selection.png" ) ); + SelectionImageButton->setToggleButton(true); + + // ------------------------------------------------------------- + + TQVBox *histoBox = new TQVBox(histogramPage); + d->histogramWidget = new HistogramWidget(256, 140, histoBox); + TQWhatsThis::add( d->histogramWidget, i18n("

    This is the histogram drawing of the " + "selected image channel")); + TQLabel *space = new TQLabel(histoBox); + space->setFixedHeight(1); + d->hGradient = new ColorGradientWidget(ColorGradientWidget::Horizontal, 10, histoBox); + d->hGradient->setColors(TQColor("black"), TQColor("white")); + + // ------------------------------------------------------------- + + TQHBoxLayout *hlay2 = new TQHBoxLayout(KDialog::spacingHint()); + TQLabel *label3 = new TQLabel(i18n("Range:"), histogramPage); + label3->setAlignment(TQt::AlignLeft | TQt::AlignVCenter); + d->minInterv = new TQSpinBox(0, 255, 1, histogramPage); + d->minInterv->setValue(0); + TQWhatsThis::add(d->minInterv, i18n("

    Select the minimal intensity " + "value of the histogram selection here.")); + d->maxInterv = new TQSpinBox(0, 255, 1, histogramPage); + d->maxInterv->setValue(255); + TQWhatsThis::add(d->minInterv, i18n("

    Select the maximal intensity value " + "of the histogram selection here.")); + hlay2->addWidget(label3); + hlay2->addWidget(d->minInterv); + hlay2->addWidget(d->maxInterv); + + // ------------------------------------------------------------- + + TQGroupBox *gbox = new TQGroupBox(2, TQt::Horizontal, i18n("Statistics"), histogramPage); + TQWhatsThis::add( gbox, i18n("

    Here you can see the statistical results calculated from the " + "selected histogram part. These values are available for all " + "channels.")); + + TQLabel *label5 = new TQLabel(i18n("Pixels:"), gbox); + label5->setAlignment(TQt::AlignLeft | TQt::AlignVCenter); + d->labelPixelsValue = new TQLabel(gbox); + d->labelPixelsValue->setAlignment(TQt::AlignRight | TQt::AlignVCenter); + + TQLabel *label7 = new TQLabel(i18n("Count:"), gbox); + label7->setAlignment(TQt::AlignLeft | TQt::AlignVCenter); + d->labelCountValue = new TQLabel(gbox); + d->labelCountValue->setAlignment(TQt::AlignRight | TQt::AlignVCenter); + + TQLabel *label4 = new TQLabel(i18n("Mean:"), gbox); + label4->setAlignment(TQt::AlignLeft | TQt::AlignVCenter); + d->labelMeanValue = new TQLabel(gbox); + d->labelMeanValue->setAlignment (TQt::AlignRight | TQt::AlignVCenter); + + TQLabel *label6 = new TQLabel(i18n("Std. deviation:"), gbox); + label6->setAlignment(TQt::AlignLeft | TQt::AlignVCenter); + d->labelStdDevValue = new TQLabel(gbox); + d->labelStdDevValue->setAlignment(TQt::AlignRight | TQt::AlignVCenter); + + TQLabel *label8 = new TQLabel(i18n("Median:"), gbox); + label8->setAlignment(TQt::AlignLeft | TQt::AlignVCenter); + d->labelMedianValue = new TQLabel(gbox); + d->labelMedianValue->setAlignment(TQt::AlignRight | TQt::AlignVCenter); + + TQLabel *label9 = new TQLabel(i18n("Percentile:"), gbox); + label9->setAlignment(TQt::AlignLeft | TQt::AlignVCenter); + d->labelPercentileValue = new TQLabel(gbox); + d->labelPercentileValue->setAlignment(TQt::AlignRight | TQt::AlignVCenter); + + TQLabel *label11 = new TQLabel(i18n("Color depth:"), gbox); + label11->setAlignment(TQt::AlignLeft | TQt::AlignVCenter); + d->labelColorDepth = new TQLabel(gbox); + d->labelColorDepth->setAlignment(TQt::AlignRight | TQt::AlignVCenter); + + TQLabel *label12 = new TQLabel(i18n("Alpha Channel:"), gbox); + label12->setAlignment(TQt::AlignLeft | TQt::AlignVCenter); + d->labelAlphaChannel = new TQLabel(gbox); + d->labelAlphaChannel->setAlignment(TQt::AlignRight | TQt::AlignVCenter); + + topLayout->addMultiCellWidget(label1, 1, 1, 0, 0); + topLayout->addMultiCellWidget(d->channelCB, 1, 1, 1, 1); + topLayout->addMultiCellWidget(d->scaleBG, 1, 1, 3, 3); + topLayout->addMultiCellWidget(label10, 2, 2, 0, 0); + topLayout->addMultiCellWidget(d->colorsCB, 2, 2, 1, 1); + topLayout->addMultiCellWidget(d->regionBG, 2, 2, 3, 3); + topLayout->addMultiCellWidget(histoBox, 3, 4, 0, 3); + topLayout->addMultiCellLayout(hlay2, 5, 5, 0, 3); + topLayout->addMultiCellWidget(gbox, 6, 6, 0, 3); + topLayout->setColStretch(2, 10); + topLayout->setRowStretch(7, 10); + + d->tab->insertTab(sv, i18n("Histogram"), ImagePropertiesColorsTabPriv::HISTOGRAM ); + + // ICC Profiles tab area --------------------------------------- + + TQScrollView *sv2 = new TQScrollView(d->tab); + sv2->viewport()->setBackgroundMode(TQt::PaletteBackground); + sv2->setResizePolicy(TQScrollView::AutoOneFit); + sv2->setFrameStyle(TQFrame::NoFrame); + + d->iccProfileWidget = new ICCProfileWidget(sv2->viewport()); + sv2->addChild(d->iccProfileWidget); + d->tab->insertTab(sv2, i18n("ICC profile"), ImagePropertiesColorsTabPriv::ICCPROFILE); + + // ------------------------------------------------------------- + + connect(d->channelCB, TQ_SIGNAL(activated(int)), + this, TQ_SLOT(slotChannelChanged(int))); + + connect(d->scaleBG, TQ_SIGNAL(released(int)), + this, TQ_SLOT(slotScaleChanged(int))); + + connect(d->colorsCB, TQ_SIGNAL(activated(int)), + this, TQ_SLOT(slotColorsChanged(int))); + + connect(d->regionBG, TQ_SIGNAL(released(int)), + this, TQ_SLOT(slotRenderingChanged(int))); + + connect(d->histogramWidget, TQ_SIGNAL(signalIntervalChanged( int, int )), + this, TQ_SLOT(slotUpdateInterval(int, int))); + + connect(d->histogramWidget, TQ_SIGNAL(signalMaximumValueChanged( int )), + this, TQ_SLOT(slotUpdateIntervRange(int))); + + connect(d->histogramWidget, TQ_SIGNAL(signalHistogramComputationDone(bool)), + this, TQ_SLOT(slotRefreshOptions(bool))); + + connect(d->histogramWidget, TQ_SIGNAL(signalHistogramComputationFailed(void)), + this, TQ_SLOT(slotHistogramComputationFailed(void))); + + connect(d->minInterv, TQ_SIGNAL(valueChanged (int)), + this, TQ_SLOT(slotMinValueChanged(int))); + + connect(d->maxInterv, TQ_SIGNAL(valueChanged (int)), + this, TQ_SLOT(slotMaxValueChanged(int))); + + // -- read config --------------------------------------------------------- + + TDEConfig* config = kapp->config(); + config->setGroup("Image Properties SideBar"); + d->tab->setCurrentPage(config->readNumEntry("ImagePropertiesColors Tab", + ImagePropertiesColorsTabPriv::HISTOGRAM)); + d->iccProfileWidget->setMode(config->readNumEntry("ICC Level", ICCProfileWidget::SIMPLE)); + d->iccProfileWidget->setCurrentItemByKey(config->readEntry("Current ICC Item", TQString())); + + d->channelCB->setCurrentItem(config->readNumEntry("Histogram Channel", 0)); // Luminosity. + d->scaleBG->setButton(config->readNumEntry("Histogram Scale", HistogramWidget::LogScaleHistogram)); + d->colorsCB->setCurrentItem(config->readNumEntry("Histogram Color", 0)); // Red. + d->regionBG->setButton(config->readNumEntry("Histogram Rendering", HistogramWidget::FullImageHistogram)); +} + +ImagePropertiesColorsTab::~ImagePropertiesColorsTab() +{ + // If there is a currently histogram computation when dialog is closed, + // stop it before the d->image data are deleted automatically! + d->histogramWidget->stopHistogramComputation(); + + TDEConfig* config = kapp->config(); + config->setGroup("Image Properties SideBar"); + config->writeEntry("ImagePropertiesColors Tab", d->tab->currentPageIndex()); + config->writeEntry("Histogram Channel", d->channelCB->currentItem()); + config->writeEntry("Histogram Scale", d->scaleBG->selectedId()); + config->writeEntry("Histogram Color", d->colorsCB->currentItem()); + config->writeEntry("Histogram Rendering", d->regionBG->selectedId()); + config->writeEntry("ICC Level", d->iccProfileWidget->getMode()); + config->writeEntry("Current ICC Item", d->iccProfileWidget->getCurrentItemKey()); + config->sync(); + + if (d->imageLoaderThread) + delete d->imageLoaderThread; + + if (d->histogramWidget) + delete d->histogramWidget; + + if (d->hGradient) + delete d->hGradient; + + delete d; +} + +void ImagePropertiesColorsTab::setData(const KURL& url, const TQRect &selectionArea, + DImg *img) +{ + // We might be getting duplicate events from AlbumIconView, + // which will cause all sorts of duplicate work. + // More importantly, while the loading thread can handle this pretty well, + // this will completely mess up the timing of progress info in the histogram widget. + // So filter here, before the stopHistogramComputation! + if (!img && url.path() == d->currentFilePath && d->inLoadingProcess) + return; + + // This is necessary to stop computation because d->image.bits() is currently used by + // threaded histogram algorithm. + d->histogramWidget->stopHistogramComputation(); + + d->currentFilePath = TQString(); + d->currentLoadingDescription = LoadingDescription(); + d->iccProfileWidget->loadFromURL(KURL()); + + // Clear information. + d->labelMeanValue->clear(); + d->labelPixelsValue->clear(); + d->labelStdDevValue->clear(); + d->labelCountValue->clear(); + d->labelMedianValue->clear(); + d->labelPercentileValue->clear(); + d->labelColorDepth->clear(); + d->labelAlphaChannel->clear(); + + if (url.isEmpty()) + { + setEnabled(false); + return; + } + + d->selectionArea = selectionArea; + d->image.reset(); + setEnabled(true); + + if (!img) + { + loadImageFromUrl(url); + } + else + { + d->image = img->copy(); + + if ( !d->image.isNull() ) + { + getICCData(); + + // If a selection area is done in Image Editor and if the current image is the same + // in Image Editor, then compute too the histogram for this selection. + if (d->selectionArea.isValid()) + { + d->imageSelection = d->image.copy(d->selectionArea); + d->histogramWidget->updateData(d->image.bits(), d->image.width(), d->image.height(), + d->image.sixteenBit(), d->imageSelection.bits(), + d->imageSelection.width(), d->imageSelection.height()); + d->regionBG->show(); + updateInformations(); + } + else + { + d->histogramWidget->updateData(d->image.bits(), d->image.width(), + d->image.height(), d->image.sixteenBit()); + d->regionBG->hide(); + updateInformations(); + } + } + else + { + d->histogramWidget->setLoadingFailed(); + d->iccProfileWidget->setLoadingFailed(); + slotHistogramComputationFailed(); + } + } +} + +void ImagePropertiesColorsTab::loadImageFromUrl(const KURL& url) +{ + // create thread on demand + if (!d->imageLoaderThread) + { + d->imageLoaderThread = new SharedLoadSaveThread(); + + connect(d->imageLoaderThread, TQ_SIGNAL(signalImageLoaded(const LoadingDescription &, const DImg&)), + this, TQ_SLOT(slotLoadImageFromUrlComplete(const LoadingDescription &, const DImg&))); + + connect(d->imageLoaderThread, TQ_SIGNAL(signalMoreCompleteLoadingAvailable(const LoadingDescription &, const LoadingDescription &)), + this, TQ_SLOT(slotMoreCompleteLoadingAvailable(const LoadingDescription &, const LoadingDescription &))); + } + + LoadingDescription desc = LoadingDescription(url.path()); + + if (DImg::fileFormat(desc.filePath) == DImg::RAW) + { + // use raw settings optimized for speed + + DRawDecoding rawDecodingSettings = DRawDecoding(); + rawDecodingSettings.optimizeTimeLoading(); + desc = LoadingDescription(desc.filePath, rawDecodingSettings); + } + + if (d->currentLoadingDescription.equalsOrBetterThan(desc)) + return; + + d->currentFilePath = desc.filePath; + d->currentLoadingDescription = desc; + d->inLoadingProcess = true; + + d->imageLoaderThread->load(d->currentLoadingDescription, + SharedLoadSaveThread::AccessModeRead, + SharedLoadSaveThread::LoadingPolicyFirstRemovePrevious); + + d->histogramWidget->setDataLoading(); + d->iccProfileWidget->setDataLoading(); +} + +void ImagePropertiesColorsTab::slotLoadImageFromUrlComplete(const LoadingDescription &loadingDescription, const DImg& img) +{ + // Discard any leftover messages from previous, possibly aborted loads + if ( !loadingDescription.equalsOrBetterThan(d->currentLoadingDescription) ) + return; + + if ( !img.isNull() ) + { + d->histogramWidget->updateData(img.bits(), img.width(), img.height(), img.sixteenBit()); + + // As a safety precaution, this must be changed only after updateData is called, + // which stops computation because d->image.bits() is currently used by threaded histogram algorithm. + d->image = img; + d->regionBG->hide(); + updateInformations(); + getICCData(); + } + else + { + d->histogramWidget->setLoadingFailed(); + d->iccProfileWidget->setLoadingFailed(); + slotHistogramComputationFailed(); + } + d->inLoadingProcess = false; +} + +void ImagePropertiesColorsTab::slotMoreCompleteLoadingAvailable(const LoadingDescription &oldLoadingDescription, + const LoadingDescription &newLoadingDescription) +{ + if (oldLoadingDescription == d->currentLoadingDescription && + newLoadingDescription.equalsOrBetterThan(d->currentLoadingDescription)) + { + // Yes, we do want to stop our old time-optimized loading and chain to the current, more complete loading. + // Even the time-optimized raw loading takes significant time, and we must avoid two dcraw instances running + // at a time. + d->currentLoadingDescription = newLoadingDescription; + d->inLoadingProcess = true; + d->imageLoaderThread->load(newLoadingDescription, + SharedLoadSaveThread::AccessModeRead, + SharedLoadSaveThread::LoadingPolicyFirstRemovePrevious); + } +} + +void ImagePropertiesColorsTab::setSelection(const TQRect &selectionArea) +{ + // This is necessary to stop computation because d->image.bits() is currently used by + // threaded histogram algorithm. + + d->histogramWidget->stopHistogramComputation(); + d->selectionArea = selectionArea; + + if (d->selectionArea.isValid()) + { + d->imageSelection = d->image.copy(d->selectionArea); + d->histogramWidget->updateSelectionData(d->imageSelection.bits(), d->imageSelection.width(), + d->imageSelection.height(), d->imageSelection.sixteenBit()); + d->regionBG->show(); + } + else + { + d->regionBG->hide(); + slotRenderingChanged(HistogramWidget::FullImageHistogram); + } +} + +void ImagePropertiesColorsTab::slotRefreshOptions(bool /*sixteenBit*/) +{ + slotChannelChanged(d->channelCB->currentItem()); + slotScaleChanged(d->scaleBG->selectedId()); + slotColorsChanged(d->colorsCB->currentItem()); + + if (d->selectionArea.isValid()) + slotRenderingChanged(d->regionBG->selectedId()); +} + +void ImagePropertiesColorsTab::slotHistogramComputationFailed() +{ + d->imageSelection.reset(); + d->image.reset(); +} + +void ImagePropertiesColorsTab::slotChannelChanged(int channel) +{ + switch(channel) + { + case RedChannel: + d->histogramWidget->m_channelType = HistogramWidget::RedChannelHistogram; + d->hGradient->setColors( TQColor( "black" ), TQColor( "red" ) ); + d->colorsCB->setEnabled(false); + break; + + case GreenChannel: + d->histogramWidget->m_channelType = HistogramWidget::GreenChannelHistogram; + d->hGradient->setColors( TQColor( "black" ), TQColor( "green" ) ); + d->colorsCB->setEnabled(false); + break; + + case BlueChannel: + d->histogramWidget->m_channelType = HistogramWidget::BlueChannelHistogram; + d->hGradient->setColors( TQColor( "black" ), TQColor( "blue" ) ); + d->colorsCB->setEnabled(false); + break; + + case AlphaChannel: + d->histogramWidget->m_channelType = HistogramWidget::AlphaChannelHistogram; + d->hGradient->setColors( TQColor( "black" ), TQColor( "white" ) ); + d->colorsCB->setEnabled(false); + break; + + case ColorChannels: + d->histogramWidget->m_channelType = HistogramWidget::ColorChannelsHistogram; + d->hGradient->setColors( TQColor( "black" ), TQColor( "white" ) ); + d->colorsCB->setEnabled(true); + break; + + default: // Luminosity. + d->histogramWidget->m_channelType = HistogramWidget::ValueHistogram; + d->hGradient->setColors( TQColor( "black" ), TQColor( "white" ) ); + d->colorsCB->setEnabled(false); + break; + } + + d->histogramWidget->repaint(false); + updateStatistiques(); +} + +void ImagePropertiesColorsTab::slotScaleChanged(int scale) +{ + d->histogramWidget->m_scaleType = scale; + d->histogramWidget->repaint(false); +} + +void ImagePropertiesColorsTab::slotColorsChanged(int color) +{ + switch(color) + { + case AllColorsGreen: + d->histogramWidget->m_colorType = HistogramWidget::GreenColor; + break; + + case AllColorsBlue: + d->histogramWidget->m_colorType = HistogramWidget::BlueColor; + break; + + default: // Red. + d->histogramWidget->m_colorType = HistogramWidget::RedColor; + break; + } + + d->histogramWidget->repaint(false); + updateStatistiques(); +} + +void ImagePropertiesColorsTab::slotRenderingChanged(int rendering) +{ + d->histogramWidget->m_renderingType = rendering; + d->histogramWidget->repaint(false); + updateStatistiques(); +} + +void ImagePropertiesColorsTab::slotMinValueChanged(int min) +{ + // Called when user changes values of spin box. + // Communicate the change to histogram widget. + + // make the one control "push" the other + if (min == d->maxInterv->value()+1) + d->maxInterv->setValue(min); + d->maxInterv->setMinValue(min-1); + d->histogramWidget->slotMinValueChanged(min); + updateStatistiques(); +} + +void ImagePropertiesColorsTab::slotMaxValueChanged(int max) +{ + if (max == d->minInterv->value()-1) + d->minInterv->setValue(max); + d->minInterv->setMaxValue(max+1); + d->histogramWidget->slotMaxValueChanged(max); + updateStatistiques(); +} + +void ImagePropertiesColorsTab::slotUpdateInterval(int min, int max) +{ + // Called when value is set from within histogram widget. + // Block signals to prevent slotMinValueChanged and + // slotMaxValueChanged being called. + d->minInterv->blockSignals(true); + d->minInterv->setMaxValue(max+1); + d->minInterv->setValue(min); + d->minInterv->blockSignals(false); + + d->maxInterv->blockSignals(true); + d->maxInterv->setMinValue(min-1); + d->maxInterv->setValue(max); + d->maxInterv->blockSignals(false); + + updateStatistiques(); +} + +void ImagePropertiesColorsTab::slotUpdateIntervRange(int range) +{ + d->maxInterv->setMaxValue( range ); +} + +void ImagePropertiesColorsTab::updateInformations() +{ + d->labelColorDepth->setText(d->image.sixteenBit() ? i18n("16 bits") : i18n("8 bits")); + d->labelAlphaChannel->setText(d->image.hasAlpha() ? i18n("Yes") : i18n("No")); +} + +void ImagePropertiesColorsTab::updateStatistiques() +{ + TQString value; + int min = d->minInterv->value(); + int max = d->maxInterv->value(); + int channel = d->channelCB->currentItem(); + + if ( channel == HistogramWidget::ColorChannelsHistogram ) + channel = d->colorsCB->currentItem()+1; + + double mean = d->histogramWidget->m_imageHistogram->getMean(channel, min, max); + d->labelMeanValue->setText(value.setNum(mean, 'f', 1)); + + double pixels = d->histogramWidget->m_imageHistogram->getPixels(); + d->labelPixelsValue->setText(value.setNum((float)pixels, 'f', 0)); + + double stddev = d->histogramWidget->m_imageHistogram->getStdDev(channel, min, max); + d->labelStdDevValue->setText(value.setNum(stddev, 'f', 1)); + + double counts = d->histogramWidget->m_imageHistogram->getCount(channel, min, max); + d->labelCountValue->setText(value.setNum((float)counts, 'f', 0)); + + double median = d->histogramWidget->m_imageHistogram->getMedian(channel, min, max); + d->labelMedianValue->setText(value.setNum(median, 'f', 1)); + + double percentile = (pixels > 0 ? (100.0 * counts / pixels) : 0.0); + d->labelPercentileValue->setText(value.setNum(percentile, 'f', 1)); +} + +void ImagePropertiesColorsTab::getICCData() +{ + if (d->image.getICCProfil().isNull()) + { + d->iccProfileWidget->setLoadingFailed(); + } + else + { + d->embedded_profile = d->image.getICCProfil(); + d->iccProfileWidget->loadFromData(d->currentFilePath, d->embedded_profile); + } +} + +} // NameSpace Digikam + diff --git a/src/libs/imageproperties/imagepropertiescolorstab.h b/src/libs/imageproperties/imagepropertiescolorstab.h new file mode 100644 index 00000000..0e01cad3 --- /dev/null +++ b/src/libs/imageproperties/imagepropertiescolorstab.h @@ -0,0 +1,114 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-11-17 + * Description : a tab to display colors information of images + * + * Copyright (C) 2004-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * ============================================================ */ + +#ifndef IMAGEPROPERTIESCOLORSTAB_H +#define IMAGEPROPERTIESCOLORSTAB_H + +// TQt includes. + +#include +#include + +// KDE includes. + +#include + +// Local includes. + +#include "dimg.h" +#include "digikam_export.h" +#include "navigatebartab.h" + +class TQRect; + +namespace Digikam +{ + +class DImg; +class LoadingDescription; +class ImagePropertiesColorsTabPriv; + +class DIGIKAM_EXPORT ImagePropertiesColorsTab : public NavigateBarTab +{ + TQ_OBJECT + + +public: + + ImagePropertiesColorsTab(TQWidget* parent, bool navBar=true); + ~ImagePropertiesColorsTab(); + + void setData(const KURL& url=KURL(), const TQRect &selectionArea = TQRect(), + DImg *img=0); + + void setSelection(const TQRect &selectionArea); + +private: + + void loadImageFromUrl(const KURL& url); + void updateInformations(); + void updateStatistiques(); + void getICCData(); + +private slots: + + void slotRefreshOptions(bool sixteenBit); + void slotHistogramComputationFailed(void); + void slotChannelChanged(int channel); + void slotScaleChanged(int scale); + void slotColorsChanged(int color); + void slotRenderingChanged(int rendering); + void slotMinValueChanged(int); + void slotMaxValueChanged(int); + + void slotUpdateInterval(int min, int max); + void slotUpdateIntervRange(int range); + + void slotLoadImageFromUrlComplete(const LoadingDescription &loadingDescription, const DImg& img); + void slotMoreCompleteLoadingAvailable(const LoadingDescription &oldLoadingDescription, + const LoadingDescription &newLoadingDescription); + +private: + + enum ColorChannel + { + LuminosityChannel=0, + RedChannel, + GreenChannel, + BlueChannel, + AlphaChannel, + ColorChannels + }; + + enum AllColorsColorType + { + AllColorsRed=0, + AllColorsGreen, + AllColorsBlue + }; + + ImagePropertiesColorsTabPriv* d; +}; + +} // NameSpace Digikam + +#endif /* IMAGEPROPERTIESCOLORSTAB_H */ diff --git a/src/libs/imageproperties/imagepropertiesmetadatatab.cpp b/src/libs/imageproperties/imagepropertiesmetadatatab.cpp new file mode 100644 index 00000000..5a48aaec --- /dev/null +++ b/src/libs/imageproperties/imagepropertiesmetadatatab.cpp @@ -0,0 +1,201 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-11-17 + * Description : a tab to display metadata information of images + * + * Copyright (C) 2004-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dmetadata.h" +#include "exifwidget.h" +#include "makernotewidget.h" +#include "iptcwidget.h" +#include "gpswidget.h" +#include "navigatebarwidget.h" +#include "imagepropertiesmetadatatab.h" +#include "imagepropertiesmetadatatab.moc" + +namespace Digikam +{ + +class ImagePropertiesMetadataTabPriv +{ +public: + + enum MetadataTab + { + EXIF=0, + MAKERNOTE, + IPTC, + GPS + }; + + ImagePropertiesMetadataTabPriv() + { + exifWidget = 0; + makernoteWidget = 0; + iptcWidget = 0; + gpsWidget = 0; + tab = 0; + } + + KTabWidget *tab; + + ExifWidget *exifWidget; + + MakerNoteWidget *makernoteWidget; + + IptcWidget *iptcWidget; + + GPSWidget *gpsWidget; +}; + +ImagePropertiesMetaDataTab::ImagePropertiesMetaDataTab(TQWidget* parent, bool navBar) + : NavigateBarTab(parent) +{ + d = new ImagePropertiesMetadataTabPriv; + + setupNavigateBar(navBar); + d->tab = new KTabWidget(this); + m_navigateBarLayout->addWidget(d->tab); + + // Exif tab area ----------------------------------------------------- + + d->exifWidget = new ExifWidget(d->tab); + d->tab->insertTab(d->exifWidget, i18n("EXIF"), ImagePropertiesMetadataTabPriv::EXIF); + + // Makernote tab area ----------------------------------------------------- + + d->makernoteWidget = new MakerNoteWidget(d->tab); + d->tab->insertTab(d->makernoteWidget, i18n("Makernote"), ImagePropertiesMetadataTabPriv::MAKERNOTE); + + // IPTC tab area --------------------------------------- + + d->iptcWidget = new IptcWidget(d->tab); + d->tab->insertTab(d->iptcWidget, i18n("IPTC"), ImagePropertiesMetadataTabPriv::IPTC); + + // GPS tab area --------------------------------------- + + d->gpsWidget = new GPSWidget(d->tab); + d->tab->insertTab(d->gpsWidget, i18n("GPS"), ImagePropertiesMetadataTabPriv::GPS); + + // -- read config --------------------------------------------------------- + + TDEConfig* config = kapp->config(); + config->setGroup("Image Properties SideBar"); + d->tab->setCurrentPage(config->readNumEntry("ImagePropertiesMetaData Tab", + ImagePropertiesMetadataTabPriv::EXIF)); + d->exifWidget->setMode(config->readNumEntry("EXIF Level", ExifWidget::SIMPLE)); + d->makernoteWidget->setMode(config->readNumEntry("MAKERNOTE Level", MakerNoteWidget::SIMPLE)); + d->iptcWidget->setMode(config->readNumEntry("IPTC Level", IptcWidget::SIMPLE)); + d->gpsWidget->setMode(config->readNumEntry("GPS Level", GPSWidget::SIMPLE)); + d->exifWidget->setCurrentItemByKey(config->readEntry("Current EXIF Item", TQString())); + d->makernoteWidget->setCurrentItemByKey(config->readEntry("Current MAKERNOTE Item", TQString())); + d->iptcWidget->setCurrentItemByKey(config->readEntry("Current IPTC Item", TQString())); + d->gpsWidget->setCurrentItemByKey(config->readEntry("Current GPS Item", TQString())); + d->gpsWidget->setWebGPSLocator(config->readNumEntry("Current Web GPS Locator", GPSWidget::MapQuest)); +} + +ImagePropertiesMetaDataTab::~ImagePropertiesMetaDataTab() +{ + TDEConfig* config = kapp->config(); + config->setGroup("Image Properties SideBar"); + config->writeEntry("ImagePropertiesMetaData Tab", d->tab->currentPageIndex()); + config->writeEntry("EXIF Level", d->exifWidget->getMode()); + config->writeEntry("MAKERNOTE Level", d->makernoteWidget->getMode()); + config->writeEntry("IPTC Level", d->iptcWidget->getMode()); + config->writeEntry("GPS Level", d->gpsWidget->getMode()); + config->writeEntry("Current EXIF Item", d->exifWidget->getCurrentItemKey()); + config->writeEntry("Current MAKERNOTE Item", d->makernoteWidget->getCurrentItemKey()); + config->writeEntry("Current IPTC Item", d->iptcWidget->getCurrentItemKey()); + config->writeEntry("Current GPS Item", d->gpsWidget->getCurrentItemKey()); + config->writeEntry("Current Web GPS Locator", d->gpsWidget->getWebGPSLocator()); + config->sync(); + + delete d; +} + +void ImagePropertiesMetaDataTab::setCurrentURL(const KURL& url) +{ + if (url.isEmpty()) + { + d->exifWidget->loadFromURL(url); + d->makernoteWidget->loadFromURL(url); + d->iptcWidget->loadFromURL(url); + d->gpsWidget->loadFromURL(url); + setEnabled(false); + return; + } + + setEnabled(true); + DMetadata metadata(url.path()); + + TQByteArray exifData = metadata.getExif(); + TQByteArray iptcData = metadata.getIptc(); + + d->exifWidget->loadFromData(url.filename(), exifData); + d->makernoteWidget->loadFromData(url.filename(), exifData); + d->iptcWidget->loadFromData(url.filename(), iptcData); + d->gpsWidget->loadFromData(url.filename(), exifData); +} + +void ImagePropertiesMetaDataTab::setCurrentData(const TQByteArray& exifData, + const TQByteArray& iptcData, + const TQString& filename) +{ + if (exifData.isEmpty() && iptcData.isEmpty()) + { + d->exifWidget->loadFromData(filename, exifData); + d->makernoteWidget->loadFromData(filename, exifData); + d->iptcWidget->loadFromData(filename, iptcData); + d->gpsWidget->loadFromData(filename, exifData); + setEnabled(false); + return; + } + + setEnabled(true); + + d->exifWidget->loadFromData(filename, exifData); + d->makernoteWidget->loadFromData(filename, exifData); + d->iptcWidget->loadFromData(filename, iptcData); + d->gpsWidget->loadFromData(filename, exifData); +} + +} // NameSpace Digikam + diff --git a/src/libs/imageproperties/imagepropertiesmetadatatab.h b/src/libs/imageproperties/imagepropertiesmetadatatab.h new file mode 100644 index 00000000..95b8ecb8 --- /dev/null +++ b/src/libs/imageproperties/imagepropertiesmetadatatab.h @@ -0,0 +1,68 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-11-17 + * Description : a tab to display metadata information of images + * + * Copyright (C) 2004-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef IMAGEPROPERTIESMETADATATAB_H +#define IMAGEPROPERTIESMETADATATAB_H + +// TQt includes. + +#include +#include + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" +#include "navigatebartab.h" + +namespace Digikam +{ + +class ImagePropertiesMetadataTabPriv; + +class DIGIKAM_EXPORT ImagePropertiesMetaDataTab : public NavigateBarTab +{ + TQ_OBJECT + + +public: + + ImagePropertiesMetaDataTab(TQWidget* parent, bool navBar=true); + ~ImagePropertiesMetaDataTab(); + + void setCurrentURL(const KURL& url=KURL()); + void setCurrentData(const TQByteArray& exifData=TQByteArray(), + const TQByteArray& iptcData=TQByteArray(), + const TQString& filename=TQString()); + +private: + + ImagePropertiesMetadataTabPriv* d; +}; + +} // NameSpace Digikam + +#endif /* IMAGEPROPERTIESMETADATATAB_H */ diff --git a/src/libs/imageproperties/imagepropertiessidebar.cpp b/src/libs/imageproperties/imagepropertiessidebar.cpp new file mode 100644 index 00000000..67254388 --- /dev/null +++ b/src/libs/imageproperties/imagepropertiessidebar.cpp @@ -0,0 +1,150 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-11-17 + * Description : simple image properties side bar (without support + * of digiKam database). + * + * Copyright (C) 2004-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "imagepropertiestab.h" +#include "imagepropertiesmetadatatab.h" +#include "imagepropertiescolorstab.h" +#include "imagepropertiessidebar.h" +#include "imagepropertiessidebar.moc" + +namespace Digikam +{ + +ImagePropertiesSideBar::ImagePropertiesSideBar(TQWidget *parent, const char *name, + TQSplitter *splitter, Side side, + bool mimimizedDefault, bool navBar) + : Sidebar(parent, name, side, mimimizedDefault) +{ + m_image = 0; + m_currentRect = TQRect(); + m_dirtyPropertiesTab = false; + m_dirtyMetadataTab = false; + m_dirtyColorTab = false; + + m_propertiesTab = new ImagePropertiesTab(parent, navBar); + m_metadataTab = new ImagePropertiesMetaDataTab(parent, navBar); + m_colorTab = new ImagePropertiesColorsTab(parent, navBar); + + setSplitter(splitter); + + appendTab(m_propertiesTab, SmallIcon("application-vnd.tde.info"), i18n("Properties")); + appendTab(m_metadataTab, SmallIcon("exifinfo"), i18n("Metadata")); + appendTab(m_colorTab, SmallIcon("blend"), i18n("Colors")); + + connect(this, TQ_SIGNAL(signalChangedTab(TQWidget*)), + this, TQ_SLOT(slotChangedTab(TQWidget*))); +} + +ImagePropertiesSideBar::~ImagePropertiesSideBar() +{ +} + +void ImagePropertiesSideBar::itemChanged(const KURL& url, const TQRect &rect, DImg *img) +{ + if (!url.isValid()) + return; + + m_currentURL = url; + m_currentRect = rect; + m_image = img; + m_dirtyPropertiesTab = false; + m_dirtyMetadataTab = false; + m_dirtyColorTab = false; + + slotChangedTab( getActiveTab() ); +} + +void ImagePropertiesSideBar::slotNoCurrentItem(void) +{ + m_currentURL = KURL(); + + m_propertiesTab->setCurrentURL(); + m_propertiesTab->setNavigateBarFileName(); + + m_metadataTab->setCurrentURL(); + m_metadataTab->setNavigateBarFileName(); + + m_colorTab->setData(); + m_colorTab->setNavigateBarFileName(); + + m_dirtyPropertiesTab = false; + m_dirtyMetadataTab = false; + m_dirtyColorTab = false; +} + +void ImagePropertiesSideBar::slotImageSelectionChanged(const TQRect &rect) +{ + m_currentRect = rect; + + if (m_dirtyColorTab) + m_colorTab->setSelection(rect); + else + slotChangedTab(m_colorTab); +} + +void ImagePropertiesSideBar::slotChangedTab(TQWidget* tab) +{ + if (!m_currentURL.isValid()) + return; + + setCursor(KCursor::waitCursor()); + + if (tab == m_propertiesTab && !m_dirtyPropertiesTab) + { + m_propertiesTab->setCurrentURL(m_currentURL); + m_dirtyPropertiesTab = true; + } + else if (tab == m_metadataTab && !m_dirtyMetadataTab) + { + m_metadataTab->setCurrentURL(m_currentURL); + m_dirtyMetadataTab = true; + } + else if (tab == m_colorTab && !m_dirtyColorTab) + { + m_colorTab->setData(m_currentURL, m_currentRect, m_image); + m_dirtyColorTab = true; + } + + unsetCursor(); +} + +} // NameSpace Digikam diff --git a/src/libs/imageproperties/imagepropertiessidebar.h b/src/libs/imageproperties/imagepropertiessidebar.h new file mode 100644 index 00000000..43e08df0 --- /dev/null +++ b/src/libs/imageproperties/imagepropertiessidebar.h @@ -0,0 +1,93 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-11-17 + * Description : simple image properties side bar (without support + * of digiKam database). + * + * Copyright (C) 2004-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef IMAGEPROPERTIESSIDEBAR_H +#define IMAGEPROPERTIESSIDEBAR_H + +// KDE includes. + +#include + +// Local includes. + +#include "sidebar.h" +#include "digikam_export.h" + +class TQSplitter; +class TQWidget; +class TQRect; + +namespace Digikam +{ + +class DImg; +class ImagePropertiesTab; +class ImagePropertiesMetaDataTab; +class ImagePropertiesColorsTab; + +class DIGIKAM_EXPORT ImagePropertiesSideBar : public Sidebar +{ + TQ_OBJECT + + +public: + + ImagePropertiesSideBar(TQWidget* parent, const char *name, TQSplitter *splitter, + Side side=Left, bool mimimizedDefault=false, bool navBar=false); + + ~ImagePropertiesSideBar(); + + virtual void itemChanged(const KURL& url, const TQRect &rect = TQRect(), DImg *img = 0); + +public slots: + + void slotImageSelectionChanged(const TQRect &rect); + virtual void slotNoCurrentItem(void); + + +protected slots: + + virtual void slotChangedTab(TQWidget* tab); + +protected: + + bool m_dirtyPropertiesTab; + bool m_dirtyMetadataTab; + bool m_dirtyColorTab; + + TQRect m_currentRect; + + KURL m_currentURL; + + DImg *m_image; + + ImagePropertiesTab *m_propertiesTab; + ImagePropertiesMetaDataTab *m_metadataTab; + ImagePropertiesColorsTab *m_colorTab; + +}; + +} // NameSpace Digikam + +#endif // IMAGEPROPERTIESSIDEBAR_H diff --git a/src/libs/imageproperties/imagepropertiessidebarcamgui.cpp b/src/libs/imageproperties/imagepropertiessidebarcamgui.cpp new file mode 100644 index 00000000..942c33e5 --- /dev/null +++ b/src/libs/imageproperties/imagepropertiessidebarcamgui.cpp @@ -0,0 +1,209 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-02-08 + * Description : simple image properties side bar used by + * camera gui. + * + * Copyright (C) 2006-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dmetadata.h" +#include "gpiteminfo.h" +#include "cameraiconview.h" +#include "cameraiconitem.h" +#include "cameraitempropertiestab.h" +#include "imagepropertiesmetadatatab.h" +#include "statusnavigatebar.h" +#include "navigatebarwidget.h" +#include "imagepropertiessidebarcamgui.h" +#include "imagepropertiessidebarcamgui.moc" + +namespace Digikam +{ + +class ImagePropertiesSideBarCamGuiPriv +{ +public: + + ImagePropertiesSideBarCamGuiPriv() + { + dirtyMetadataTab = false; + dirtyCameraItemTab = false; + metadataTab = 0; + cameraItemTab = 0; + itemInfo = 0; + cameraView = 0; + cameraItem = 0; + exifData = TQByteArray(); + currentURL = KURL(); + } + + bool dirtyMetadataTab; + bool dirtyCameraItemTab; + + TQByteArray exifData; + + KURL currentURL; + + GPItemInfo *itemInfo; + + ImagePropertiesMetaDataTab *metadataTab; + + CameraIconView *cameraView; + + CameraIconViewItem *cameraItem; + + CameraItemPropertiesTab *cameraItemTab; +}; + +ImagePropertiesSideBarCamGui::ImagePropertiesSideBarCamGui(TQWidget *parent, const char *name, + TQSplitter *splitter, Side side, + bool mimimizedDefault) + : Sidebar(parent, name, side, mimimizedDefault) +{ + d = new ImagePropertiesSideBarCamGuiPriv; + d->cameraItemTab = new CameraItemPropertiesTab(parent, true); + d->metadataTab = new ImagePropertiesMetaDataTab(parent, true); + + setSplitter(splitter); + + appendTab(d->cameraItemTab, SmallIcon("application-vnd.tde.info"), i18n("Properties")); + appendTab(d->metadataTab, SmallIcon("exifinfo"), i18n("Metadata")); + + // ---------------------------------------------------------- + + connectNavigateSignals(d->cameraItemTab); + connectNavigateSignals(d->metadataTab); + + connect(this, TQ_SIGNAL(signalChangedTab(TQWidget*)), + this, TQ_SLOT(slotChangedTab(TQWidget*))); +} + +ImagePropertiesSideBarCamGui::~ImagePropertiesSideBarCamGui() +{ + delete d; +} + +void ImagePropertiesSideBarCamGui::connectNavigateSignals(NavigateBarTab *tab) +{ + connect(tab, TQ_SIGNAL(signalFirstItem()), + this, TQ_SIGNAL(signalFirstItem())); + + connect(tab, TQ_SIGNAL(signalPrevItem()), + this, TQ_SIGNAL(signalPrevItem())); + + connect(tab, TQ_SIGNAL(signalNextItem()), + this, TQ_SIGNAL(signalNextItem())); + + connect(tab, TQ_SIGNAL(signalLastItem()), + this, TQ_SIGNAL(signalLastItem())); +} + +void ImagePropertiesSideBarCamGui::itemChanged(GPItemInfo* itemInfo, const KURL& url, + const TQByteArray& exifData, + CameraIconView* view, CameraIconViewItem* item) +{ + if (!itemInfo) + return; + + d->exifData = exifData; + d->itemInfo = itemInfo; + d->currentURL = url; + d->dirtyMetadataTab = false; + d->dirtyCameraItemTab = false; + d->cameraView = view; + d->cameraItem = item; + + if (d->exifData.isEmpty()) + { + DMetadata metaData(d->currentURL.path()); + d->exifData = metaData.getExif(); + } + + slotChangedTab( getActiveTab() ); +} + +void ImagePropertiesSideBarCamGui::slotNoCurrentItem(void) +{ + d->itemInfo = 0; + d->cameraItem = 0; + d->exifData = TQByteArray(); + d->currentURL = KURL(); + d->dirtyMetadataTab = false; + d->dirtyCameraItemTab = false; + + d->cameraItemTab->setCurrentItem(); + d->metadataTab->setCurrentURL(); +} + +void ImagePropertiesSideBarCamGui::slotChangedTab(TQWidget* tab) +{ + if (!d->itemInfo) + return; + + setCursor(KCursor::waitCursor()); + + if (tab == d->cameraItemTab && !d->dirtyCameraItemTab) + { + d->cameraItemTab->setCurrentItem(d->itemInfo, + d->cameraItem->getDownloadName(), d->exifData, + d->currentURL); + + d->dirtyCameraItemTab = true; + } + else if (tab == d->metadataTab && !d->dirtyMetadataTab) + { + d->metadataTab->setCurrentData(d->exifData, TQByteArray(), + d->itemInfo->name); + + d->dirtyMetadataTab = true; + } + + // setting of NavigateBar, common for all tabs + NavigateBarTab *navtab = dynamic_cast(tab); + if (navtab) + { + int currentItemType = StatusNavigateBar::ItemCurrent; + if (d->cameraView->firstItem() == d->cameraItem) + currentItemType = StatusNavigateBar::ItemFirst; + else if (d->cameraView->lastItem() == d->cameraItem) + currentItemType = StatusNavigateBar::ItemLast; + + navtab->setNavigateBarState(currentItemType); + navtab->setNavigateBarFileName(); + } + unsetCursor(); +} + +} // NameSpace Digikam diff --git a/src/libs/imageproperties/imagepropertiessidebarcamgui.h b/src/libs/imageproperties/imagepropertiessidebarcamgui.h new file mode 100644 index 00000000..3c2eafd0 --- /dev/null +++ b/src/libs/imageproperties/imagepropertiessidebarcamgui.h @@ -0,0 +1,87 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-02-08 + * Description : simple image properties side bar used by + * camera gui. + * + * Copyright (C) 2006-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef IMAGEPROPERTIESSIDEBARCAMGUI_H +#define IMAGEPROPERTIESSIDEBARCAMGUI_H + +// KDE includes. + +#include + +// Local includes. + +#include "sidebar.h" +#include "digikam_export.h" + +class TQSplitter; +class TQWidget; + +namespace Digikam +{ + +class GPItemInfo; +class CameraIconView; +class CameraIconViewItem; +class NavigateBarTab; +class ImagePropertiesSideBarCamGuiPriv; + +class DIGIKAM_EXPORT ImagePropertiesSideBarCamGui : public Sidebar +{ + TQ_OBJECT + + +public: + + ImagePropertiesSideBarCamGui(TQWidget* parent, const char *name, TQSplitter *splitter, + Side side=Left, bool mimimizedDefault=false); + + ~ImagePropertiesSideBarCamGui(); + + void itemChanged(GPItemInfo* itemInfo, const KURL& url, const TQByteArray& exifData=TQByteArray(), + CameraIconView* view=0, CameraIconViewItem* item=0); + +public slots: + + virtual void slotNoCurrentItem(void); + +signals: + + void signalFirstItem(void); + void signalPrevItem(void); + void signalNextItem(void); + void signalLastItem(void); + +private slots: + + virtual void slotChangedTab(TQWidget* tab); + +private: + + ImagePropertiesSideBarCamGuiPriv* d; + void connectNavigateSignals(NavigateBarTab *tab); +}; + +} // NameSpace Digikam + +#endif // IMAGEPROPERTIESSIDEBARCAMGUI_H diff --git a/src/libs/imageproperties/imagepropertiessidebardb.cpp b/src/libs/imageproperties/imagepropertiessidebardb.cpp new file mode 100644 index 00000000..f3dc5f7a --- /dev/null +++ b/src/libs/imageproperties/imagepropertiessidebardb.cpp @@ -0,0 +1,368 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-11-17 + * Description : image properties side bar using data from + * digiKam database. + * + * Copyright (C) 2004-2008 by Gilles Caulier + * Copyright (C) 2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "imageinfo.h" +#include "imagedescedittab.h" +#include "imageattributeswatch.h" +#include "imagepropertiestab.h" +#include "imagepropertiesmetadatatab.h" +#include "imagepropertiescolorstab.h" +#include "imagepropertiessidebardb.h" +#include "imagepropertiessidebardb.moc" + +namespace Digikam +{ + +class ImagePropertiesSideBarDBPriv +{ +public: + + ImagePropertiesSideBarDBPriv() + { + desceditTab = 0; + dirtyDesceditTab = false; + hasPrevious = false; + hasNext = false; + hasImageInfoOwnership = false; + } + + bool dirtyDesceditTab; + + TQPtrList currentInfos; + + ImageDescEditTab *desceditTab; + + bool hasPrevious; + bool hasNext; + + bool hasImageInfoOwnership; +}; + +ImagePropertiesSideBarDB::ImagePropertiesSideBarDB(TQWidget *parent, const char *name, TQSplitter *splitter, + Side side, bool mimimizedDefault) + : ImagePropertiesSideBar(parent, name, splitter, side, mimimizedDefault, false) +{ + // Navigate bar is disabled by passing false to parent class constructor, and tab constructors + + d = new ImagePropertiesSideBarDBPriv; + d->desceditTab = new ImageDescEditTab(parent, false); + + appendTab(d->desceditTab, SmallIcon("imagecomment"), i18n("Captions/Tags")); + + // ---------------------------------------------------------- + + connect(this, TQ_SIGNAL(signalChangedTab(TQWidget*)), + this, TQ_SLOT(slotChangedTab(TQWidget*))); + + connect(d->desceditTab, TQ_SIGNAL(signalProgressBarMode(int, const TQString&)), + this, TQ_SIGNAL(signalProgressBarMode(int, const TQString&))); + + connect(d->desceditTab, TQ_SIGNAL(signalProgressValue(int)), + this, TQ_SIGNAL(signalProgressValue(int))); + + ImageAttributesWatch *watch = ImageAttributesWatch::instance(); + + connect(watch, TQ_SIGNAL(signalFileMetadataChanged(const KURL &)), + this, TQ_SLOT(slotFileMetadataChanged(const KURL &))); +} + +ImagePropertiesSideBarDB::~ImagePropertiesSideBarDB() +{ + delete d; +} + +void ImagePropertiesSideBarDB::itemChanged(ImageInfo *info, + const TQRect &rect, DImg *img) +{ + itemChanged(info->kurl(), info, rect, img); +} + +void ImagePropertiesSideBarDB::itemChanged(const KURL& url, const TQRect &rect, DImg *img) +{ + itemChanged(url, 0, rect, img); +} + +void ImagePropertiesSideBarDB::itemChanged(const KURL& url, ImageInfo *info, + const TQRect &rect, DImg *img) +{ + if ( !url.isValid() ) + return; + + m_currentURL = url; + + TQPtrList list; + if (info) + list.append(info); + + itemChanged(list, rect, img); +} + +void ImagePropertiesSideBarDB::itemChanged(TQPtrList infos) +{ + if (infos.isEmpty()) + return; + + m_currentURL = infos.first()->kurl(); + + itemChanged(infos, TQRect(), 0); +} + +void ImagePropertiesSideBarDB::itemChanged(TQPtrList infos, + const TQRect &rect, DImg *img) +{ + m_currentRect = rect; + m_image = img; + + // The list _may_ have autoDelete set to true. + // Keep old ImageInfo objects from being deleted + // until the tab has had the chance to save changes and clear lists. + TQPtrList temporaryList; + if (d->hasImageInfoOwnership) + { + temporaryList = d->currentInfos; + d->hasImageInfoOwnership = false; + } + + d->currentInfos = infos; + + m_dirtyPropertiesTab = false; + m_dirtyMetadataTab = false; + m_dirtyColorTab = false; + d->dirtyDesceditTab = false; + + // All tabs that store the ImageInfo list and access it after selection change + // must release the image info here. slotChangedTab only handles the active tab! + d->desceditTab->setItem(); + + slotChangedTab( getActiveTab() ); + + // now delete old objects, after slotChangedTab + for (ImageInfo *info = temporaryList.first(); info; info = temporaryList.next()) + { + delete info; + } +} + +void ImagePropertiesSideBarDB::takeImageInfoOwnership(bool takeOwnership) +{ + d->hasImageInfoOwnership = takeOwnership; +} + + +void ImagePropertiesSideBarDB::slotNoCurrentItem(void) +{ + ImagePropertiesSideBar::slotNoCurrentItem(); + + // All tabs that store the ImageInfo list and access it after selection change + // must release the image info here. slotChangedTab only handles the active tab! + d->desceditTab->setItem(); + + if (d->hasImageInfoOwnership) + { + for (ImageInfo *info = d->currentInfos.first(); info; info = d->currentInfos.next()) + { + delete info; + } + d->hasImageInfoOwnership = false; + } + d->currentInfos.clear(); + + d->desceditTab->setItem(); + d->dirtyDesceditTab = false; +} + +void ImagePropertiesSideBarDB::populateTags(void) +{ + d->desceditTab->populateTags(); +} + +void ImagePropertiesSideBarDB::slotChangedTab(TQWidget* tab) +{ + setCursor(KCursor::waitCursor()); + + // No database data available, for example in the case of image editor is + // started from camera GUI. + if (d->currentInfos.isEmpty()) + { + if (tab == m_propertiesTab && !m_dirtyPropertiesTab) + { + m_propertiesTab->setCurrentURL(m_currentURL); + m_dirtyPropertiesTab = true; + } + else if (tab == m_metadataTab && !m_dirtyMetadataTab) + { + if (m_image) + m_metadataTab->setCurrentData(m_image->getExif(), m_image->getIptc(), + m_currentURL.fileName()); + else + m_metadataTab->setCurrentURL(m_currentURL); + + m_dirtyMetadataTab = true; + } + else if (tab == m_colorTab && !m_dirtyColorTab) + { + m_colorTab->setData(m_currentURL, m_currentRect, m_image); + m_dirtyColorTab = true; + } + else if (tab == d->desceditTab && !d->dirtyDesceditTab) + { + // Do nothing here. We cannot get data from database ! + d->desceditTab->setItem(); + d->dirtyDesceditTab = true; + } + } + else if (d->currentInfos.count() == 1) // Data from database available... + { + if (tab == m_propertiesTab && !m_dirtyPropertiesTab) + { + m_propertiesTab->setCurrentURL(m_currentURL); + m_dirtyPropertiesTab = true; + } + else if (tab == m_metadataTab && !m_dirtyMetadataTab) + { + if (m_image) + m_metadataTab->setCurrentData(m_image->getExif(), m_image->getIptc(), + m_currentURL.fileName()); + else + m_metadataTab->setCurrentURL(m_currentURL); + + m_dirtyMetadataTab = true; + } + else if (tab == m_colorTab && !m_dirtyColorTab) + { + m_colorTab->setData(m_currentURL, m_currentRect, m_image); + m_dirtyColorTab = true; + } + else if (tab == d->desceditTab && !d->dirtyDesceditTab) + { + d->desceditTab->setItem(d->currentInfos.first()); + d->dirtyDesceditTab = true; + } + } + else // Data from database available, multiple selection + { + if (tab == m_propertiesTab && !m_dirtyPropertiesTab) + { + //TODO + m_propertiesTab->setCurrentURL(m_currentURL); + m_dirtyPropertiesTab = true; + } + else if (tab == m_metadataTab && !m_dirtyMetadataTab) + { + // any ideas? + m_metadataTab->setCurrentURL(); + m_dirtyMetadataTab = true; + } + else if (tab == m_colorTab && !m_dirtyColorTab) + { + // any ideas? + m_colorTab->setData(); + m_dirtyColorTab = true; + } + else if (tab == d->desceditTab && !d->dirtyDesceditTab) + { + d->desceditTab->setItems(d->currentInfos); + d->dirtyDesceditTab = true; + } + } + + unsetCursor(); +} + +void ImagePropertiesSideBarDB::slotFileMetadataChanged(const KURL &url) +{ + if (url == m_currentURL) + { + // trigger an update + m_dirtyMetadataTab = false; + + if (getActiveTab() == m_metadataTab) + { + // update now - reuse code form slotChangedTab + slotChangedTab( getActiveTab() ); + } + } +} + +void ImagePropertiesSideBarDB::slotAssignRating(int rating) +{ + d->desceditTab->assignRating(rating); +} + +void ImagePropertiesSideBarDB::slotAssignRatingNoStar() +{ + d->desceditTab->assignRating(0); +} + +void ImagePropertiesSideBarDB::slotAssignRatingOneStar() +{ + d->desceditTab->assignRating(1); +} + +void ImagePropertiesSideBarDB::slotAssignRatingTwoStar() +{ + d->desceditTab->assignRating(2); +} + +void ImagePropertiesSideBarDB::slotAssignRatingThreeStar() +{ + d->desceditTab->assignRating(3); +} + +void ImagePropertiesSideBarDB::slotAssignRatingFourStar() +{ + d->desceditTab->assignRating(4); +} + +void ImagePropertiesSideBarDB::slotAssignRatingFiveStar() +{ + d->desceditTab->assignRating(5); +} + +void ImagePropertiesSideBarDB::refreshTagsView() +{ + d->desceditTab->refreshTagsView(); +} + +} // NameSpace Digikam diff --git a/src/libs/imageproperties/imagepropertiessidebardb.h b/src/libs/imageproperties/imagepropertiessidebardb.h new file mode 100644 index 00000000..7037b648 --- /dev/null +++ b/src/libs/imageproperties/imagepropertiessidebardb.h @@ -0,0 +1,117 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-11-17 + * Description : image properties side bar using data from + * digiKam database. + * + * Copyright (C) 2004-2008 by Gilles Caulier + * Copyright (C) 2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef IMAGEPROPERTIESSIDEBARDB_H +#define IMAGEPROPERTIESSIDEBARDB_H + +// TQt includes. + +#include + +// KDE includes. + +#include + +// Local includes. + +#include "imagepropertiessidebar.h" +#include "digikam_export.h" + +class TQSplitter; +class TQWidget; +class TQRect; + +namespace Digikam +{ + +class DImg; +class AlbumIconView; +class AlbumIconItem; +class ImageInfo; +class NavigateBarTab; +class ImagePropertiesSideBarDBPriv; + +class DIGIKAM_EXPORT ImagePropertiesSideBarDB : public ImagePropertiesSideBar +{ + TQ_OBJECT + + +public: + + ImagePropertiesSideBarDB(TQWidget* parent, const char *name, TQSplitter *splitter, Side side=Left, + bool mimimizedDefault=false); + + ~ImagePropertiesSideBarDB(); + + virtual void itemChanged(const KURL& url, const TQRect &rect = TQRect(), DImg *img = 0); + + virtual void itemChanged(ImageInfo *info, const TQRect &rect = TQRect(), DImg *img = 0); + virtual void itemChanged(TQPtrList infos); + + void takeImageInfoOwnership(bool takeOwnership); + + void populateTags(void); + void refreshTagsView(); + +signals: + + void signalFirstItem(void); + void signalPrevItem(void); + void signalNextItem(void); + void signalLastItem(void); + void signalProgressBarMode(int, const TQString&); + void signalProgressValue(int); + +public slots: + + void slotAssignRating(int rating); + void slotAssignRatingNoStar(); + void slotAssignRatingOneStar(); + void slotAssignRatingTwoStar(); + void slotAssignRatingThreeStar(); + void slotAssignRatingFourStar(); + void slotAssignRatingFiveStar(); + + virtual void slotNoCurrentItem(void); + +private slots: + + void slotChangedTab(TQWidget* tab); + void slotFileMetadataChanged(const KURL &url); + +private: + + void itemChanged(const KURL& url, ImageInfo *info, + const TQRect &rect, DImg *img); + void itemChanged(TQPtrList infos, const TQRect &rect, DImg *img); + +private: + + ImagePropertiesSideBarDBPriv* d; +}; + +} // NameSpace Digikam + +#endif // IMAGEPROPERTIESSIDEBARDB_H diff --git a/src/libs/imageproperties/imagepropertiestab.cpp b/src/libs/imageproperties/imagepropertiestab.cpp new file mode 100644 index 00000000..7141c039 --- /dev/null +++ b/src/libs/imageproperties/imagepropertiestab.cpp @@ -0,0 +1,601 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-04-19 + * Description : A tab to display general image information + * + * Copyright (C) 2006-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE includes. + +#include +#include +#include +#include +#include + +// LibKDcraw includes. + +#include +#include + +#if KDCRAW_VERSION < 0x000106 +#include +#endif + +// Local includes. + +#include "ddebug.h" +#include "dmetadata.h" +#include "navigatebarwidget.h" +#include "imagepropertiestab.h" +#include "imagepropertiestab.moc" + +namespace Digikam +{ + +class ImagePropertiesTabPriv +{ +public: + + ImagePropertiesTabPriv() + { + settingsArea = 0; + title = 0; + file = 0; + folder = 0; + modifiedDate = 0; + size = 0; + owner = 0; + permissions = 0; + title2 = 0; + mime = 0; + dimensions = 0; + compression = 0; + bitDepth = 0; + colorMode = 0; + title3 = 0; + make = 0; + model = 0; + photoDate = 0; + aperture = 0; + focalLength = 0; + exposureTime = 0; + sensitivity = 0; + exposureMode = 0; + flash = 0; + whiteBalance = 0; + labelFile = 0; + labelFolder = 0; + labelFileModifiedDate = 0; + labelFileSize = 0; + labelFileOwner = 0; + labelFilePermissions = 0; + labelImageMime = 0; + labelImageDimensions = 0; + labelImageCompression = 0; + labelImageBitDepth = 0; + labelImageColorMode = 0; + labelPhotoMake = 0; + labelPhotoModel = 0; + labelPhotoDateTime = 0; + labelPhotoAperture = 0; + labelPhotoFocalLength = 0; + labelPhotoExposureTime = 0; + labelPhotoSensitivity = 0; + labelPhotoExposureMode = 0; + labelPhotoFlash = 0; + labelPhotoWhiteBalance = 0; + } + + TQLabel *title; + TQLabel *file; + TQLabel *folder; + TQLabel *modifiedDate; + TQLabel *size; + TQLabel *owner; + TQLabel *permissions; + + TQLabel *title2; + TQLabel *mime; + TQLabel *dimensions; + TQLabel *compression; + TQLabel *bitDepth; + TQLabel *colorMode; + + TQLabel *title3; + TQLabel *make; + TQLabel *model; + TQLabel *photoDate; + TQLabel *aperture; + TQLabel *focalLength; + TQLabel *exposureTime; + TQLabel *sensitivity; + TQLabel *exposureMode; + TQLabel *flash; + TQLabel *whiteBalance; + + TQFrame *settingsArea; + + KSqueezedTextLabel *labelFile; + KSqueezedTextLabel *labelFolder; + KSqueezedTextLabel *labelFileModifiedDate; + KSqueezedTextLabel *labelFileSize; + KSqueezedTextLabel *labelFileOwner; + KSqueezedTextLabel *labelFilePermissions; + + KSqueezedTextLabel *labelImageMime; + KSqueezedTextLabel *labelImageDimensions; + KSqueezedTextLabel *labelImageCompression; + KSqueezedTextLabel *labelImageBitDepth; + KSqueezedTextLabel *labelImageColorMode; + + KSqueezedTextLabel *labelPhotoMake; + KSqueezedTextLabel *labelPhotoModel; + KSqueezedTextLabel *labelPhotoDateTime; + KSqueezedTextLabel *labelPhotoAperture; + KSqueezedTextLabel *labelPhotoFocalLength; + KSqueezedTextLabel *labelPhotoExposureTime; + KSqueezedTextLabel *labelPhotoSensitivity; + KSqueezedTextLabel *labelPhotoExposureMode; + KSqueezedTextLabel *labelPhotoFlash; + KSqueezedTextLabel *labelPhotoWhiteBalance; +}; + +ImagePropertiesTab::ImagePropertiesTab(TQWidget* parent, bool navBar) + : NavigateBarTab(parent) +{ + d = new ImagePropertiesTabPriv; + + setupNavigateBar(navBar); + + TQScrollView *sv = new TQScrollView(this); + sv->viewport()->setBackgroundMode(TQt::PaletteBackground); + sv->setResizePolicy(TQScrollView::AutoOneFit); + sv->setFrameStyle(TQFrame::NoFrame); + + d->settingsArea = new TQFrame(sv->viewport()); + d->settingsArea->setFrameStyle( TQFrame::StyledPanel | TQFrame::Sunken ); + d->settingsArea->setLineWidth( style().pixelMetric(TQStyle::PM_DefaultFrameWidth, this) ); + + sv->addChild(d->settingsArea); + m_navigateBarLayout->addWidget(sv); + + // -------------------------------------------------- + + TQGridLayout *settingsLayout = new TQGridLayout(d->settingsArea, 33, 1, KDialog::spacingHint(), 0); + + // -------------------------------------------------- + + d->title = new TQLabel(i18n("File Properties"), d->settingsArea); + d->file = new TQLabel(i18n("File:"), d->settingsArea); + d->folder = new TQLabel(i18n("Folder:"), d->settingsArea); + d->modifiedDate = new TQLabel(i18n("Modified:"), d->settingsArea); + d->size = new TQLabel(i18n("Size:"), d->settingsArea); + d->owner = new TQLabel(i18n("Owner:"), d->settingsArea); + d->permissions = new TQLabel(i18n("Permissions:"), d->settingsArea); + + KSeparator *line = new KSeparator(TQt::Horizontal, d->settingsArea); + d->title2 = new TQLabel(i18n("Image Properties"), d->settingsArea); + d->mime = new TQLabel(i18n("Type:"), d->settingsArea); + d->dimensions = new TQLabel(i18n("Dimensions:"), d->settingsArea); + d->compression = new TQLabel(i18n("Compression:"), d->settingsArea); + d->bitDepth = new TQLabel(i18n("Bit depth:"), d->settingsArea); + d->colorMode = new TQLabel(i18n("Color mode:"), d->settingsArea); + + KSeparator *line2 = new KSeparator(TQt::Horizontal, d->settingsArea); + d->title3 = new TQLabel(i18n("Photograph Properties"), d->settingsArea); + d->make = new TQLabel(i18n("Make:"), d->settingsArea); + d->model = new TQLabel(i18n("Model:"), d->settingsArea); + d->photoDate = new TQLabel(i18n("Created:"), d->settingsArea); + d->aperture = new TQLabel(i18n("Aperture:"), d->settingsArea); + d->focalLength = new TQLabel(i18n("Focal:"), d->settingsArea); + d->exposureTime = new TQLabel(i18n("Exposure:"), d->settingsArea); + d->sensitivity = new TQLabel(i18n("Sensitivity:"), d->settingsArea); + d->exposureMode = new TQLabel(i18n("Mode/Program:"), d->settingsArea); + d->flash = new TQLabel(i18n("Flash:"), d->settingsArea); + d->whiteBalance = new TQLabel(i18n("White balance:"), d->settingsArea); + + d->labelFile = new KSqueezedTextLabel(0, d->settingsArea); + d->labelFolder = new KSqueezedTextLabel(0, d->settingsArea); + d->labelFileModifiedDate = new KSqueezedTextLabel(0, d->settingsArea); + d->labelFileSize = new KSqueezedTextLabel(0, d->settingsArea); + d->labelFileOwner = new KSqueezedTextLabel(0, d->settingsArea); + d->labelFilePermissions = new KSqueezedTextLabel(0, d->settingsArea); + + d->labelImageMime = new KSqueezedTextLabel(0, d->settingsArea); + d->labelImageDimensions = new KSqueezedTextLabel(0, d->settingsArea); + d->labelImageCompression = new KSqueezedTextLabel(0, d->settingsArea); + d->labelImageBitDepth = new KSqueezedTextLabel(0, d->settingsArea); + d->labelImageColorMode = new KSqueezedTextLabel(0, d->settingsArea); + + d->labelPhotoMake = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoModel = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoDateTime = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoAperture = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoFocalLength = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoExposureTime = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoSensitivity = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoExposureMode = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoFlash = new KSqueezedTextLabel(0, d->settingsArea); + d->labelPhotoWhiteBalance = new KSqueezedTextLabel(0, d->settingsArea); + + int hgt = fontMetrics().height()-2; + d->title->setAlignment(TQt::AlignCenter); + d->file->setMaximumHeight(hgt); + d->folder->setMaximumHeight(hgt); + d->modifiedDate->setMaximumHeight(hgt); + d->size->setMaximumHeight(hgt); + d->owner->setMaximumHeight(hgt); + d->permissions->setMaximumHeight(hgt); + d->labelFile->setMaximumHeight(hgt); + d->labelFolder->setMaximumHeight(hgt); + d->labelFileModifiedDate->setMaximumHeight(hgt); + d->labelFileSize->setMaximumHeight(hgt); + d->labelFileOwner->setMaximumHeight(hgt); + d->labelFilePermissions->setMaximumHeight(hgt); + + d->title2->setAlignment(TQt::AlignCenter); + d->mime->setMaximumHeight(hgt); + d->dimensions->setMaximumHeight(hgt); + d->compression->setMaximumHeight(hgt); + d->bitDepth->setMaximumHeight(hgt); + d->colorMode->setMaximumHeight(hgt); + d->labelImageMime->setMaximumHeight(hgt); + d->labelImageDimensions->setMaximumHeight(hgt); + d->labelImageCompression->setMaximumHeight(hgt); + d->labelImageBitDepth->setMaximumHeight(hgt); + d->labelImageColorMode->setMaximumHeight(hgt); + + d->title3->setAlignment(TQt::AlignCenter); + d->make->setMaximumHeight(hgt); + d->model->setMaximumHeight(hgt); + d->photoDate->setMaximumHeight(hgt); + d->aperture->setMaximumHeight(hgt); + d->focalLength->setMaximumHeight(hgt); + d->exposureTime->setMaximumHeight(hgt); + d->sensitivity->setMaximumHeight(hgt); + d->exposureMode->setMaximumHeight(hgt); + d->flash->setMaximumHeight(hgt); + d->whiteBalance->setMaximumHeight(hgt); + d->labelPhotoMake->setMaximumHeight(hgt); + d->labelPhotoModel->setMaximumHeight(hgt); + d->labelPhotoDateTime->setMaximumHeight(hgt); + d->labelPhotoAperture->setMaximumHeight(hgt); + d->labelPhotoFocalLength->setMaximumHeight(hgt); + d->labelPhotoExposureTime->setMaximumHeight(hgt); + d->labelPhotoSensitivity->setMaximumHeight(hgt); + d->labelPhotoExposureMode->setMaximumHeight(hgt); + d->labelPhotoFlash->setMaximumHeight(hgt); + d->labelPhotoWhiteBalance->setMaximumHeight(hgt); + + // -------------------------------------------------- + + settingsLayout->addMultiCellWidget(d->title, 0, 0, 0, 1); + settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(), + TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 1, 1, 0, 1); + settingsLayout->addMultiCellWidget(d->file, 2, 2, 0, 0); + settingsLayout->addMultiCellWidget(d->labelFile, 2, 2, 1, 1); + settingsLayout->addMultiCellWidget(d->folder, 3, 3, 0, 0); + settingsLayout->addMultiCellWidget(d->labelFolder, 3, 3, 1, 1); + settingsLayout->addMultiCellWidget(d->modifiedDate, 4, 4, 0, 0); + settingsLayout->addMultiCellWidget(d->labelFileModifiedDate, 4, 4, 1, 1); + settingsLayout->addMultiCellWidget(d->size, 5, 5, 0, 0); + settingsLayout->addMultiCellWidget(d->labelFileSize, 5, 5, 1, 1); + settingsLayout->addMultiCellWidget(d->owner, 6, 6, 0, 0); + settingsLayout->addMultiCellWidget(d->labelFileOwner, 6, 6, 1, 1); + settingsLayout->addMultiCellWidget(d->permissions, 7, 7, 0, 0); + settingsLayout->addMultiCellWidget(d->labelFilePermissions, 7, 7, 1, 1); + + settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(), + TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 8, 8, 0, 1); + settingsLayout->addMultiCellWidget(line, 9, 9, 0, 1); + settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(), + TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 10, 10, 0, 1); + + settingsLayout->addMultiCellWidget(d->title2, 11, 11, 0, 1); + settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(), + TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 12, 12, 0, 1); + settingsLayout->addMultiCellWidget(d->mime, 13, 13, 0, 0); + settingsLayout->addMultiCellWidget(d->labelImageMime, 13, 13, 1, 1); + settingsLayout->addMultiCellWidget(d->dimensions, 14, 14, 0, 0); + settingsLayout->addMultiCellWidget(d->labelImageDimensions, 14, 14, 1, 1); + settingsLayout->addMultiCellWidget(d->compression, 15, 15, 0, 0); + settingsLayout->addMultiCellWidget(d->labelImageCompression, 15, 15, 1, 1); + settingsLayout->addMultiCellWidget(d->bitDepth, 16, 16, 0, 0); + settingsLayout->addMultiCellWidget(d->labelImageBitDepth, 16, 16, 1, 1); + settingsLayout->addMultiCellWidget(d->colorMode, 17, 17, 0, 0); + settingsLayout->addMultiCellWidget(d->labelImageColorMode, 17, 17, 1, 1); + + settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(), + TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 18, 18, 0, 1); + settingsLayout->addMultiCellWidget(line2, 19, 19, 0, 1); + settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(), + TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 20, 20, 0, 1); + + settingsLayout->addMultiCellWidget(d->title3, 21, 21, 0, 1); + settingsLayout->addMultiCell(new TQSpacerItem(KDialog::spacingHint(), KDialog::spacingHint(), + TQSizePolicy::Minimum, TQSizePolicy::MinimumExpanding), 22, 22, 0, 1); + settingsLayout->addMultiCellWidget(d->make, 23, 23, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoMake, 23, 23, 1, 1); + settingsLayout->addMultiCellWidget(d->model, 24, 24, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoModel, 24, 24, 1, 1); + settingsLayout->addMultiCellWidget(d->photoDate, 25, 25, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoDateTime, 25, 25, 1, 1); + settingsLayout->addMultiCellWidget(d->aperture, 26, 26, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoAperture, 26, 26, 1, 1); + settingsLayout->addMultiCellWidget(d->focalLength, 27, 27, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoFocalLength, 27, 27, 1, 1); + settingsLayout->addMultiCellWidget(d->exposureTime, 28, 28, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoExposureTime, 28, 28, 1, 1); + settingsLayout->addMultiCellWidget(d->sensitivity, 29, 29, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoSensitivity, 29, 29, 1, 1); + settingsLayout->addMultiCellWidget(d->exposureMode, 30, 30, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoExposureMode, 30, 30, 1, 1); + settingsLayout->addMultiCellWidget(d->flash, 31, 31, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoFlash, 31, 31, 1, 1); + settingsLayout->addMultiCellWidget(d->whiteBalance, 32, 32, 0, 0); + settingsLayout->addMultiCellWidget(d->labelPhotoWhiteBalance, 32, 32, 1, 1); + + settingsLayout->setRowStretch(33, 10); + settingsLayout->setColStretch(1, 10); +} + +ImagePropertiesTab::~ImagePropertiesTab() +{ + delete d; +} + +void ImagePropertiesTab::setCurrentURL(const KURL& url) +{ + if (url.isEmpty()) + { + setNavigateBarFileName(); + + d->labelFile->setText(TQString()); + d->labelFolder->setText(TQString()); + d->labelFileModifiedDate->setText(TQString()); + d->labelFileSize->setText(TQString()); + d->labelFileOwner->setText(TQString()); + d->labelFilePermissions->setText(TQString()); + + d->labelImageMime->setText(TQString()); + d->labelImageDimensions->setText(TQString()); + d->labelImageCompression->setText(TQString()); + d->labelImageBitDepth->setText(TQString()); + d->labelImageColorMode->setText(TQString()); + + d->labelPhotoMake->setText(TQString()); + d->labelPhotoModel->setText(TQString()); + d->labelPhotoDateTime->setText(TQString()); + d->labelPhotoAperture->setText(TQString()); + d->labelPhotoFocalLength->setText(TQString()); + d->labelPhotoExposureTime->setText(TQString()); + d->labelPhotoSensitivity->setText(TQString()); + d->labelPhotoExposureMode->setText(TQString()); + d->labelPhotoFlash->setText(TQString()); + d->labelPhotoWhiteBalance->setText(TQString()); + + setEnabled(false); + return; + } + + setEnabled(true); + + TQString str; + TQString unavailable(i18n("unavailable")); + + KFileItem fi(KFileItem::Unknown, KFileItem::Unknown, url); + TQFileInfo fileInfo(url.path()); + DMetadata metaData(url.path()); + + // -- File system information ------------------------------------------ + + d->labelFile->setText(url.fileName()); + d->labelFolder->setText(url.directory()); + + TQDateTime modifiedDate = fileInfo.lastModified(); + str = TDEGlobal::locale()->formatDateTime(modifiedDate, true, true); + d->labelFileModifiedDate->setText(str); + + str = TQString("%1 (%2)").arg(TDEIO::convertSize(fi.size())) + .arg(TDEGlobal::locale()->formatNumber(fi.size(), 0)); + d->labelFileSize->setText(str); + + d->labelFileOwner->setText( TQString("%1 - %2").arg(fi.user()).arg(fi.group()) ); + d->labelFilePermissions->setText( fi.permissionsString() ); + + // -- Image Properties -------------------------------------------------- + + TQSize dims; + TQString compression, bitDepth, colorMode; +#if KDCRAW_VERSION < 0x000106 + TQString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles()); +#else + TQString rawFilesExt(KDcrawIface::KDcraw::rawFiles()); +#endif + TQString ext = fileInfo.extension(false).upper(); + + if (!ext.isEmpty() && rawFilesExt.upper().contains(ext)) + { + d->labelImageMime->setText(i18n("RAW Image")); + compression = i18n("None"); + bitDepth = "48"; + dims = metaData.getImageDimensions(); + colorMode = i18n("Uncalibrated"); + } + else + { + d->labelImageMime->setText(fi.mimeComment()); + + KFileMetaInfo meta = fi.metaInfo(); + if (meta.isValid()) + { + if (meta.containsGroup("Jpeg EXIF Data")) // JPEG image ? + { + dims = meta.group("Jpeg EXIF Data").item("Dimensions").value().toSize(); + + TQString quality = meta.group("Jpeg EXIF Data").item("JPEG quality").value().toString(); + quality.isEmpty() ? compression = unavailable : + compression = i18n("JPEG quality %1").arg(quality); + bitDepth = meta.group("Jpeg EXIF Data").item("BitDepth").value().toString(); + colorMode = meta.group("Jpeg EXIF Data").item("ColorMode").value().toString(); + } + + if (meta.containsGroup("General")) + { + if (dims.isEmpty() ) + dims = meta.group("General").item("Dimensions").value().toSize(); + if (compression.isEmpty()) + compression = meta.group("General").item("Compression").value().toString(); + if (bitDepth.isEmpty()) + bitDepth = meta.group("General").item("BitDepth").value().toString(); + if (colorMode.isEmpty()) + colorMode = meta.group("General").item("ColorMode").value().toString(); + } + + if (meta.containsGroup("Technical")) + { + if (dims.isEmpty()) + dims = meta.group("Technical").item("Dimensions").value().toSize(); + if (compression.isEmpty()) + compression = meta.group("Technical").item("Compression").value().toString(); + if (bitDepth.isEmpty()) + bitDepth = meta.group("Technical").item("BitDepth").value().toString(); + if (colorMode.isEmpty()) + colorMode = meta.group("Technical").item("ColorMode").value().toString(); + } + } + } + + TQString mpixels; + mpixels.setNum(dims.width()*dims.height()/1000000.0, 'f', 2); + str = (!dims.isValid()) ? i18n("Unknown") : i18n("%1x%2 (%3Mpx)") + .arg(dims.width()).arg(dims.height()).arg(mpixels); + d->labelImageDimensions->setText(str); + d->labelImageCompression->setText(compression.isEmpty() ? unavailable : compression); + d->labelImageBitDepth->setText(bitDepth.isEmpty() ? unavailable : i18n("%1 bpp").arg(bitDepth)); + d->labelImageColorMode->setText(colorMode.isEmpty() ? unavailable : colorMode); + + // -- Photograph information ------------------------------------------ + // NOTA: If something is changed here, please updated albumfiletip section too. + + PhotoInfoContainer photoInfo = metaData.getPhotographInformations(); + + if (photoInfo.isEmpty()) + { + d->title3->hide(); + d->make->hide(); + d->model->hide(); + d->photoDate->hide(); + d->aperture->hide(); + d->focalLength->hide(); + d->exposureTime->hide(); + d->sensitivity->hide(); + d->exposureMode->hide(); + d->flash->hide(); + d->whiteBalance->hide(); + d->labelPhotoMake->hide(); + d->labelPhotoModel->hide(); + d->labelPhotoDateTime->hide(); + d->labelPhotoAperture->hide(); + d->labelPhotoFocalLength->hide(); + d->labelPhotoExposureTime->hide(); + d->labelPhotoSensitivity->hide(); + d->labelPhotoExposureMode->hide(); + d->labelPhotoFlash->hide(); + d->labelPhotoWhiteBalance->hide(); + } + else + { + d->title3->show(); + d->make->show(); + d->model->show(); + d->photoDate->show(); + d->aperture->show(); + d->focalLength->show(); + d->exposureTime->show(); + d->sensitivity->show(); + d->exposureMode->show(); + d->flash->show(); + d->whiteBalance->show(); + d->labelPhotoMake->show(); + d->labelPhotoModel->show(); + d->labelPhotoDateTime->show(); + d->labelPhotoAperture->show(); + d->labelPhotoFocalLength->show(); + d->labelPhotoExposureTime->show(); + d->labelPhotoSensitivity->show(); + d->labelPhotoExposureMode->show(); + d->labelPhotoFlash->show(); + d->labelPhotoWhiteBalance->show(); + } + + d->labelPhotoMake->setText(photoInfo.make.isEmpty() ? unavailable : photoInfo.make); + d->labelPhotoModel->setText(photoInfo.model.isEmpty() ? unavailable : photoInfo.model); + + if (photoInfo.dateTime.isValid()) + { + str = TDEGlobal::locale()->formatDateTime(photoInfo.dateTime, true, true); + d->labelPhotoDateTime->setText(str); + } + else + d->labelPhotoDateTime->setText(unavailable); + + d->labelPhotoAperture->setText(photoInfo.aperture.isEmpty() ? unavailable : photoInfo.aperture); + + if (photoInfo.focalLength35mm.isEmpty()) + d->labelPhotoFocalLength->setText(photoInfo.focalLength.isEmpty() ? unavailable : photoInfo.focalLength); + else + { + str = i18n("%1 (35mm: %2)").arg(photoInfo.focalLength).arg(photoInfo.focalLength35mm); + d->labelPhotoFocalLength->setText(str); + } + + d->labelPhotoExposureTime->setText(photoInfo.exposureTime.isEmpty() ? unavailable : photoInfo.exposureTime); + d->labelPhotoSensitivity->setText(photoInfo.sensitivity.isEmpty() ? unavailable : i18n("%1 ISO").arg(photoInfo.sensitivity)); + + if (photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty()) + d->labelPhotoExposureMode->setText(unavailable); + else if (!photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty()) + d->labelPhotoExposureMode->setText(photoInfo.exposureMode); + else if (photoInfo.exposureMode.isEmpty() && !photoInfo.exposureProgram.isEmpty()) + d->labelPhotoExposureMode->setText(photoInfo.exposureProgram); + else + { + str = TQString("%1 / %2").arg(photoInfo.exposureMode).arg(photoInfo.exposureProgram); + d->labelPhotoExposureMode->setText(str); + } + + d->labelPhotoFlash->setText(photoInfo.flash.isEmpty() ? unavailable : photoInfo.flash); + d->labelPhotoWhiteBalance->setText(photoInfo.whiteBalance.isEmpty() ? unavailable : photoInfo.whiteBalance); +} + +} // NameSpace Digikam diff --git a/src/libs/imageproperties/imagepropertiestab.h b/src/libs/imageproperties/imagepropertiestab.h new file mode 100644 index 00000000..2a69ce97 --- /dev/null +++ b/src/libs/imageproperties/imagepropertiestab.h @@ -0,0 +1,66 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-04-19 + * Description : A tab to display general image information + * + * Copyright (C) 2006-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef IMAGEPROPERTIESTAB_H +#define IMAGEPROPERTIESTAB_H + +// TQt includes. + +#include +#include +#include + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" +#include "navigatebartab.h" + +namespace Digikam +{ + +class ImagePropertiesTabPriv; + +class DIGIKAM_EXPORT ImagePropertiesTab : public NavigateBarTab +{ + TQ_OBJECT + + +public: + + ImagePropertiesTab(TQWidget* parent, bool navBar=true); + ~ImagePropertiesTab(); + + void setCurrentURL(const KURL& url=KURL()); + +private: + + ImagePropertiesTabPriv* d; +}; + +} // NameSpace Digikam + +#endif /* IMAGEPROPERTIESTAB_H */ diff --git a/src/libs/imageproperties/navigatebartab.cpp b/src/libs/imageproperties/navigatebartab.cpp new file mode 100644 index 00000000..d40ae134 --- /dev/null +++ b/src/libs/imageproperties/navigatebartab.cpp @@ -0,0 +1,146 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-01-04 + * Description : A parent tab class with a navigation bar + * + * Copyright (C) 2006-2007 by Gilles Caulier + * Copyright (C) 2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include +#include +#include + +// Local includes. + +#include "statusnavigatebar.h" +#include "navigatebarwidget.h" +#include "navigatebartab.h" +#include "navigatebartab.moc" + +namespace Digikam +{ + +class NavigateBarTabPriv +{ +public: + + NavigateBarTabPriv() + { + stack = 0; + navigateBar = 0; + label = 0; + } + + TQWidgetStack *stack; + + TQLabel *label; + + NavigateBarWidget *navigateBar; +}; + +NavigateBarTab::NavigateBarTab(TQWidget* parent) + : TQWidget(parent, 0, TQt::WDestructiveClose) +{ + d = new NavigateBarTabPriv; + m_navigateBarLayout = 0; +} + +NavigateBarTab::~NavigateBarTab() +{ + delete d; +} + +void NavigateBarTab::setupNavigateBar(bool withBar) +{ + m_navigateBarLayout = new TQVBoxLayout(this); + + if (withBar) + { + d->stack = new TQWidgetStack(this); + m_navigateBarLayout->addWidget(d->stack); + + d->navigateBar = new NavigateBarWidget(d->stack, withBar); + d->stack->addWidget(d->navigateBar); + + connect(d->navigateBar, TQ_SIGNAL(signalFirstItem()), + this, TQ_SIGNAL(signalFirstItem())); + + connect(d->navigateBar, TQ_SIGNAL(signalPrevItem()), + this, TQ_SIGNAL(signalPrevItem())); + + connect(d->navigateBar, TQ_SIGNAL(signalNextItem()), + this, TQ_SIGNAL(signalNextItem())); + + connect(d->navigateBar, TQ_SIGNAL(signalLastItem()), + this, TQ_SIGNAL(signalLastItem())); + + d->label = new TQLabel(d->stack); + d->label->setAlignment(TQt::AlignCenter); + d->stack->addWidget(d->label); + } +} + +void NavigateBarTab::setNavigateBarState(bool hasPrevious, bool hasNext) +{ + if (!d->navigateBar) + return; + + d->stack->raiseWidget(d->navigateBar); + + if (hasPrevious && hasNext) + d->navigateBar->setButtonsState(StatusNavigateBar::ItemCurrent); + else if (!hasPrevious && hasNext) + d->navigateBar->setButtonsState(StatusNavigateBar::ItemFirst); + else if (hasPrevious && !hasNext) + d->navigateBar->setButtonsState(StatusNavigateBar::ItemLast); + else + d->navigateBar->setButtonsState(StatusNavigateBar::NoNavigation); +} + +void NavigateBarTab::setNavigateBarState(int itemType) +{ + if (!d->navigateBar) + return; + + d->stack->raiseWidget(d->navigateBar); + d->navigateBar->setButtonsState(itemType); +} + +void NavigateBarTab::setNavigateBarFileName(const TQString &name) +{ + if (!d->navigateBar) + return; + + d->stack->raiseWidget(d->navigateBar); + d->navigateBar->setFileName(name); +} + +void NavigateBarTab::setLabelText(const TQString &text) +{ + if (!d->label) + return; + + d->stack->raiseWidget(d->label); + d->label->setText(text); +} + +} // NameSpace Digikam + diff --git a/src/libs/imageproperties/navigatebartab.h b/src/libs/imageproperties/navigatebartab.h new file mode 100644 index 00000000..a93090e0 --- /dev/null +++ b/src/libs/imageproperties/navigatebartab.h @@ -0,0 +1,85 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2007-01-04 + * Description : A parent tab class with a navigation bar + * + * Copyright (C) 2006-2007 by Gilles Caulier + * Copyright (C) 2007 by Marcel Wiesweg + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef NAVIGATEBARTAB_H +#define NAVIGATEBARTAB_H + +// TQt includes. + +#include +#include + +// KDE includes. + +#include + +// Local includes. + +#include "digikam_export.h" +#include "imagepropertiessidebar.h" + +class TQVBoxLayout; + +namespace Digikam +{ + +class NavigateBarWidget; +class NavigateBarTabPriv; + +class DIGIKAM_EXPORT NavigateBarTab : public TQWidget +{ + TQ_OBJECT + + +public: + + NavigateBarTab(TQWidget* parent); + ~NavigateBarTab(); + + void setNavigateBarState(bool hasPrevious, bool hasNext); + void setNavigateBarState(int itemType); + void setNavigateBarFileName(const TQString &name = TQString()); + void setLabelText(const TQString &text); + +signals: + + void signalFirstItem(void); + void signalPrevItem(void); + void signalNextItem(void); + void signalLastItem(void); + +protected: + + void setupNavigateBar(bool withBar); + +protected: + + TQVBoxLayout *m_navigateBarLayout; + NavigateBarTabPriv *d; + +}; + +} // NameSpace Digikam + +#endif /* NAVIGATEBARTAB_H */ diff --git a/src/libs/imageproperties/navigatebarwidget.cpp b/src/libs/imageproperties/navigatebarwidget.cpp new file mode 100644 index 00000000..9232c55e --- /dev/null +++ b/src/libs/imageproperties/navigatebarwidget.cpp @@ -0,0 +1,112 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-07-07 + * Description : a navigate bar with text + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include + +// KDE includes. + +#include +#include +#include + +// Local includes. + +#include "statusnavigatebar.h" +#include "navigatebarwidget.h" +#include "navigatebarwidget.moc" + +namespace Digikam +{ + +class NavigateBarWidgetPriv +{ +public: + + NavigateBarWidgetPriv() + { + filename = 0; + navBar = 0; + } + + KSqueezedTextLabel *filename; + + StatusNavigateBar *navBar; +}; + +NavigateBarWidget::NavigateBarWidget(TQWidget *parent, bool show) + : TQWidget(parent, 0, TQt::WDestructiveClose) +{ + d = new NavigateBarWidgetPriv; + + TQHBoxLayout *lay = new TQHBoxLayout(this); + d->navBar = new StatusNavigateBar(this); + d->filename = new KSqueezedTextLabel(this); + + lay->addWidget(d->navBar); + lay->addSpacing( KDialog::spacingHint() ); + lay->addWidget(d->filename); + + if (!show) hide(); + + connect(d->navBar, TQ_SIGNAL(signalFirstItem()), + this, TQ_SIGNAL(signalFirstItem())); + + connect(d->navBar, TQ_SIGNAL(signalPrevItem()), + this, TQ_SIGNAL(signalPrevItem())); + + connect(d->navBar, TQ_SIGNAL(signalNextItem()), + this, TQ_SIGNAL(signalNextItem())); + + connect(d->navBar, TQ_SIGNAL(signalLastItem()), + this, TQ_SIGNAL(signalLastItem())); +} + +NavigateBarWidget::~NavigateBarWidget() +{ + delete d; +} + +void NavigateBarWidget::setFileName(TQString filename) +{ + d->filename->setText(filename); +} + +TQString NavigateBarWidget::getFileName() +{ + return (d->filename->text()); +} + +void NavigateBarWidget::setButtonsState(int itemType) +{ + d->navBar->setButtonsState(itemType); +} + +int NavigateBarWidget::getButtonsState() +{ + return (d->navBar->getButtonsState()); +} + +} // namespace Digikam + diff --git a/src/libs/imageproperties/navigatebarwidget.h b/src/libs/imageproperties/navigatebarwidget.h new file mode 100644 index 00000000..7be088ed --- /dev/null +++ b/src/libs/imageproperties/navigatebarwidget.h @@ -0,0 +1,70 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-07-07 + * Description : a navigate bar with text + * + * Copyright (C) 2005-2007 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef NAVIGATEBARWIDGET_H +#define NAVIGATEBARWIDGET_H + +// TQt includes. + +#include +#include + +// Local includes. + +#include "digikam_export.h" + +namespace Digikam +{ + +class NavigateBarWidgetPriv; + +class DIGIKAM_EXPORT NavigateBarWidget : public TQWidget +{ +TQ_OBJECT + + +public: + + NavigateBarWidget(TQWidget *parent=0, bool show=true); + ~NavigateBarWidget(); + + void setFileName(TQString filename=TQString()); + TQString getFileName(); + void setButtonsState(int itemType); + int getButtonsState(); + +signals: + + void signalFirstItem(void); + void signalPrevItem(void); + void signalNextItem(void); + void signalLastItem(void); + +private : + + NavigateBarWidgetPriv* d; +}; + +} // namespace Digikam + +#endif /* NAVIGATEBARWIDGET_H */ diff --git a/src/libs/imageproperties/talbumlistview.cpp b/src/libs/imageproperties/talbumlistview.cpp new file mode 100644 index 00000000..1369d228 --- /dev/null +++ b/src/libs/imageproperties/talbumlistview.cpp @@ -0,0 +1,528 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-18-12 + * Description : A list view to display digiKam Tags. + * + * Copyright (C) 2006-2009 by Gilles Caulier + * Copyright (C) 2009 by Andi Clemens + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include + +// KDE includes. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "albumiconitem.h" +#include "albumlister.h" +#include "albummanager.h" +#include "albumdb.h" +#include "album.h" +#include "albumsettings.h" +#include "imageinfo.h" +#include "navigatebarwidget.h" +#include "dragobjects.h" +#include "imageattributeswatch.h" +#include "albumthumbnailloader.h" +#include "statusprogressbar.h" +#include "talbumlistview.h" +#include "talbumlistview.moc" + +// X11 includes. + +extern "C" +{ +#include +} + +namespace Digikam +{ + +TAlbumCheckListItem::TAlbumCheckListItem(TQListView* parent, TAlbum* album) + : FolderCheckListItem(parent, album->title(), TQCheckListItem::RadioButtonController) +{ + setDragEnabled(true); + m_album = album; + m_count = 0; + + if (m_album) + m_album->setExtraData(listView(), this); +} + +TAlbumCheckListItem::TAlbumCheckListItem(TQCheckListItem* parent, TAlbum* album) + : FolderCheckListItem(parent, album->title(), TQCheckListItem::CheckBox) +{ + setDragEnabled(true); + m_album = album; + m_count = 0; + + if (m_album) + m_album->setExtraData(listView(), this); +} + +void TAlbumCheckListItem::refresh() +{ + if (!m_album) return; + + if (AlbumSettings::instance()->getShowFolderTreeViewItemsCount() && + dynamic_cast(parent())) + { + if (isOpen()) + setText(0, TQString("%1 (%2)").arg(m_album->title()).arg(m_count)); + else + { + int countRecursive = m_count; + AlbumIterator it(m_album); + while ( it.current() ) + { + TAlbumCheckListItem *item = (TAlbumCheckListItem*)it.current()->extraData(listView()); + if (item) + countRecursive += item->count(); + ++it; + } + setText(0, TQString("%1 (%2)").arg(m_album->title()).arg(countRecursive)); + } + } + else + { + setText(0, m_album->title()); + } +} + +void TAlbumCheckListItem::stateChange(bool val) +{ + TQCheckListItem::stateChange(val); + ((TAlbumListView*)listView())->stateChanged(this); +} + +void TAlbumCheckListItem::setOpen(bool o) +{ + TQListViewItem::setOpen(o); + refresh(); +} + +TAlbum* TAlbumCheckListItem::album() const +{ + return m_album; +} + +int TAlbumCheckListItem::id() const +{ + return m_album ? m_album->id() : 0; +} + +void TAlbumCheckListItem::setCount(int count) +{ + m_count = count; + refresh(); +} + +int TAlbumCheckListItem::count() +{ + return m_count; +} + +void TAlbumCheckListItem::setStatus(MetadataHub::TagStatus status) +{ + if (status == MetadataHub::MetadataDisjoint) + { + if (type() != TQCheckListItem::RadioButtonController) setTristate(true); + setState(TQCheckListItem::NoChange); + } + else + { + if (type() != TQCheckListItem::RadioButtonController) setTristate(false); + setOn(status.hasTag); + } +} + +// ------------------------------------------------------------------------ + +TAlbumListView::TAlbumListView(TQWidget* parent) + : FolderView(parent, "TAlbumListView") +{ + addColumn(i18n("Tags")); + header()->hide(); + setResizeMode(TQListView::LastColumn); + setRootIsDecorated(true); + + setAcceptDrops(true); + viewport()->setAcceptDrops(true); + + connect(AlbumManager::instance(), TQ_SIGNAL(signalTAlbumsDirty(const TQMap&)), + this, TQ_SLOT(slotRefresh(const TQMap&))); +} + +TAlbumListView::~TAlbumListView() +{ + saveViewState(); +} + +void TAlbumListView::stateChanged(TAlbumCheckListItem *item) +{ + emit signalItemStateChanged(item); +} + +TQDragObject* TAlbumListView::dragObject() +{ + TAlbumCheckListItem *item = dynamic_cast(dragItem()); + if(!item) + return 0; + + if(!item->parent()) + return 0; + + TagDrag *t = new TagDrag(item->id(), this); + t->setPixmap(*item->pixmap(0)); + + return t; +} + +bool TAlbumListView::acceptDrop(const TQDropEvent *e) const +{ + TQPoint vp = contentsToViewport(e->pos()); + TAlbumCheckListItem *itemDrop = dynamic_cast(itemAt(vp)); + TAlbumCheckListItem *itemDrag = dynamic_cast(dragItem()); + + if(TagDrag::canDecode(e) || TagListDrag::canDecode(e)) + { + // Allow dragging at the root, to move the tag to the root + if(!itemDrop) + return true; + + // Dragging an item on itself makes no sense + if(itemDrag == itemDrop) + return false; + + // Dragging a parent on its child makes no sense + if(itemDrag && itemDrag->album()->isAncestorOf(itemDrop->album())) + return false; + + return true; + } + + if (ItemDrag::canDecode(e) && itemDrop && itemDrop->album()->parent()) + { + // Only other possibility is image items being dropped + // And allow this only if there is a Tag to be dropped + // on and also the Tag is not root. + return true; + } + + return false; +} + +void TAlbumListView::contentsDropEvent(TQDropEvent *e) +{ + TQListView::contentsDropEvent(e); + + if(!acceptDrop(e)) + return; + + TQPoint vp = contentsToViewport(e->pos()); + TAlbumCheckListItem *itemDrop = dynamic_cast(itemAt(vp)); + + if(TagDrag::canDecode(e)) + { + TQByteArray ba = e->encodedData("digikam/tag-id"); + TQDataStream ds(ba, IO_ReadOnly); + int tagID; + ds >> tagID; + + AlbumManager* man = AlbumManager::instance(); + TAlbum* talbum = man->findTAlbum(tagID); + + if(!talbum) + return; + + if (talbum == itemDrop->album()) + return; + + TDEPopupMenu popMenu(this); + popMenu.insertTitle(SmallIcon("digikam"), i18n("Tags")); + popMenu.insertItem(SmallIcon("goto"), i18n("&Move Here"), 10); + popMenu.insertSeparator(-1); + popMenu.insertItem(SmallIcon("cancel"), i18n("C&ancel"), 20); + popMenu.setMouseTracking(true); + int id = popMenu.exec(TQCursor::pos()); + + if(id == 10) + { + TAlbum *newParentTag = 0; + + if (!itemDrop) + { + // move dragItem to the root + newParentTag = AlbumManager::instance()->findTAlbum(0); + } + else + { + // move dragItem as child of dropItem + newParentTag = itemDrop->album(); + } + + TQString errMsg; + if (!AlbumManager::instance()->moveTAlbum(talbum, newParentTag, errMsg)) + { + KMessageBox::error(this, errMsg); + } + + if(itemDrop && !itemDrop->isOpen()) + itemDrop->setOpen(true); + } + + return; + } + + if (ItemDrag::canDecode(e)) + { + TAlbum *destAlbum = itemDrop->album(); + TAlbum *srcAlbum; + + KURL::List urls; + KURL::List kioURLs; + TQValueList albumIDs; + TQValueList imageIDs; + + if (!ItemDrag::decode(e, urls, kioURLs, albumIDs, imageIDs)) + return; + + if (urls.isEmpty() || kioURLs.isEmpty() || albumIDs.isEmpty() || imageIDs.isEmpty()) + return; + + // all the albumids will be the same + int albumID = albumIDs.first(); + srcAlbum = AlbumManager::instance()->findTAlbum(albumID); + if (!srcAlbum) + { + DWarning() << "Could not find source album of drag" + << endl; + return; + } + + int id = 0; + char keys_return[32]; + XQueryKeymap(x11Display(), keys_return); + int key_1 = XKeysymToKeycode(x11Display(), 0xFFE3); + int key_2 = XKeysymToKeycode(x11Display(), 0xFFE4); + + if(srcAlbum == destAlbum) + { + // Setting the dropped image as the album thumbnail + // If the ctrl key is pressed, when dropping the image, the + // thumbnail is set without a popup menu + if (((keys_return[key_1 / 8]) && (1 << (key_1 % 8))) || + ((keys_return[key_2 / 8]) && (1 << (key_2 % 8)))) + { + id = 12; + } + else + { + TDEPopupMenu popMenu(this); + popMenu.insertTitle(SmallIcon("digikam"), i18n("Tags")); + popMenu.insertItem(i18n("Set as Tag Thumbnail"), 12); + popMenu.insertSeparator(-1); + popMenu.insertItem( SmallIcon("cancel"), i18n("C&ancel") ); + + popMenu.setMouseTracking(true); + id = popMenu.exec(TQCursor::pos()); + } + + if(id == 12) + { + TQString errMsg; + AlbumManager::instance()->updateTAlbumIcon(destAlbum, TQString(), + imageIDs.first(), errMsg); + } + return; + } + + // If a ctrl key is pressed while dropping the drag object, + // the tag is assigned to the images without showing a + // popup menu. + if (((keys_return[key_1 / 8]) && (1 << (key_1 % 8))) || + ((keys_return[key_2 / 8]) && (1 << (key_2 % 8)))) + { + id = 10; + } + else + { + TDEPopupMenu popMenu(this); + popMenu.insertTitle(SmallIcon("digikam"), i18n("Tags")); + popMenu.insertItem( SmallIcon("tag"), i18n("Assign Tag '%1' to Items") + .arg(destAlbum->prettyURL()), 10) ; + popMenu.insertSeparator(-1); + popMenu.insertItem( SmallIcon("cancel"), i18n("C&ancel") ); + + popMenu.setMouseTracking(true); + id = popMenu.exec(TQCursor::pos()); + } + + if (id == 10) + { + emit signalProgressBarMode(StatusProgressBar::ProgressBarMode, + i18n("Assign tag to images. Please wait...")); + + AlbumLister::instance()->blockSignals(true); + AlbumManager::instance()->albumDB()->beginTransaction(); + int i=0; + for (TQValueList::const_iterator it = imageIDs.begin(); + it != imageIDs.end(); ++it) + { + // create temporary ImageInfo object + ImageInfo info(*it); + + MetadataHub hub; + hub.load(&info); + hub.setTag(destAlbum, true); + hub.write(&info, MetadataHub::PartialWrite); + hub.write(info.filePath(), MetadataHub::FullWriteIfChanged); + + emit signalProgressValue((int)((i++/(float)imageIDs.count())*100.0)); + kapp->processEvents(); + } + AlbumLister::instance()->blockSignals(false); + AlbumManager::instance()->albumDB()->commitTransaction(); + + ImageAttributesWatch::instance()->imagesChanged(destAlbum->id()); + + emit signalProgressBarMode(StatusProgressBar::TextMode, TQString()); + } + } +} + +void TAlbumListView::refresh() +{ + TQListViewItemIterator it(this); + + while (it.current()) + { + TAlbumCheckListItem* item = dynamic_cast(*it); + if (item) + item->refresh(); + ++it; + } +} + +void TAlbumListView::slotRefresh(const TQMap& tagsStatMap) +{ + TQListViewItemIterator it(this); + + while (it.current()) + { + TAlbumCheckListItem* item = dynamic_cast(*it); + if (item) + { + if (item->album()) + { + int id = item->id(); + TQMap::const_iterator it2 = tagsStatMap.find(id); + if ( it2 != tagsStatMap.end() ) + item->setCount(it2.data()); + } + } + ++it; + } + + refresh(); +} + +void TAlbumListView::loadViewState() +{ + TDEConfig *config = kapp->config(); + config->setGroup(name()); + + int selectedItem = config->readNumEntry("LastSelectedItem", 0); + + TQValueList openFolders; + if(config->hasKey("OpenFolders")) + { + openFolders = config->readIntListEntry("OpenFolders"); + } + + TAlbumCheckListItem *item = 0; + TAlbumCheckListItem *foundItem = 0; + TQListViewItemIterator it(this->lastItem()); + + for( ; it.current(); --it) + { + item = dynamic_cast(it.current()); + if(!item) + continue; + + // Start the album root always open + if(openFolders.contains(item->id()) || item->id() == 0) + setOpen(item, true); + else + setOpen(item, false); + + if(item->id() == selectedItem) + { + // Save the found selected item so that it can be made visible. + foundItem = item; + } + } + + // Important note: this cannot be done inside the previous loop + // because opening folders prevents the visibility. + // Fixes bug #144815. + // (Looks a bit like a bug in TQt to me ...) + if (foundItem) + { + setSelected(foundItem, true); + ensureItemVisible(foundItem); + } +} + +void TAlbumListView::saveViewState() +{ + TDEConfig *config = kapp->config(); + config->setGroup(name()); + + TAlbumCheckListItem *item = dynamic_cast(selectedItem()); + if(item) + config->writeEntry("LastSelectedItem", item->id()); + else + config->writeEntry("LastSelectedItem", 0); + + TQValueList openFolders; + TQListViewItemIterator it(this); + for( ; it.current(); ++it) + { + item = dynamic_cast(it.current()); + if(item && isOpen(item)) + openFolders.push_back(item->id()); + } + config->writeEntry("OpenFolders", openFolders); +} + +} // NameSpace Digikam diff --git a/src/libs/imageproperties/talbumlistview.h b/src/libs/imageproperties/talbumlistview.h new file mode 100644 index 00000000..b483706c --- /dev/null +++ b/src/libs/imageproperties/talbumlistview.h @@ -0,0 +1,109 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2006-18-12 + * Description : A list view to display digiKam Tags. + * + * Copyright (C) 2006-2009 by Gilles Caulier + * Copyright (C) 2009 by Andi Clemens + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef TALBUMLISTVIEW_H +#define TALBUMLISTVIEW_H + +// Local includes. + +#include "digikam_export.h" +#include "metadatahub.h" +#include "folderitem.h" +#include "folderview.h" + +class TQDropEvent; +class TQMouseEvent; + +namespace Digikam +{ +class TAlbum; + +class DIGIKAM_EXPORT TAlbumCheckListItem : public FolderCheckListItem +{ +public: + + TAlbumCheckListItem(TQListView* parent, TAlbum* album); + + TAlbumCheckListItem(TQCheckListItem* parent, TAlbum* album); + + void setStatus(MetadataHub::TagStatus status); + void refresh(); + void setOpen(bool o); + TAlbum* album() const; + int id() const; + void setCount(int count); + int count(); + +private : + + void stateChange(bool val); + +private : + + int m_count; + + TAlbum *m_album; +}; + +// ------------------------------------------------------------------------ + +class DIGIKAM_EXPORT TAlbumListView : public FolderView +{ + TQ_OBJECT + + +public: + + TAlbumListView(TQWidget* parent); + ~TAlbumListView(); + + void stateChanged(TAlbumCheckListItem *item); + void refresh(); + void loadViewState(); + +signals: + + void signalProgressBarMode(int, const TQString&); + void signalProgressValue(int); + void signalItemStateChanged(TAlbumCheckListItem *item); + +protected: + + bool acceptDrop(const TQDropEvent *e) const; + void contentsDropEvent(TQDropEvent *e); + + TQDragObject* dragObject(); + +private slots: + + void slotRefresh(const TQMap&); + +private: + + void saveViewState(); +}; + +} // NameSpace Digikam + +#endif // TALBUMLISTVIEW_H diff --git a/src/libs/jpegutils/Makefile.am b/src/libs/jpegutils/Makefile.am new file mode 100644 index 00000000..3c07aa5a --- /dev/null +++ b/src/libs/jpegutils/Makefile.am @@ -0,0 +1,22 @@ +METASOURCES = AUTO + +# --enable-final triggers: http://bugs.kde.org/show_bug.cgi?id=126326 +# digikam: camera download: auto-rotated images lose EXIF info ... +# So make sure nofinal is always used here! +KDE_OPTIONS = nofinal + +INCLUDES = $(all_includes) \ + -I$(top_srcdir)/src/libs/dmetadata \ + -I$(top_srcdir)/src/libs/dimg \ + -I$(top_srcdir)/src/digikam \ + $(LIBKDCRAW_CFLAGS) \ + $(LIBKEXIV2_CFLAGS) + + +noinst_LTLIBRARIES = libjpegutils.la + +libjpegutils_la_SOURCES = jpegutils.cpp transupp.cpp + +libjpegutils_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +libjpegutils_la_LIBADD = $(LIBJPEG) diff --git a/src/libs/jpegutils/jinclude.h b/src/libs/jpegutils/jinclude.h new file mode 100644 index 00000000..adee51e0 --- /dev/null +++ b/src/libs/jpegutils/jinclude.h @@ -0,0 +1,90 @@ +/* + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +/* Include auto-config file to find out which system include files we need. */ + +#include "jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/src/libs/jpegutils/jpegint.h b/src/libs/jpegutils/jpegint.h new file mode 100644 index 00000000..bf01aa3e --- /dev/null +++ b/src/libs/jpegutils/jpegint.h @@ -0,0 +1,811 @@ +#if JPEG_LIB_VERSION >= 80 + +/* + * jpegint.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 1997-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Ensuring definition INT32 */ +#ifndef INT32 +#define INT32 TQ_INT32 +#endif + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +typedef JMETHOD(void, forward_DCT_ptr, + (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); + +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* It is useful to allow each component to have a separate FDCT method. */ + forward_DCT_ptr forward_DCT[MAX_COMPONENTS]; +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); + /* These routines are exported to allow insertion of extra markers */ + /* Probably only COM and APPn markers should be written this way */ + JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker, + unsigned int datalen)); + JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val)); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_arith_encoder jIAEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_arith_decoder jIADecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#define jpeg_natural_order7 jZAGTable7 +#define jpeg_natural_order6 jZAGTable6 +#define jpeg_natural_order5 jZAGTable5 +#define jpeg_natural_order4 jZAGTable4 +#define jpeg_natural_order3 jZAGTable3 +#define jpeg_natural_order2 jZAGTable2 +#define jpeg_aritab jAriTab +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Compression module initialization routines */ +EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_arith_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_arith_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN(long) jdiv_round_up JPP((long a, long b)); +EXTERN(long) jround_up JPP((long a, long b)); +EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +#if 0 /* This table is not actually needed in v6a */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +#endif +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ +extern const int jpeg_natural_order7[]; /* zz to natural order for 7x7 block */ +extern const int jpeg_natural_order6[]; /* zz to natural order for 6x6 block */ +extern const int jpeg_natural_order5[]; /* zz to natural order for 5x5 block */ +extern const int jpeg_natural_order4[]; /* zz to natural order for 4x4 block */ +extern const int jpeg_natural_order3[]; /* zz to natural order for 3x3 block */ +extern const int jpeg_natural_order2[]; /* zz to natural order for 2x2 block */ + +/* Arithmetic coding probability estimation tables in jaricom.c */ +extern const INT32 jpeg_aritab[]; + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ + + +#else // JPEG_LIB_VERSION >= 80 + +/* + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* perhaps this should be an array??? */ + JMETHOD(void, forward_DCT, (j_compress_ptr cinfo, + jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); + /* These routines are exported to allow insertion of extra markers */ + /* Probably only COM and APPn markers should be written this way */ + JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker, + unsigned int datalen)); + JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val)); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); + + /* This is here to share code between baseline and progressive decoders; */ + /* other modules probably should not use it */ + boolean insufficient_data; /* set TRUE after emitting warning */ +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_phuff_encoder jIPHEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_phuff_decoder jIPHDecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Compression module initialization routines */ +EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_phuff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_phuff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN(long) jdiv_round_up JPP((long a, long b)); +EXTERN(long) jround_up JPP((long a, long b)); +EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +#if 0 /* This table is not actually needed in v6a */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +#endif +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ + +#endif // JPEG_LIB_VERSION >= 80 diff --git a/src/libs/jpegutils/jpegutils.cpp b/src/libs/jpegutils/jpegutils.cpp new file mode 100644 index 00000000..2f2aabb6 --- /dev/null +++ b/src/libs/jpegutils/jpegutils.cpp @@ -0,0 +1,532 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-09-29 + * Description : perform lossless rotation/flip to JPEG file + * + * Copyright (C) 2004-2005 by Renchi Raju + * Copyright (C) 2006-2009 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#define XMD_H + +// C++ includes. + +#include +#include + +// C Ansi includes. + +extern "C" +{ +#include +#include +#include +#include +#include +#include +} + +// TQt includes. + +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "dmetadata.h" +#include "transupp.h" +#include "jpegutils.h" + +namespace Digikam +{ + +// To manage Errors/Warnings handling provide by libjpeg + +//#define ENABLE_DEBUG_MESSAGES + +struct jpegutils_jpeg_error_mgr : public jpeg_error_mgr +{ + jmp_buf setjmp_buffer; +}; + +static void jpegutils_jpeg_error_exit(j_common_ptr cinfo); +static void jpegutils_jpeg_emit_message(j_common_ptr cinfo, int msg_level); +static void jpegutils_jpeg_output_message(j_common_ptr cinfo); + +static void jpegutils_jpeg_error_exit(j_common_ptr cinfo) +{ + jpegutils_jpeg_error_mgr* myerr = (jpegutils_jpeg_error_mgr*) cinfo->err; + + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << endl; +#endif + + longjmp(myerr->setjmp_buffer, 1); +} + +static void jpegutils_jpeg_emit_message(j_common_ptr cinfo, int msg_level) +{ + Q_UNUSED(msg_level) + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << " (" << msg_level << ")" << endl; +#endif +} + +static void jpegutils_jpeg_output_message(j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << endl; +#endif +} + +bool loadJPEGScaled(TQImage& image, const TQString& path, int maximumSize) +{ + TQString format = TQImageIO::imageFormat(path); + if (format !="JPEG") return false; + + FILE* inputFile=fopen(TQFile::encodeName(path), "rb"); + if(!inputFile) + return false; + + struct jpeg_decompress_struct cinfo; + struct jpegutils_jpeg_error_mgr jerr; + + // JPEG error handling - thanks to Marcus Meissner + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = jpegutils_jpeg_error_exit; + cinfo.err->emit_message = jpegutils_jpeg_emit_message; + cinfo.err->output_message = jpegutils_jpeg_output_message; + + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_decompress(&cinfo); + fclose(inputFile); + return false; + } + + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, inputFile); + jpeg_read_header(&cinfo, true); + + int imgSize = TQMAX(cinfo.image_width, cinfo.image_height); + + // libjpeg supports 1/1, 1/2, 1/4, 1/8 + int scale=1; + while(maximumSize*scale*2<=imgSize) + { + scale*=2; + } + if(scale>8) scale=8; + + cinfo.scale_num=1; + cinfo.scale_denom=scale; + + switch (cinfo.jpeg_color_space) + { + case JCS_UNKNOWN: + break; + case JCS_GRAYSCALE: + case JCS_RGB: + case JCS_YCbCr: + cinfo.out_color_space = JCS_RGB; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo.out_color_space = JCS_CMYK; + break; + } + + jpeg_start_decompress(&cinfo); + + TQImage img; + + // We only take RGB with 1 or 3 components, or CMYK with 4 components + if (!( + (cinfo.out_color_space == JCS_RGB && (cinfo.output_components == 3 || cinfo.output_components == 1)) + || (cinfo.out_color_space == JCS_CMYK && cinfo.output_components == 4) + )) + { + jpeg_destroy_decompress(&cinfo); + fclose(inputFile); + return false; + } + + switch(cinfo.output_components) + { + case 3: + case 4: + img.create( cinfo.output_width, cinfo.output_height, 32 ); + break; + case 1: // B&W image + img.create( cinfo.output_width, cinfo.output_height, 8, 256 ); + for (int i = 0 ; i < 256 ; i++) + img.setColor(i, tqRgb(i, i, i)); + break; + } + + uchar** lines = img.jumpTable(); + while (cinfo.output_scanline < cinfo.output_height) + jpeg_read_scanlines(&cinfo, lines + cinfo.output_scanline, cinfo.output_height); + + jpeg_finish_decompress(&cinfo); + + // Expand 24->32 bpp + if ( cinfo.output_components == 3 ) + { + for (uint j=0; jerror_exit = jpegutils_jpeg_error_exit; + srcinfo.err->emit_message = jpegutils_jpeg_emit_message; + srcinfo.err->output_message = jpegutils_jpeg_output_message; + + // Initialize the JPEG compression object with default error handling + dstinfo.err = jpeg_std_error(&jdsterr); + dstinfo.err->error_exit = jpegutils_jpeg_error_exit; + dstinfo.err->emit_message = jpegutils_jpeg_emit_message; + dstinfo.err->output_message = jpegutils_jpeg_output_message; + + FILE *input_file; + FILE *output_file; + + input_file = fopen(in, "rb"); + if (!input_file) + { + DWarning() << "ExifRotate: Error in opening input file: " << input_file << endl; + return false; + } + + output_file = fopen(out, "wb"); + if (!output_file) + { + fclose(input_file); + DWarning() << "ExifRotate: Error in opening output file: " << output_file << endl; + return false; + } + + if (setjmp(jsrcerr.setjmp_buffer) || setjmp(jdsterr.setjmp_buffer)) + { + jpeg_destroy_decompress(&srcinfo); + jpeg_destroy_compress(&dstinfo); + fclose(input_file); + fclose(output_file); + return false; + } + + jpeg_create_decompress(&srcinfo); + jpeg_create_compress(&dstinfo); + + jpeg_stdio_src(&srcinfo, input_file); + jcopy_markers_setup(&srcinfo, copyoption); + + (void) jpeg_read_header(&srcinfo, true); + + jtransform_request_workspace(&srcinfo, &transformoption); + + // Read source file as DCT coefficients + src_coef_arrays = jpeg_read_coefficients(&srcinfo); + + // Initialize destination compression parameters from source values + jpeg_copy_critical_parameters(&srcinfo, &dstinfo); + + dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo, + src_coef_arrays, &transformoption); + + // Specify data destination for compression + jpeg_stdio_dest(&dstinfo, output_file); + + // Start compressor (note no image data is actually written here) + jpeg_write_coefficients(&dstinfo, dst_coef_arrays); + + // Copy to the output file any extra markers that we want to preserve + jcopy_markers_execute(&srcinfo, &dstinfo, copyoption); + + jtransform_execute_transformation(&srcinfo, &dstinfo, + src_coef_arrays, &transformoption); + + // Finish compression and release memory + jpeg_finish_compress(&dstinfo); + jpeg_destroy_compress(&dstinfo); + (void) jpeg_finish_decompress(&srcinfo); + jpeg_destroy_decompress(&srcinfo); + + fclose(input_file); + fclose(output_file); + + // -- Metadata operations ------------------------------------------------------ + + // Reset the Exif orientation tag of the temp image to normal + DDebug() << "ExifRotate: set Orientation tag to normal: " << file << endl; + + metaData.load(temp); + metaData.setImageOrientation(DMetadata::ORIENTATION_NORMAL); + TQImage img(temp); + + // Get the new image dimension of the temp image. Using a dummy TQImage objet here + // has a sense because the Exif dimension information can be missing from original image. + // Get new dimensions with TQImage will always work... + metaData.setImageDimensions(img.size()); + + // Update the image thumbnail. + TQImage thumb = img.scale(160, 120, TQImage::ScaleMin); + metaData.setExifThumbnail(thumb); + + // Update Exif Document Name tag (the orinal file name from camera for example). + metaData.setExifTagString("Exif.Image.DocumentName", documentName); + + // We update all new metadata now... + metaData.applyChanges(); + + // ----------------------------------------------------------------------------- + // set the file modification time of the temp file to that + // of the original file + struct stat st; + stat(in, &st); + + struct utimbuf ut; + ut.modtime = st.st_mtime; + ut.actime = st.st_atime; + + utime(out, &ut); + + // now overwrite the original file + if (rename(out, in) == 0) + { + return true; + } + else + { + // moving failed. unlink the temp file + unlink(out); + return false; + } + } + + // Not a jpeg image. + DDebug() << "ExifRotate: not a JPEG file: " << file << endl; + return false; +} + +bool jpegConvert(const TQString& src, const TQString& dest, const TQString& documentName, const TQString& format) +{ + TQFileInfo fi(src); + if (!fi.exists()) + { + DDebug() << "JpegConvert: file do not exist: " << src << endl; + return false; + } + + if (isJpegImage(src)) + { + DImg image(src); + + // Get image Exif/Iptc data. + DMetadata meta; + meta.setExif(image.getExif()); + meta.setIptc(image.getIptc()); + + // Update Iptc preview. + TQImage preview = image.smoothScale(1280, 1024, TQSize::ScaleMin).copyTQImage(); + + // TODO: see B.K.O #130525. a JPEG segment is limited to 64K. If the IPTC byte array is + // bigger than 64K duing of image preview tag size, the target JPEG image will be + // broken. Note that IPTC image preview tag is limited to 256K!!! + // Temp. solution to disable IPTC preview record in JPEG file until a right solution + // will be found into Exiv2. + // Note : There is no limitation with TIFF and PNG about IPTC byte array size. + + if (format.upper() != TQString("JPG") && format.upper() != TQString("JPEG") && + format.upper() != TQString("JPE")) + meta.setImagePreview(preview); + + // Update Exif thumbnail. + TQImage thumb = preview.smoothScale(160, 120, TQImage::ScaleMin); + meta.setExifThumbnail(thumb); + + // Update Exif Document Name tag (the orinal file name from camera for example). + meta.setExifTagString("Exif.Image.DocumentName", documentName); + + // Store new Exif/Iptc data into image. + image.setExif(meta.getExif()); + image.setIptc(meta.getIptc()); + + // And now save the image to a new file format. + + if ( format.upper() == TQString("PNG") ) + image.setAttribute("quality", 9); + + if ( format.upper() == TQString("TIFF") || format.upper() == TQString("TIF") ) + image.setAttribute("compress", true); + + return (image.save(dest, format)); + } + + return false; +} + +bool isJpegImage(const TQString& file) +{ + // Check if the file is an JPEG image + TQString format = TQString(TQImage::imageFormat(file)).upper(); + DDebug() << "mimetype = " << format << endl; + if (format !="JPEG") return false; + + return true; +} + +} // Namespace Digikam diff --git a/src/libs/jpegutils/jpegutils.h b/src/libs/jpegutils/jpegutils.h new file mode 100644 index 00000000..ad384770 --- /dev/null +++ b/src/libs/jpegutils/jpegutils.h @@ -0,0 +1,44 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-09-29 + * Description : perform lossless rotation/flip to JPEG file + * + * Copyright (C) 2004-2005 by Renchi Raju + * Copyright (C) 2006-2009 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef JPEGUTILS_H +#define JPEGUTILS_H + +// TQt includes. + +#include +#include + +namespace Digikam +{ + +bool loadJPEGScaled(TQImage& image, const TQString& path, int maximumSize); +bool exifRotate(const TQString& file, const TQString& documentName); +bool jpegConvert(const TQString& src, const TQString& dest, const TQString& documentName, + const TQString& format=TQString("PNG")); +bool isJpegImage(const TQString& file); + +} + +#endif /* JPEGUTILS_H */ diff --git a/src/libs/jpegutils/libjpeg62.README b/src/libs/jpegutils/libjpeg62.README new file mode 100644 index 00000000..a18979ac --- /dev/null +++ b/src/libs/jpegutils/libjpeg62.README @@ -0,0 +1,385 @@ +The Independent JPEG Group's JPEG software +========================================== + +README for release 6b of 27-Mar-1998 +==================================== + +This distribution contains the sixth public release of the Independent JPEG +Group's free JPEG software. You are welcome to redistribute this software and +to use it for any purpose, subject to the conditions under LEGAL ISSUES, below. + +Serious users of this software (particularly those incorporating it into +larger programs) should contact IJG at jpeg-info@uunet.uu.net to be added to +our electronic mailing list. Mailing list members are notified of updates +and have a chance to participate in technical discussions, etc. + +This software is the work of Tom Lane, Philip Gladstone, Jim Boucher, +Lee Crocker, Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi, +Guido Vollbeding, Ge' Weijers, and other members of the Independent JPEG +Group. + +IJG is not affiliated with the official ISO JPEG standards committee. + + +DOCUMENTATION ROADMAP +===================== + +This file contains the following sections: + +OVERVIEW General description of JPEG and the IJG software. +LEGAL ISSUES Copyright, lack of warranty, terms of distribution. +REFERENCES Where to learn more about JPEG. +ARCHIVE LOCATIONS Where to find newer versions of this software. +RELATED SOFTWARE Other stuff you should get. +FILE FORMAT WARS Software *not* to get. +TO DO Plans for future IJG releases. + +Other documentation files in the distribution are: + +User documentation: + install.doc How to configure and install the IJG software. + usage.doc Usage instructions for cjpeg, djpeg, jpegtran, + rdjpgcom, and wrjpgcom. + *.1 Unix-style man pages for programs (same info as usage.doc). + wizard.doc Advanced usage instructions for JPEG wizards only. + change.log Version-to-version change highlights. +Programmer and internal documentation: + libjpeg.doc How to use the JPEG library in your own programs. + example.c Sample code for calling the JPEG library. + structure.doc Overview of the JPEG library's internal structure. + filelist.doc Road map of IJG files. + coderules.doc Coding style rules --- please read if you contribute code. + +Please read at least the files install.doc and usage.doc. Useful information +can also be found in the JPEG FAQ (Frequently Asked Questions) article. See +ARCHIVE LOCATIONS below to find out where to obtain the FAQ article. + +If you want to understand how the JPEG code works, we suggest reading one or +more of the REFERENCES, then looking at the documentation files (in roughly +the order listed) before diving into the code. + + +OVERVIEW +======== + +This package contains C software to implement JPEG image compression and +decompression. JPEG (pronounced "jay-peg") is a standardized compression +method for full-color and gray-scale images. JPEG is intended for compressing +"real-world" scenes; line drawings, cartoons and other non-realistic images +are not its strong suit. JPEG is lossy, meaning that the output image is not +exactly identical to the input image. Hence you must not use JPEG if you +have to have identical output bits. However, on typical photographic images, +very good compression levels can be obtained with no visible change, and +remarkably high compression levels are possible if you can tolerate a +low-quality image. For more details, see the references, or just experiment +with various compression settings. + +This software implements JPEG baseline, extended-sequential, and progressive +compression processes. Provision is made for supporting all variants of these +processes, although some uncommon parameter settings aren't implemented yet. +For legal reasons, we are not distributing code for the arithmetic-coding +variants of JPEG; see LEGAL ISSUES. We have made no provision for supporting +the hierarchical or lossless processes defined in the standard. + +We provide a set of library routines for reading and writing JPEG image files, +plus two sample applications "cjpeg" and "djpeg", which use the library to +perform conversion between JPEG and some other popular image file formats. +The library is intended to be reused in other applications. + +In order to support file conversion and viewing software, we have included +considerable functionality beyond the bare JPEG coding/decoding capability; +for example, the color quantization modules are not strictly part of JPEG +decoding, but they are essential for output to colormapped file formats or +colormapped displays. These extra functions can be compiled out of the +library if not required for a particular application. We have also included +"jpegtran", a utility for lossless transcoding between different JPEG +processes, and "rdjpgcom" and "wrjpgcom", two simple applications for +inserting and extracting textual comments in JFIF files. + +The emphasis in designing this software has been on achieving portability and +flexibility, while also making it fast enough to be useful. In particular, +the software is not intended to be read as a tutorial on JPEG. (See the +REFERENCES section for introductory material.) Rather, it is intended to +be reliable, portable, industrial-strength code. We do not claim to have +achieved that goal in every aspect of the software, but we strive for it. + +We welcome the use of this software as a component of commercial products. +No royalty is required, but we do ask for an acknowledgement in product +documentation, as described under LEGAL ISSUES. + + +LEGAL ISSUES +============ + +In plain English: + +1. We don't promise that this software works. (But if you find any bugs, + please let us know!) +2. You can use this software for whatever you want. You don't have to pay us. +3. You may not pretend that you wrote this software. If you use it in a + program, you must acknowledge somewhere in your documentation that + you've used the IJG code. + +In legalese: + +The authors make NO WARRANTY or representation, either express or implied, +with respect to this software, its quality, accuracy, merchantability, or +fitness for a particular purpose. This software is provided "AS IS", and you, +its user, assume the entire risk as to its quality and accuracy. + +This software is copyright (C) 1991-1998, Thomas G. Lane. +All Rights Reserved except as specified below. + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +Permission is NOT granted for the use of any IJG author's name or company name +in advertising or publicity relating to this software or products derived from +it. This software may be referred to only as "the Independent JPEG Group's +software". + +We specifically permit and encourage the use of this software as the basis of +commercial products, provided that all warranty or liability claims are +assumed by the product vendor. + + +ansi2knr.c is included in this distribution by permission of L. Peter Deutsch, +sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA. +ansi2knr.c is NOT covered by the above copyright and conditions, but instead +by the usual distribution terms of the Free Software Foundation; principally, +that you must include source code if you redistribute it. (See the file +ansi2knr.c for full details.) However, since ansi2knr.c is not needed as part +of any program generated from the IJG code, this does not limit you more than +the foregoing paragraphs do. + +The Unix configuration script "configure" was produced with GNU Autoconf. +It is copyright by the Free Software Foundation but is freely distributable. +The same holds for its supporting scripts (config.guess, config.sub, +ltconfig, ltmain.sh). Another support script, install-sh, is copyright +by M.I.T. but is also freely distributable. + +It appears that the arithmetic coding option of the JPEG spec is covered by +patents owned by IBM, AT&T, and Mitsubishi. Hence arithmetic coding cannot +legally be used without obtaining one or more licenses. For this reason, +support for arithmetic coding has been removed from the free JPEG software. +(Since arithmetic coding provides only a marginal gain over the unpatented +Huffman mode, it is unlikely that very many implementations will support it.) +So far as we are aware, there are no patent restrictions on the remaining +code. + +The IJG distribution formerly included code to read and write GIF files. +To avoid entanglement with the Unisys LZW patent, GIF reading support has +been removed altogether, and the GIF writer has been simplified to produce +"uncompressed GIFs". This technique does not use the LZW algorithm; the +resulting GIF files are larger than usual, but are readable by all standard +GIF decoders. + +We are required to state that + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + + +REFERENCES +========== + +We highly recommend reading one or more of these references before trying to +understand the innards of the JPEG software. + +The best short technical introduction to the JPEG compression algorithm is + Wallace, Gregory K. "The JPEG Still Image Compression Standard", + Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44. +(Adjacent articles in that issue discuss MPEG motion picture compression, +applications of JPEG, and related topics.) If you don't have the CACM issue +handy, a PostScript file containing a revised version of Wallace's article is +available at ftp://ftp.uu.net/graphics/jpeg/wallace.ps.gz. The file (actually +a preprint for an article that appeared in IEEE Trans. Consumer Electronics) +omits the sample images that appeared in CACM, but it includes corrections +and some added material. Note: the Wallace article is copyright ACM and IEEE, +and it may not be used for commercial purposes. + +A somewhat less technical, more leisurely introduction to JPEG can be found in +"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by +M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides +good explanations and example C code for a multitude of compression methods +including JPEG. It is an excellent source if you are comfortable reading C +code but don't know much about data compression in general. The book's JPEG +sample code is far from industrial-strength, but when you are ready to look +at a full implementation, you've got one here... + +The best full description of JPEG is the textbook "JPEG Still Image Data +Compression Standard" by William B. Pennebaker and Joan L. Mitchell, published +by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1. Price US$59.95, 638 pp. +The book includes the complete text of the ISO JPEG standards (DIS 10918-1 +and draft DIS 10918-2). This is by far the most complete exposition of JPEG +in existence, and we highly recommend it. + +The JPEG standard itself is not available electronically; you must order a +paper copy through ISO or ITU. (Unless you feel a need to own a certified +official copy, we recommend buying the Pennebaker and Mitchell book instead; +it's much cheaper and includes a great deal of useful explanatory material.) +In the USA, copies of the standard may be ordered from ANSI Sales at (212) +642-4900, or from Global Engineering Documents at (800) 854-7179. (ANSI +doesn't take credit card orders, but Global does.) It's not cheap: as of +1992, ANSI was charging $95 for Part 1 and $47 for Part 2, plus 7% +shipping/handling. The standard is divided into two parts, Part 1 being the +actual specification, while Part 2 covers compliance testing methods. Part 1 +is titled "Digital Compression and Coding of Continuous-tone Still Images, +Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS +10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of +Continuous-tone Still Images, Part 2: Compliance testing" and has document +numbers ISO/IEC IS 10918-2, ITU-T T.83. + +Some extensions to the original JPEG standard are defined in JPEG Part 3, +a newer ISO standard numbered ISO/IEC IS 10918-3 and ITU-T T.84. IJG +currently does not support any Part 3 extensions. + +The JPEG standard does not specify all details of an interchangeable file +format. For the omitted details we follow the "JFIF" conventions, revision +1.02. A copy of the JFIF spec is available from: + Literature Department + C-Cube Microsystems, Inc. + 1778 McCarthy Blvd. + Milpitas, CA 95035 + phone (408) 944-6300, fax (408) 944-6314 +A PostScript version of this document is available by FTP at +ftp://ftp.uu.net/graphics/jpeg/jfif.ps.gz. There is also a plain text +version at ftp://ftp.uu.net/graphics/jpeg/jfif.txt.gz, but it is missing +the figures. + +The TIFF 6.0 file format specification can be obtained by FTP from +ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation scheme +found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems. +IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6). +Instead, we recommend the JPEG design proposed by TIFF Technical Note #2 +(Compression tag 7). Copies of this Note can be obtained from ftp.sgi.com or +from ftp://ftp.uu.net/graphics/jpeg/. It is expected that the next revision +of the TIFF spec will replace the 6.0 JPEG design with the Note's design. +Although IJG's own code does not support TIFF/JPEG, the free libtiff library +uses our library to implement TIFF/JPEG per the Note. libtiff is available +from ftp://ftp.sgi.com/graphics/tiff/. + + +ARCHIVE LOCATIONS +================= + +The "official" archive site for this software is ftp.uu.net (Internet +address 192.48.96.9). The most recent released version can always be found +there in directory graphics/jpeg. This particular version will be archived +as ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz. If you don't have +direct Internet access, UUNET's archives are also available via UUCP; contact +help@uunet.uu.net for information on retrieving files that way. + +Numerous Internet sites maintain copies of the UUNET files. However, only +ftp.uu.net is guaranteed to have the latest official version. + +You can also obtain this software in DOS-compatible "zip" archive format from +the SimTel archives (ftp://ftp.simtel.net/pub/simtelnet/msdos/graphics/), or +on CompuServe in the Graphics Support forum (GO CIS:GRAPHSUP), library 12 +"JPEG Tools". Again, these versions may sometimes lag behind the ftp.uu.net +release. + +The JPEG FAQ (Frequently Asked Questions) article is a useful source of +general information about JPEG. It is updated constantly and therefore is +not included in this distribution. The FAQ is posted every two weeks to +Usenet newsgroups comp.graphics.misc, news.answers, and other groups. +It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/ +and other news.answers archive sites, including the official news.answers +archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/. +If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu +with body + send usenet/news.answers/jpeg-faq/part1 + send usenet/news.answers/jpeg-faq/part2 + + +RELATED SOFTWARE +================ + +Numerous viewing and image manipulation programs now support JPEG. (Quite a +few of them use this library to do so.) The JPEG FAQ described above lists +some of the more popular free and shareware viewers, and tells where to +obtain them on Internet. + +If you are on a Unix machine, we highly recommend Jef Poskanzer's free +PBMPLUS software, which provides many useful operations on PPM-format image +files. In particular, it can convert PPM images to and from a wide range of +other formats, thus making cjpeg/djpeg considerably more useful. The latest +version is distributed by the NetPBM group, and is available from numerous +sites, notably ftp://wuarchive.wustl.edu/graphics/graphics/packages/NetPBM/. +Unfortunately PBMPLUS/NETPBM is not nearly as portable as the IJG software is; +you are likely to have difficulty making it work on any non-Unix machine. + +A different free JPEG implementation, written by the PVRG group at Stanford, +is available from ftp://havefun.stanford.edu/pub/jpeg/. This program +is designed for research and experimentation rather than production use; +it is slower, harder to use, and less portable than the IJG code, but it +is easier to read and modify. Also, the PVRG code supports lossless JPEG, +which we do not. (On the other hand, it doesn't do progressive JPEG.) + + +FILE FORMAT WARS +================ + +Some JPEG programs produce files that are not compatible with our library. +The root of the problem is that the ISO JPEG committee failed to specify a +concrete file format. Some vendors "filled in the blanks" on their own, +creating proprietary formats that no one else could read. (For example, none +of the early commercial JPEG implementations for the Macintosh were able to +exchange compressed files.) + +The file format we have adopted is called JFIF (see REFERENCES). This format +has been agreed to by a number of major commercial JPEG vendors, and it has +become the de facto standard. JFIF is a minimal or "low end" representation. +We recommend the use of TIFF/JPEG (TIFF revision 6.0 as modified by TIFF +Technical Note #2) for "high end" applications that need to record a lot of +additional data about an image. TIFF/JPEG is fairly new and not yet widely +supported, unfortunately. + +The upcoming JPEG Part 3 standard defines a file format called SPIFF. +SPIFF is interoperable with JFIF, in the sense that most JFIF decoders should +be able to read the most common variant of SPIFF. SPIFF has some technical +advantages over JFIF, but its major claim to fame is simply that it is an +official standard rather than an informal one. At this point it is unclear +whether SPIFF will supersede JFIF or whether JFIF will remain the de-facto +standard. IJG intends to support SPIFF once the standard is frozen, but we +have not decided whether it should become our default output format or not. +(In any case, our decoder will remain capable of reading JFIF indefinitely.) + +Various proprietary file formats incorporating JPEG compression also exist. +We have little or no sympathy for the existence of these formats. Indeed, +one of the original reasons for developing this free software was to help +force convergence on common, open format standards for JPEG files. Don't +use a proprietary file format! + + +TO DO +===== + +The major thrust for v7 will probably be improvement of visual quality. +The current method for scaling the quantization tables is known not to be +very good at low Q values. We also intend to investigate block boundary +smoothing, "poor man's variable quantization", and other means of improving +quality-vs-file-size performance without sacrificing compatibility. + +In future versions, we are considering supporting some of the upcoming JPEG +Part 3 extensions --- principally, variable quantization and the SPIFF file +format. + +As always, speeding things up is of great interest. + +Please send bug reports, offers of help, etc. to jpeg-info@uunet.uu.net. diff --git a/src/libs/jpegutils/transupp.cpp b/src/libs/jpegutils/transupp.cpp new file mode 100644 index 00000000..47a9aa8b --- /dev/null +++ b/src/libs/jpegutils/transupp.cpp @@ -0,0 +1,2527 @@ +/* Although this file really shouldn't have access to the library internals, + * it's helpful to let it call jround_up() and jcopy_block_row(). + */ +#define JPEG_INTERNALS + +// LibJPEG includes. + +extern "C" +{ +#include "jinclude.h" +#include "jpeglib.h" +} + +#if JPEG_LIB_VERSION >= 80 + +/* + * transupp.c + * + * Copyright (C) 1997-2009, Thomas G. Lane, Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains image transformation routines and other utility code + * used by the jpegtran sample application. These are NOT part of the core + * JPEG library. But we keep these routines separate from jpegtran.c to + * ease the task of maintaining jpegtran-like programs that have other user + * interfaces. + */ + +#include "transupp.h" /* My own external interface */ +#include /* to declare isdigit() */ + +namespace Digikam +{ + +#if TRANSFORMS_SUPPORTED + +/* + * Lossless image transformation routines. These routines work on DCT + * coefficient arrays and thus do not require any lossy decompression + * or recompression of the image. + * Thanks to Guido Vollbeding for the initial design and code of this feature, + * and to Ben Jackson for introducing the cropping feature. + * + * Horizontal flipping is done in-place, using a single top-to-bottom + * pass through the virtual source array. It will thus be much the + * fastest option for images larger than main memory. + * + * The other routines require a set of destination virtual arrays, so they + * need twice as much memory as jpegtran normally does. The destination + * arrays are always written in normal scan order (top to bottom) because + * the virtual array manager expects this. The source arrays will be scanned + * in the corresponding order, which means multiple passes through the source + * arrays for most of the transforms. That could result in much thrashing + * if the image is larger than main memory. + * + * If cropping or trimming is involved, the destination arrays may be smaller + * than the source arrays. Note it is not possible to do horizontal flip + * in-place when a nonzero Y crop offset is specified, since we'd have to move + * data from one block row to another but the virtual array manager doesn't + * guarantee we can touch more than one row at a time. So in that case, + * we have to use a separate destination array. + * + * Some notes about the operating environment of the individual transform + * routines: + * 1. Both the source and destination virtual arrays are allocated from the + * source JPEG object, and therefore should be manipulated by calling the + * source's memory manager. + * 2. The destination's component count should be used. It may be smaller + * than the source's when forcing to grayscale. + * 3. Likewise the destination's sampling factors should be used. When + * forcing to grayscale the destination's sampling factors will be all 1, + * and we may as well take that as the effective iMCU size. + * 4. When "trim" is in effect, the destination's dimensions will be the + * trimmed values but the source's will be untrimmed. + * 5. When "crop" is in effect, the destination's dimensions will be the + * cropped values but the source's will be uncropped. Each transform + * routine is responsible for picking up source data starting at the + * correct X and Y offset for the crop region. (The X and Y offsets + * passed to the transform routines are measured in iMCU blocks of the + * destination.) + * 6. All the routines assume that the source and destination buffers are + * padded out to a full iMCU boundary. This is true, although for the + * source buffer it is an undocumented property of jdcoefct.c. + */ + + +LOCAL(void) +do_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Crop. This is only used when no rotate/flip is requested with the crop. */ +{ + JDIMENSION dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + jpeg_component_info *compptr; + + /* We simply have to copy the right amount of data (the destination's + * image size) starting at the given X and Y offsets in the source. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, + dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } +} + + +LOCAL(void) +do_flip_h_no_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, + jvirt_barray_ptr *src_coef_arrays) +/* Horizontal flip; done in-place, so no separate dest array is required. + * NB: this only works when y_crop_offset is zero. + */ +{ + JDIMENSION MCU_cols, comp_width, blk_x, blk_y, x_crop_blocks; + int ci, k, offset_y; + JBLOCKARRAY buffer; + JCOEFPTR ptr1, ptr2; + JCOEF temp1, temp2; + jpeg_component_info *compptr; + + /* Horizontal mirroring of DCT blocks is accomplished by swapping + * pairs of blocks in-place. Within a DCT block, we perform horizontal + * mirroring by changing the signs of odd-numbered columns. + * Partial iMCUs at the right edge are left untouched. + */ + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + for (blk_y = 0; blk_y < compptr->height_in_blocks; + blk_y += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + /* Do the mirroring */ + for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) { + ptr1 = buffer[offset_y][blk_x]; + ptr2 = buffer[offset_y][comp_width - blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + temp1 = *ptr1; /* swap even column */ + temp2 = *ptr2; + *ptr1++ = temp2; + *ptr2++ = temp1; + temp1 = *ptr1; /* swap odd column with sign change */ + temp2 = *ptr2; + *ptr1++ = -temp2; + *ptr2++ = -temp1; + } + } + if (x_crop_blocks > 0) { + /* Now left-justify the portion of the data to be kept. + * We can't use a single jcopy_block_row() call because that routine + * depends on memcpy(), whose behavior is unspecified for overlapping + * source and destination areas. Sigh. + */ + for (blk_x = 0; blk_x < compptr->width_in_blocks; blk_x++) { + jcopy_block_row(buffer[offset_y] + blk_x + x_crop_blocks, + buffer[offset_y] + blk_x, + (JDIMENSION) 1); + } + } + } + } + } +} + + +LOCAL(void) +do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Horizontal flip in general cropping case */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, k, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Here we must output into a separate array because we can't touch + * different rows of a single virtual array simultaneously. Otherwise, + * this is essentially the same as the routine above. + */ + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[offset_y]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Do the mirrorable blocks */ + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + *dst_ptr++ = *src_ptr++; /* copy even column */ + *dst_ptr++ = - *src_ptr++; /* copy odd column with sign change */ + } + } else { + /* Copy last partial block(s) verbatim */ + jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks, + dst_row_ptr + dst_blk_x, + (JDIMENSION) 1); + } + } + } + } + } +} + + +LOCAL(void) +do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Vertical flip */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* We output into a separate array because we can't touch different + * rows of the source virtual array simultaneously. Otherwise, this + * is a pretty straightforward analog of horizontal flip. + * Within a DCT block, vertical mirroring is done by changing the signs + * of odd-numbered rows. + * Partial iMCUs at the bottom edge are copied verbatim. + */ + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - y_crop_blocks - dst_blk_y - + (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge blocks will be copied verbatim. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + src_row_ptr += x_crop_blocks; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + /* copy even row */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + /* copy odd row with sign change */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Just copy row verbatim. */ + jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, + dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } + } +} + + +LOCAL(void) +do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transpose source into destination */ +{ + JDIMENSION dst_blk_x, dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Transposing pixels within a block just requires transposing the + * DCT coefficients. + * Partial iMCUs at the edges require no special treatment; we simply + * process all the available DCT blocks for every component. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } +} + + +LOCAL(void) +do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 90 degree rotation is equivalent to + * 1. Transposing the image; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) right edge properly. They just get transposed and + * not mirrored. + */ + MCU_cols = srcinfo->output_height / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_width - x_crop_blocks - dst_blk_x - + (JDIMENSION) compptr->h_samp_factor, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } else { + /* Edge blocks are transposed but not mirrored. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 270 degree rotation is equivalent to + * 1. Horizontal mirroring; + * 2. Transposing the image. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) bottom edge properly. They just get transposed and + * not mirrored. + */ + MCU_rows = srcinfo->output_width / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[offset_x] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 180 degree rotation is equivalent to + * 1. Vertical mirroring; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the vertically mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - y_crop_blocks - dst_blk_y - + (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge rows are only mirrored horizontally. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + dst_row_ptr = dst_buffer[offset_y]; + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Process the blocks that can be mirrored both ways. */ + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE; i += 2) { + /* For even row, negate every odd column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + /* For odd row, negate every even column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = *src_ptr++; + } + } + } else { + /* Any remaining right-edge blocks are only mirrored vertically. */ + src_ptr = src_row_ptr[x_crop_blocks + dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } + } else { + /* Remaining rows are just mirrored horizontally. */ + src_row_ptr = src_buffer[offset_y]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Process the blocks that can be mirrored. */ + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE2; i += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + } else { + /* Any remaining right-edge blocks are only copied. */ + jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks, + dst_row_ptr + dst_blk_x, + (JDIMENSION) 1); + } + } + } + } + } + } +} + + +LOCAL(void) +do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transverse transpose is equivalent to + * 1. 180 degree rotation; + * 2. Transposition; + * or + * 1. Horizontal mirroring; + * 2. Transposition; + * 3. Horizontal mirroring. + * These steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_height / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_width / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_width - x_crop_blocks - dst_blk_x - + (JDIMENSION) compptr->h_samp_factor, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } else { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (y_crop_blocks + dst_blk_y < comp_height) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + i++; + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Right-edge blocks are mirrored in y only */ + src_ptr = src_buffer[offset_x] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } + } else { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Bottom-edge blocks are mirrored in x only */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* At lower right corner, just transpose, no mirroring */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } + } +} + + +/* Parse an unsigned integer: subroutine for jtransform_parse_crop_spec. + * Returns TRUE if valid integer found, FALSE if not. + * *strptr is advanced over the digit string, and *result is set to its value. + */ + +LOCAL(boolean) +jt_read_integer (const char ** strptr, JDIMENSION * result) +{ + const char * ptr = *strptr; + JDIMENSION val = 0; + + for (; isdigit(*ptr); ptr++) { + val = val * 10 + (JDIMENSION) (*ptr - '0'); + } + *result = val; + if (ptr == *strptr) + return FALSE; /* oops, no digits */ + *strptr = ptr; + return TRUE; +} + + +/* Parse a crop specification (written in X11 geometry style). + * The routine returns TRUE if the spec string is valid, FALSE if not. + * + * The crop spec string should have the format + * x{+-}{+-} + * where width, height, xoffset, and yoffset are unsigned integers. + * Each of the elements can be omitted to indicate a default value. + * (A weakness of this style is that it is not possible to omit xoffset + * while specifying yoffset, since they look alike.) + * + * This code is loosely based on XParseGeometry from the X11 distribution. + */ + +GLOBAL(boolean) +jtransform_parse_crop_spec (jpeg_transform_info *info, const char *spec) +{ + info->crop = FALSE; + info->crop_width_set = JCROP_UNSET; + info->crop_height_set = JCROP_UNSET; + info->crop_xoffset_set = JCROP_UNSET; + info->crop_yoffset_set = JCROP_UNSET; + + if (isdigit(*spec)) { + /* fetch width */ + if (! jt_read_integer(&spec, &info->crop_width)) + return FALSE; + info->crop_width_set = JCROP_POS; + } + if (*spec == 'x' || *spec == 'X') { + /* fetch height */ + spec++; + if (! jt_read_integer(&spec, &info->crop_height)) + return FALSE; + info->crop_height_set = JCROP_POS; + } + if (*spec == '+' || *spec == '-') { + /* fetch xoffset */ + info->crop_xoffset_set = (*spec == '-') ? JCROP_NEG : JCROP_POS; + spec++; + if (! jt_read_integer(&spec, &info->crop_xoffset)) + return FALSE; + } + if (*spec == '+' || *spec == '-') { + /* fetch yoffset */ + info->crop_yoffset_set = (*spec == '-') ? JCROP_NEG : JCROP_POS; + spec++; + if (! jt_read_integer(&spec, &info->crop_yoffset)) + return FALSE; + } + /* We had better have gotten to the end of the string. */ + if (*spec != '\0') + return FALSE; + info->crop = TRUE; + return TRUE; +} + + +/* Trim off any partial iMCUs on the indicated destination edge */ + +LOCAL(void) +trim_right_edge (jpeg_transform_info *info, JDIMENSION full_width) +{ + JDIMENSION MCU_cols; + + MCU_cols = info->output_width / info->iMCU_sample_width; + if (MCU_cols > 0 && info->x_crop_offset + MCU_cols == + full_width / info->iMCU_sample_width) + info->output_width = MCU_cols * info->iMCU_sample_width; +} + +LOCAL(void) +trim_bottom_edge (jpeg_transform_info *info, JDIMENSION full_height) +{ + JDIMENSION MCU_rows; + + MCU_rows = info->output_height / info->iMCU_sample_height; + if (MCU_rows > 0 && info->y_crop_offset + MCU_rows == + full_height / info->iMCU_sample_height) + info->output_height = MCU_rows * info->iMCU_sample_height; +} + + +/* Request any required workspace. + * + * This routine figures out the size that the output image will be + * (which implies that all the transform parameters must be set before + * it is called). + * + * We allocate the workspace virtual arrays from the source decompression + * object, so that all the arrays (both the original data and the workspace) + * will be taken into account while making memory management decisions. + * Hence, this routine must be called after jpeg_read_header (which reads + * the image dimensions) and before jpeg_read_coefficients (which realizes + * the source's virtual arrays). + * + * This function returns FALSE right away if -perfect is given + * and transformation is not perfect. Otherwise returns TRUE. + */ + +GLOBAL(boolean) +jtransform_request_workspace (j_decompress_ptr srcinfo, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *coef_arrays; + boolean need_workspace, transpose_it; + jpeg_component_info *compptr; + JDIMENSION xoffset, yoffset; + JDIMENSION width_in_iMCUs, height_in_iMCUs; + JDIMENSION width_in_blocks, height_in_blocks; + int ci, h_samp_factor, v_samp_factor; + + /* Determine number of components in output image */ + if (info->force_grayscale && + srcinfo->jpeg_color_space == JCS_YCbCr && + srcinfo->num_components == 3) + /* We'll only process the first component */ + info->num_components = 1; + else + /* Process all the components */ + info->num_components = srcinfo->num_components; + + /* Compute output image dimensions and related values. */ + jpeg_core_output_dimensions(srcinfo); + + /* Return right away if -perfect is given and transformation is not perfect. + */ + if (info->perfect) { + if (info->num_components == 1) { + if (!jtransform_perfect_transform(srcinfo->output_width, + srcinfo->output_height, + srcinfo->min_DCT_h_scaled_size, + srcinfo->min_DCT_v_scaled_size, + info->transform)) + return FALSE; + } else { + if (!jtransform_perfect_transform(srcinfo->output_width, + srcinfo->output_height, + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size, + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size, + info->transform)) + return FALSE; + } + } + + /* If there is only one output component, force the iMCU size to be 1; + * else use the source iMCU size. (This allows us to do the right thing + * when reducing color to grayscale, and also provides a handy way of + * cleaning up "funny" grayscale images whose sampling factors are not 1x1.) + */ + switch (info->transform) { + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + info->output_width = srcinfo->output_height; + info->output_height = srcinfo->output_width; + if (info->num_components == 1) { + info->iMCU_sample_width = srcinfo->min_DCT_v_scaled_size; + info->iMCU_sample_height = srcinfo->min_DCT_h_scaled_size; + } else { + info->iMCU_sample_width = + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size; + info->iMCU_sample_height = + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size; + } + break; + default: + info->output_width = srcinfo->output_width; + info->output_height = srcinfo->output_height; + if (info->num_components == 1) { + info->iMCU_sample_width = srcinfo->min_DCT_h_scaled_size; + info->iMCU_sample_height = srcinfo->min_DCT_v_scaled_size; + } else { + info->iMCU_sample_width = + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size; + info->iMCU_sample_height = + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size; + } + break; + } + + /* If cropping has been requested, compute the crop area's position and + * dimensions, ensuring that its upper left corner falls at an iMCU boundary. + */ + if (info->crop) { + /* Insert default values for unset crop parameters */ + if (info->crop_xoffset_set == JCROP_UNSET) + info->crop_xoffset = 0; /* default to +0 */ + if (info->crop_yoffset_set == JCROP_UNSET) + info->crop_yoffset = 0; /* default to +0 */ + if (info->crop_xoffset >= info->output_width || + info->crop_yoffset >= info->output_height) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + if (info->crop_width_set == JCROP_UNSET) + info->crop_width = info->output_width - info->crop_xoffset; + if (info->crop_height_set == JCROP_UNSET) + info->crop_height = info->output_height - info->crop_yoffset; + /* Ensure parameters are valid */ + if (info->crop_width <= 0 || info->crop_width > info->output_width || + info->crop_height <= 0 || info->crop_height > info->output_height || + info->crop_xoffset > info->output_width - info->crop_width || + info->crop_yoffset > info->output_height - info->crop_height) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + /* Convert negative crop offsets into regular offsets */ + if (info->crop_xoffset_set == JCROP_NEG) + xoffset = info->output_width - info->crop_width - info->crop_xoffset; + else + xoffset = info->crop_xoffset; + if (info->crop_yoffset_set == JCROP_NEG) + yoffset = info->output_height - info->crop_height - info->crop_yoffset; + else + yoffset = info->crop_yoffset; + /* Now adjust so that upper left corner falls at an iMCU boundary */ + info->output_width = + info->crop_width + (xoffset % info->iMCU_sample_width); + info->output_height = + info->crop_height + (yoffset % info->iMCU_sample_height); + /* Save x/y offsets measured in iMCUs */ + info->x_crop_offset = xoffset / info->iMCU_sample_width; + info->y_crop_offset = yoffset / info->iMCU_sample_height; + } else { + info->x_crop_offset = 0; + info->y_crop_offset = 0; + } + + /* Figure out whether we need workspace arrays, + * and if so whether they are transposed relative to the source. + */ + need_workspace = FALSE; + transpose_it = FALSE; + switch (info->transform) { + case JXFORM_NONE: + if (info->x_crop_offset != 0 || info->y_crop_offset != 0) + need_workspace = TRUE; + /* No workspace needed if neither cropping nor transforming */ + break; + case JXFORM_FLIP_H: + if (info->trim) + trim_right_edge(info, srcinfo->output_width); + if (info->y_crop_offset != 0) + need_workspace = TRUE; + /* do_flip_h_no_crop doesn't need a workspace array */ + break; + case JXFORM_FLIP_V: + if (info->trim) + trim_bottom_edge(info, srcinfo->output_height); + /* Need workspace arrays having same dimensions as source image. */ + need_workspace = TRUE; + break; + case JXFORM_TRANSPOSE: + /* transpose does NOT have to trim anything */ + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_TRANSVERSE: + if (info->trim) { + trim_right_edge(info, srcinfo->output_height); + trim_bottom_edge(info, srcinfo->output_width); + } + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_ROT_90: + if (info->trim) + trim_right_edge(info, srcinfo->output_height); + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_ROT_180: + if (info->trim) { + trim_right_edge(info, srcinfo->output_width); + trim_bottom_edge(info, srcinfo->output_height); + } + /* Need workspace arrays having same dimensions as source image. */ + need_workspace = TRUE; + break; + case JXFORM_ROT_270: + if (info->trim) + trim_bottom_edge(info, srcinfo->output_width); + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + } + + /* Allocate workspace if needed. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + if (need_workspace) { + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + width_in_iMCUs = (JDIMENSION) + jdiv_round_up((long) info->output_width, + (long) info->iMCU_sample_width); + height_in_iMCUs = (JDIMENSION) + jdiv_round_up((long) info->output_height, + (long) info->iMCU_sample_height); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + if (info->num_components == 1) { + /* we're going to force samp factors to 1x1 in this case */ + h_samp_factor = v_samp_factor = 1; + } else if (transpose_it) { + h_samp_factor = compptr->v_samp_factor; + v_samp_factor = compptr->h_samp_factor; + } else { + h_samp_factor = compptr->h_samp_factor; + v_samp_factor = compptr->v_samp_factor; + } + width_in_blocks = width_in_iMCUs * h_samp_factor; + height_in_blocks = height_in_iMCUs * v_samp_factor; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + width_in_blocks, height_in_blocks, (JDIMENSION) v_samp_factor); + } + info->workspace_coef_arrays = coef_arrays; + } else + info->workspace_coef_arrays = NULL; + + return TRUE; +} + + +/* Transpose destination image parameters */ + +LOCAL(void) +transpose_critical_parameters (j_compress_ptr dstinfo) +{ + int tblno, i, j, ci, itemp; + jpeg_component_info *compptr; + JQUANT_TBL *qtblptr; + JDIMENSION jtemp; + UINT16 qtemp; + + /* Transpose image dimensions */ + jtemp = dstinfo->image_width; + dstinfo->image_width = dstinfo->image_height; + dstinfo->image_height = jtemp; + itemp = dstinfo->min_DCT_h_scaled_size; + dstinfo->min_DCT_h_scaled_size = dstinfo->min_DCT_v_scaled_size; + dstinfo->min_DCT_v_scaled_size = itemp; + + /* Transpose sampling factors */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + itemp = compptr->h_samp_factor; + compptr->h_samp_factor = compptr->v_samp_factor; + compptr->v_samp_factor = itemp; + } + + /* Transpose quantization tables */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + qtblptr = dstinfo->quant_tbl_ptrs[tblno]; + if (qtblptr != NULL) { + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < i; j++) { + qtemp = qtblptr->quantval[i*DCTSIZE+j]; + qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i]; + qtblptr->quantval[j*DCTSIZE+i] = qtemp; + } + } + } + } +} + + +/* Adjust Exif image parameters. + * + * We try to adjust the Tags ExifImageWidth and ExifImageHeight if possible. + */ + +LOCAL(void) +adjust_exif_parameters (JOCTET FAR * data, unsigned int length, + JDIMENSION new_width, JDIMENSION new_height) +{ + boolean is_motorola; /* Flag for byte order */ + unsigned int number_of_tags, tagnum; + unsigned int firstoffset, offset; + JDIMENSION new_value; + + if (length < 12) return; /* Length of an IFD entry */ + + /* Discover byte order */ + if (GETJOCTET(data[0]) == 0x49 && GETJOCTET(data[1]) == 0x49) + is_motorola = FALSE; + else if (GETJOCTET(data[0]) == 0x4D && GETJOCTET(data[1]) == 0x4D) + is_motorola = TRUE; + else + return; + + /* Check Tag Mark */ + if (is_motorola) { + if (GETJOCTET(data[2]) != 0) return; + if (GETJOCTET(data[3]) != 0x2A) return; + } else { + if (GETJOCTET(data[3]) != 0) return; + if (GETJOCTET(data[2]) != 0x2A) return; + } + + /* Get first IFD offset (offset to IFD0) */ + if (is_motorola) { + if (GETJOCTET(data[4]) != 0) return; + if (GETJOCTET(data[5]) != 0) return; + firstoffset = GETJOCTET(data[6]); + firstoffset <<= 8; + firstoffset += GETJOCTET(data[7]); + } else { + if (GETJOCTET(data[7]) != 0) return; + if (GETJOCTET(data[6]) != 0) return; + firstoffset = GETJOCTET(data[5]); + firstoffset <<= 8; + firstoffset += GETJOCTET(data[4]); + } + if (firstoffset > length - 2) return; /* check end of data segment */ + + /* Get the number of directory entries contained in this IFD */ + if (is_motorola) { + number_of_tags = GETJOCTET(data[firstoffset]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[firstoffset+1]); + } else { + number_of_tags = GETJOCTET(data[firstoffset+1]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[firstoffset]); + } + if (number_of_tags == 0) return; + firstoffset += 2; + + /* Search for ExifSubIFD offset Tag in IFD0 */ + for (;;) { + if (firstoffset > length - 12) return; /* check end of data segment */ + /* Get Tag number */ + if (is_motorola) { + tagnum = GETJOCTET(data[firstoffset]); + tagnum <<= 8; + tagnum += GETJOCTET(data[firstoffset+1]); + } else { + tagnum = GETJOCTET(data[firstoffset+1]); + tagnum <<= 8; + tagnum += GETJOCTET(data[firstoffset]); + } + if (tagnum == 0x8769) break; /* found ExifSubIFD offset Tag */ + if (--number_of_tags == 0) return; + firstoffset += 12; + } + + /* Get the ExifSubIFD offset */ + if (is_motorola) { + if (GETJOCTET(data[firstoffset+8]) != 0) return; + if (GETJOCTET(data[firstoffset+9]) != 0) return; + offset = GETJOCTET(data[firstoffset+10]); + offset <<= 8; + offset += GETJOCTET(data[firstoffset+11]); + } else { + if (GETJOCTET(data[firstoffset+11]) != 0) return; + if (GETJOCTET(data[firstoffset+10]) != 0) return; + offset = GETJOCTET(data[firstoffset+9]); + offset <<= 8; + offset += GETJOCTET(data[firstoffset+8]); + } + if (offset > length - 2) return; /* check end of data segment */ + + /* Get the number of directory entries contained in this SubIFD */ + if (is_motorola) { + number_of_tags = GETJOCTET(data[offset]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[offset+1]); + } else { + number_of_tags = GETJOCTET(data[offset+1]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[offset]); + } + if (number_of_tags < 2) return; + offset += 2; + + /* Search for ExifImageWidth and ExifImageHeight Tags in this SubIFD */ + do { + if (offset > length - 12) return; /* check end of data segment */ + /* Get Tag number */ + if (is_motorola) { + tagnum = GETJOCTET(data[offset]); + tagnum <<= 8; + tagnum += GETJOCTET(data[offset+1]); + } else { + tagnum = GETJOCTET(data[offset+1]); + tagnum <<= 8; + tagnum += GETJOCTET(data[offset]); + } + if (tagnum == 0xA002 || tagnum == 0xA003) { + if (tagnum == 0xA002) + new_value = new_width; /* ExifImageWidth Tag */ + else + new_value = new_height; /* ExifImageHeight Tag */ + if (is_motorola) { + data[offset+2] = 0; /* Format = unsigned long (4 octets) */ + data[offset+3] = 4; + data[offset+4] = 0; /* Number Of Components = 1 */ + data[offset+5] = 0; + data[offset+6] = 0; + data[offset+7] = 1; + data[offset+8] = 0; + data[offset+9] = 0; + data[offset+10] = (JOCTET)((new_value >> 8) & 0xFF); + data[offset+11] = (JOCTET)(new_value & 0xFF); + } else { + data[offset+2] = 4; /* Format = unsigned long (4 octets) */ + data[offset+3] = 0; + data[offset+4] = 1; /* Number Of Components = 1 */ + data[offset+5] = 0; + data[offset+6] = 0; + data[offset+7] = 0; + data[offset+8] = (JOCTET)(new_value & 0xFF); + data[offset+9] = (JOCTET)((new_value >> 8) & 0xFF); + data[offset+10] = 0; + data[offset+11] = 0; + } + } + offset += 12; + } while (--number_of_tags); +} + + +/* Adjust output image parameters as needed. + * + * This must be called after jpeg_copy_critical_parameters() + * and before jpeg_write_coefficients(). + * + * The return value is the set of virtual coefficient arrays to be written + * (either the ones allocated by jtransform_request_workspace, or the + * original source data arrays). The caller will need to pass this value + * to jpeg_write_coefficients(). + */ + +GLOBAL(jvirt_barray_ptr *) +jtransform_adjust_parameters (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + /* If force-to-grayscale is requested, adjust destination parameters */ + if (info->force_grayscale) { + /* First, ensure we have YCbCr or grayscale data, and that the source's + * Y channel is full resolution. (No reasonable person would make Y + * be less than full resolution, so actually coping with that case + * isn't worth extra code space. But we check it to avoid crashing.) + */ + if (((dstinfo->jpeg_color_space == JCS_YCbCr && + dstinfo->num_components == 3) || + (dstinfo->jpeg_color_space == JCS_GRAYSCALE && + dstinfo->num_components == 1)) && + srcinfo->comp_info[0].h_samp_factor == srcinfo->max_h_samp_factor && + srcinfo->comp_info[0].v_samp_factor == srcinfo->max_v_samp_factor) { + /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed + * properly. Among other things, it sets the target h_samp_factor & + * v_samp_factor to 1, which typically won't match the source. + * We have to preserve the source's quantization table number, however. + */ + int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no; + jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE); + dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no; + } else { + /* Sorry, can't do it */ + ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL); + } + } else if (info->num_components == 1) { + /* For a single-component source, we force the destination sampling factors + * to 1x1, with or without force_grayscale. This is useful because some + * decoders choke on grayscale images with other sampling factors. + */ + dstinfo->comp_info[0].h_samp_factor = 1; + dstinfo->comp_info[0].v_samp_factor = 1; + } + + /* Correct the destination's image dimensions as necessary + * for rotate/flip, resize, and crop operations. + */ + dstinfo->jpeg_width = info->output_width; + dstinfo->jpeg_height = info->output_height; + + /* Transpose destination image parameters */ + switch (info->transform) { + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + transpose_critical_parameters(dstinfo); + break; + default: + break; + } + + /* Adjust Exif properties */ + if (srcinfo->marker_list != NULL && + srcinfo->marker_list->marker == JPEG_APP0+1 && + srcinfo->marker_list->data_length >= 6 && + GETJOCTET(srcinfo->marker_list->data[0]) == 0x45 && + GETJOCTET(srcinfo->marker_list->data[1]) == 0x78 && + GETJOCTET(srcinfo->marker_list->data[2]) == 0x69 && + GETJOCTET(srcinfo->marker_list->data[3]) == 0x66 && + GETJOCTET(srcinfo->marker_list->data[4]) == 0 && + GETJOCTET(srcinfo->marker_list->data[5]) == 0) { + /* Suppress output of JFIF marker */ + dstinfo->write_JFIF_header = FALSE; + /* Adjust Exif image parameters */ + if (dstinfo->jpeg_width != srcinfo->image_width || + dstinfo->jpeg_height != srcinfo->image_height) + /* Align data segment to start of TIFF structure for parsing */ + adjust_exif_parameters(srcinfo->marker_list->data + 6, + srcinfo->marker_list->data_length - 6, + dstinfo->jpeg_width, dstinfo->jpeg_height); + } + + /* Return the appropriate output data set */ + if (info->workspace_coef_arrays != NULL) + return info->workspace_coef_arrays; + return src_coef_arrays; +} + + +/* Execute the actual transformation, if any. + * + * This must be called *after* jpeg_write_coefficients, because it depends + * on jpeg_write_coefficients to have computed subsidiary values such as + * the per-component width and height fields in the destination object. + * + * Note that some transformations will modify the source data arrays! + */ + +GLOBAL(void) +jtransform_execute_transform (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays; + + /* Note: conditions tested here should match those in switch statement + * in jtransform_request_workspace() + */ + switch (info->transform) { + case JXFORM_NONE: + if (info->x_crop_offset != 0 || info->y_crop_offset != 0) + do_crop(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_FLIP_H: + if (info->y_crop_offset != 0) + do_flip_h(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + else + do_flip_h_no_crop(srcinfo, dstinfo, info->x_crop_offset, + src_coef_arrays); + break; + case JXFORM_FLIP_V: + do_flip_v(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSPOSE: + do_transpose(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSVERSE: + do_transverse(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_90: + do_rot_90(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_180: + do_rot_180(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_270: + do_rot_270(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + } +} + +/* jtransform_perfect_transform + * + * Determine whether lossless transformation is perfectly + * possible for a specified image and transformation. + * + * Inputs: + * image_width, image_height: source image dimensions. + * MCU_width, MCU_height: pixel dimensions of MCU. + * transform: transformation identifier. + * Parameter sources from initialized jpeg_struct + * (after reading source header): + * image_width = cinfo.image_width + * image_height = cinfo.image_height + * MCU_width = cinfo.max_h_samp_factor * cinfo.block_size + * MCU_height = cinfo.max_v_samp_factor * cinfo.block_size + * Result: + * TRUE = perfect transformation possible + * FALSE = perfect transformation not possible + * (may use custom action then) + */ + +GLOBAL(boolean) +jtransform_perfect_transform(JDIMENSION image_width, JDIMENSION image_height, + int MCU_width, int MCU_height, + JXFORM_CODE transform) +{ + boolean result = TRUE; /* initialize TRUE */ + + switch (transform) { + case JXFORM_FLIP_H: + case JXFORM_ROT_270: + if (image_width % (JDIMENSION) MCU_width) + result = FALSE; + break; + case JXFORM_FLIP_V: + case JXFORM_ROT_90: + if (image_height % (JDIMENSION) MCU_height) + result = FALSE; + break; + case JXFORM_TRANSVERSE: + case JXFORM_ROT_180: + if (image_width % (JDIMENSION) MCU_width) + result = FALSE; + if (image_height % (JDIMENSION) MCU_height) + result = FALSE; + break; + default: + break; + } + + return result; +} + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* Setup decompression object to save desired markers in memory. + * This must be called before jpeg_read_header() to have the desired effect. + */ + +GLOBAL(void) +jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option) +{ +#ifdef SAVE_MARKERS_SUPPORTED + int m; + + /* Save comments except under NONE option */ + if (option != JCOPYOPT_NONE) { + jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF); + } + /* Save all types of APPn markers iff ALL option */ + if (option == JCOPYOPT_ALL) { + for (m = 0; m < 16; m++) + jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF); + } +#endif /* SAVE_MARKERS_SUPPORTED */ +} + +/* Copy markers saved in the given source object to the destination object. + * This should be called just after jpeg_start_compress() or + * jpeg_write_coefficients(). + * Note that those routines will have written the SOI, and also the + * JFIF APP0 or Adobe APP14 markers if selected. + */ + +GLOBAL(void) +jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option) +{ + jpeg_saved_marker_ptr marker; + + /* In the current implementation, we don't actually need to examine the + * option flag here; we just copy everything that got saved. + * But to avoid confusion, we do not output JFIF and Adobe APP14 markers + * if the encoder library already wrote one. + */ + for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) { + if (dstinfo->write_JFIF_header && + marker->marker == JPEG_APP0 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x4A && + GETJOCTET(marker->data[1]) == 0x46 && + GETJOCTET(marker->data[2]) == 0x49 && + GETJOCTET(marker->data[3]) == 0x46 && + GETJOCTET(marker->data[4]) == 0) + continue; /* reject duplicate JFIF */ + if (dstinfo->write_Adobe_marker && + marker->marker == JPEG_APP0+14 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x41 && + GETJOCTET(marker->data[1]) == 0x64 && + GETJOCTET(marker->data[2]) == 0x6F && + GETJOCTET(marker->data[3]) == 0x62 && + GETJOCTET(marker->data[4]) == 0x65) + continue; /* reject duplicate Adobe */ +#ifdef NEED_FAR_POINTERS + /* We could use jpeg_write_marker if the data weren't FAR... */ + { + unsigned int i; + jpeg_write_m_header(dstinfo, marker->marker, marker->data_length); + for (i = 0; i < marker->data_length; i++) + jpeg_write_m_byte(dstinfo, marker->data[i]); + } +#else + jpeg_write_marker(dstinfo, marker->marker, + marker->data, marker->data_length); +#endif + } +} + +} // namespace Digikam + +#else // JPEG_LIB_VERSION >= 80 + +/* + * transupp.c + * + * Copyright (C) 1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains image transformation routines and other utility code + * used by the jpegtran sample application. These are NOT part of the core + * JPEG library. But we keep these routines separate from jpegtran.c to + * ease the task of maintaining jpegtran-like programs that have other user + * interfaces. + */ + +// Local includes. + +#include "transupp.h" /* My own external interface */ + +namespace Digikam +{ + +#if TRANSFORMS_SUPPORTED + +/* + * Lossless image transformation routines. These routines work on DCT + * coefficient arrays and thus do not require any lossy decompression + * or recompression of the image. + * Thanks to Guido Vollbeding for the initial design and code of this feature. + * + * Horizontal flipping is done in-place, using a single top-to-bottom + * pass through the virtual source array. It will thus be much the + * fastest option for images larger than main memory. + * + * The other routines require a set of destination virtual arrays, so they + * need twice as much memory as jpegtran normally does. The destination + * arrays are always written in normal scan order (top to bottom) because + * the virtual array manager expects this. The source arrays will be scanned + * in the corresponding order, which means multiple passes through the source + * arrays for most of the transforms. That could result in much thrashing + * if the image is larger than main memory. + * + * Some notes about the operating environment of the individual transform + * routines: + * 1. Both the source and destination virtual arrays are allocated from the + * source JPEG object, and therefore should be manipulated by calling the + * source's memory manager. + * 2. The destination's component count should be used. It may be smaller + * than the source's when forcing to grayscale. + * 3. Likewise the destination's sampling factors should be used. When + * forcing to grayscale the destination's sampling factors will be all 1, + * and we may as well take that as the effective iMCU size. + * 4. When "trim" is in effect, the destination's dimensions will be the + * trimmed values but the source's will be untrimmed. + * 5. All the routines assume that the source and destination buffers are + * padded out to a full iMCU boundary. This is true, although for the + * source buffer it is an undocumented property of jdcoefct.c. + * Notes 2,3,4 boil down to this: generally we should use the destination's + * dimensions and ignore the source's. + */ + + +LOCAL(void) +do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays) +/* Horizontal flip; done in-place, so no separate dest array is required */ +{ + JDIMENSION MCU_cols, comp_width, blk_x, blk_y; + int ci, k, offset_y; + JBLOCKARRAY buffer; + JCOEFPTR ptr1, ptr2; + JCOEF temp1, temp2; + jpeg_component_info *compptr; + + /* Horizontal mirroring of DCT blocks is accomplished by swapping + * pairs of blocks in-place. Within a DCT block, we perform horizontal + * mirroring by changing the signs of odd-numbered columns. + * Partial iMCUs at the right edge are left untouched. + */ + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + for (blk_y = 0; blk_y < compptr->height_in_blocks; + blk_y += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y, + (JDIMENSION) compptr->v_samp_factor, true); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) { + ptr1 = buffer[offset_y][blk_x]; + ptr2 = buffer[offset_y][comp_width - blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + temp1 = *ptr1; /* swap even column */ + temp2 = *ptr2; + *ptr1++ = temp2; + *ptr2++ = temp1; + temp1 = *ptr1; /* swap odd column with sign change */ + temp2 = *ptr2; + *ptr1++ = -temp2; + *ptr2++ = -temp1; + } + } + } + } + } +} + + +LOCAL(void) +do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Vertical flip */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* We output into a separate array because we can't touch different + * rows of the source virtual array simultaneously. Otherwise, this + * is a pretty straightforward analog of horizontal flip. + * Within a DCT block, vertical mirroring is done by changing the signs + * of odd-numbered rows. + * Partial iMCUs at the bottom edge are copied verbatim. + */ + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, true); + if (dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, false); + } else { + /* Bottom-edge blocks will be copied verbatim. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, false); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + /* copy even row */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + /* copy odd row with sign change */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Just copy row verbatim. */ + jcopy_block_row(src_buffer[offset_y], dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } + } +} + + +LOCAL(void) +do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transpose source into destination */ +{ + JDIMENSION dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Transposing pixels within a block just requires transposing the + * DCT coefficients. + * Partial iMCUs at the edges require no special treatment; we simply + * process all the available DCT blocks for every component. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, true); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, false); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } +} + + +LOCAL(void) +do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 90 degree rotation is equivalent to + * 1. Transposing the image; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) right edge properly. They just get transposed and + * not mirrored. + */ + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, true); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, false); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + if (dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* Edge blocks are transposed but not mirrored. */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 270 degree rotation is equivalent to + * 1. Horizontal mirroring; + * 2. Transposing the image. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) bottom edge properly. They just get transposed and + * not mirrored. + */ + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, true); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, false); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (dst_blk_y < comp_height) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[offset_x] + [comp_height - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 180 degree rotation is equivalent to + * 1. Vertical mirroring; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, true); + if (dst_blk_y < comp_height) { + /* Row is within the vertically mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, false); + } else { + /* Bottom-edge rows are only mirrored horizontally. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, false); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + /* Process the blocks that can be mirrored both ways. */ + for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE; i += 2) { + /* For even row, negate every odd column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + /* For odd row, negate every even column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = *src_ptr++; + } + } + } + /* Any remaining right-edge blocks are only mirrored vertically. */ + for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Remaining rows are just mirrored horizontally. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[offset_y]; + /* Process the blocks that can be mirrored. */ + for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE2; i += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + } + /* Any remaining right-edge blocks are only copied. */ + for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE2; i++) + *dst_ptr++ = *src_ptr++; + } + } + } + } + } +} + + +LOCAL(void) +do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transverse transpose is equivalent to + * 1. 180 degree rotation; + * 2. Transposition; + * or + * 1. Horizontal mirroring; + * 2. Transposition; + * 3. Horizontal mirroring. + * These steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, true); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, false); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + if (dst_blk_y < comp_height) { + src_ptr = src_buffer[offset_x] + [comp_height - dst_blk_y - offset_y - 1]; + if (dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + i++; + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Right-edge blocks are mirrored in y only */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } + } else { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + if (dst_blk_x < comp_width) { + /* Bottom-edge blocks are mirrored in x only */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* At lower right corner, just transpose, no mirroring */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } + } +} + + +/* Request any required workspace. + * + * We allocate the workspace virtual arrays from the source decompression + * object, so that all the arrays (both the original data and the workspace) + * will be taken into account while making memory management decisions. + * Hence, this routine must be called after jpeg_read_header (which reads + * the image dimensions) and before jpeg_read_coefficients (which realizes + * the source's virtual arrays). + */ + +GLOBAL(void) +jtransform_request_workspace (j_decompress_ptr srcinfo, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *coef_arrays = NULL; + jpeg_component_info *compptr; + int ci; + + if (info->force_grayscale && + srcinfo->jpeg_color_space == JCS_YCbCr && + srcinfo->num_components == 3) { + /* We'll only process the first component */ + info->num_components = 1; + } else { + /* Process all the components */ + info->num_components = srcinfo->num_components; + } + + switch (info->transform) { + case JXFORM_NONE: + case JXFORM_FLIP_H: + /* Don't need a workspace array */ + break; + case JXFORM_FLIP_V: + case JXFORM_ROT_180: + /* Need workspace arrays having same dimensions as source image. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, false, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) compptr->v_samp_factor); + } + break; + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + /* Need workspace arrays having transposed dimensions. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, false, + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) compptr->h_samp_factor); + } + break; + } + info->workspace_coef_arrays = coef_arrays; +} + + +/* Transpose destination image parameters */ + +LOCAL(void) +transpose_critical_parameters (j_compress_ptr dstinfo) +{ + int tblno, i, j, ci, itemp; + jpeg_component_info *compptr; + JQUANT_TBL *qtblptr; + JDIMENSION dtemp; + UINT16 qtemp; + + /* Transpose basic image dimensions */ + dtemp = dstinfo->image_width; + dstinfo->image_width = dstinfo->image_height; + dstinfo->image_height = dtemp; + + /* Transpose sampling factors */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + itemp = compptr->h_samp_factor; + compptr->h_samp_factor = compptr->v_samp_factor; + compptr->v_samp_factor = itemp; + } + + /* Transpose quantization tables */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + qtblptr = dstinfo->quant_tbl_ptrs[tblno]; + if (qtblptr != NULL) { + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < i; j++) { + qtemp = qtblptr->quantval[i*DCTSIZE+j]; + qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i]; + qtblptr->quantval[j*DCTSIZE+i] = qtemp; + } + } + } + } +} + + +/* Trim off any partial iMCUs on the indicated destination edge */ + +LOCAL(void) +trim_right_edge (j_compress_ptr dstinfo) +{ + int ci, max_h_samp_factor; + JDIMENSION MCU_cols; + + /* We have to compute max_h_samp_factor ourselves, + * because it hasn't been set yet in the destination + * (and we don't want to use the source's value). + */ + max_h_samp_factor = 1; + for (ci = 0; ci < dstinfo->num_components; ci++) { + int h_samp_factor = dstinfo->comp_info[ci].h_samp_factor; + max_h_samp_factor = MAX(max_h_samp_factor, h_samp_factor); + } + MCU_cols = dstinfo->image_width / (max_h_samp_factor * DCTSIZE); + if (MCU_cols > 0) /* can't trim to 0 pixels */ + dstinfo->image_width = MCU_cols * (max_h_samp_factor * DCTSIZE); +} + +LOCAL(void) +trim_bottom_edge (j_compress_ptr dstinfo) +{ + int ci, max_v_samp_factor; + JDIMENSION MCU_rows; + + /* We have to compute max_v_samp_factor ourselves, + * because it hasn't been set yet in the destination + * (and we don't want to use the source's value). + */ + max_v_samp_factor = 1; + for (ci = 0; ci < dstinfo->num_components; ci++) { + int v_samp_factor = dstinfo->comp_info[ci].v_samp_factor; + max_v_samp_factor = MAX(max_v_samp_factor, v_samp_factor); + } + MCU_rows = dstinfo->image_height / (max_v_samp_factor * DCTSIZE); + if (MCU_rows > 0) /* can't trim to 0 pixels */ + dstinfo->image_height = MCU_rows * (max_v_samp_factor * DCTSIZE); +} + + +/* Adjust output image parameters as needed. + * + * This must be called after jpeg_copy_critical_parameters() + * and before jpeg_write_coefficients(). + * + * The return value is the set of virtual coefficient arrays to be written + * (either the ones allocated by jtransform_request_workspace, or the + * original source data arrays). The caller will need to pass this value + * to jpeg_write_coefficients(). + */ + +GLOBAL(jvirt_barray_ptr *) +jtransform_adjust_parameters (j_decompress_ptr /*srcinfo*/, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + /* If force-to-grayscale is requested, adjust destination parameters */ + if (info->force_grayscale) { + /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed + * properly. Among other things, the target h_samp_factor & v_samp_factor + * will get set to 1, which typically won't match the source. + * In fact we do this even if the source is already grayscale; that + * provides an easy way of coercing a grayscale JPEG with funny sampling + * factors to the customary 1,1. (Some decoders fail on other factors.) + */ + if ((dstinfo->jpeg_color_space == JCS_YCbCr && + dstinfo->num_components == 3) || + (dstinfo->jpeg_color_space == JCS_GRAYSCALE && + dstinfo->num_components == 1)) { + /* We have to preserve the source's quantization table number. */ + int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no; + jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE); + dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no; + } else { + /* Sorry, can't do it */ + ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL); + } + } + + /* Correct the destination's image dimensions etc if necessary */ + switch (info->transform) { + case JXFORM_NONE: + /* Nothing to do */ + break; + case JXFORM_FLIP_H: + if (info->trim) + trim_right_edge(dstinfo); + break; + case JXFORM_FLIP_V: + if (info->trim) + trim_bottom_edge(dstinfo); + break; + case JXFORM_TRANSPOSE: + transpose_critical_parameters(dstinfo); + /* transpose does NOT have to trim anything */ + break; + case JXFORM_TRANSVERSE: + transpose_critical_parameters(dstinfo); + if (info->trim) { + trim_right_edge(dstinfo); + trim_bottom_edge(dstinfo); + } + break; + case JXFORM_ROT_90: + transpose_critical_parameters(dstinfo); + if (info->trim) + trim_right_edge(dstinfo); + break; + case JXFORM_ROT_180: + if (info->trim) { + trim_right_edge(dstinfo); + trim_bottom_edge(dstinfo); + } + break; + case JXFORM_ROT_270: + transpose_critical_parameters(dstinfo); + if (info->trim) + trim_bottom_edge(dstinfo); + break; + } + + /* Return the appropriate output data set */ + if (info->workspace_coef_arrays != NULL) + return info->workspace_coef_arrays; + return src_coef_arrays; +} + + +/* Execute the actual transformation, if any. + * + * This must be called *after* jpeg_write_coefficients, because it depends + * on jpeg_write_coefficients to have computed subsidiary values such as + * the per-component width and height fields in the destination object. + * + * Note that some transformations will modify the source data arrays! + */ + +GLOBAL(void) +jtransform_execute_transformation (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays; + + switch (info->transform) { + case JXFORM_NONE: + break; + case JXFORM_FLIP_H: + do_flip_h(srcinfo, dstinfo, src_coef_arrays); + break; + case JXFORM_FLIP_V: + do_flip_v(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSPOSE: + do_transpose(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSVERSE: + do_transverse(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_90: + do_rot_90(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_180: + do_rot_180(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_270: + do_rot_270(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + } +} + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* Setup decompression object to save desired markers in memory. + * This must be called before jpeg_read_header() to have the desired effect. + */ + +GLOBAL(void) +jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option) +{ +#ifdef SAVE_MARKERS_SUPPORTED + int m; + + /* Save comments except under NONE option */ + if (option != JCOPYOPT_NONE) { + jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF); + } + /* Save all types of APPn markers iff ALL option */ + if (option == JCOPYOPT_ALL) { + for (m = 0; m < 16; m++) + jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF); + } +#endif /* SAVE_MARKERS_SUPPORTED */ +} + +/* Copy markers saved in the given source object to the destination object. + * This should be called just after jpeg_start_compress() or + * jpeg_write_coefficients(). + * Note that those routines will have written the SOI, and also the + * JFIF APP0 or Adobe APP14 markers if selected. + */ + +GLOBAL(void) +jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION /*option*/) +{ + jpeg_saved_marker_ptr marker; + + /* In the current implementation, we don't actually need to examine the + * option flag here; we just copy everything that got saved. + * But to avoid confusion, we do not output JFIF and Adobe APP14 markers + * if the encoder library already wrote one. + */ + for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) { + if (dstinfo->write_JFIF_header && + marker->marker == JPEG_APP0 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x4A && + GETJOCTET(marker->data[1]) == 0x46 && + GETJOCTET(marker->data[2]) == 0x49 && + GETJOCTET(marker->data[3]) == 0x46 && + GETJOCTET(marker->data[4]) == 0) + continue; /* reject duplicate JFIF */ + if (dstinfo->write_Adobe_marker && + marker->marker == JPEG_APP0+14 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x41 && + GETJOCTET(marker->data[1]) == 0x64 && + GETJOCTET(marker->data[2]) == 0x6F && + GETJOCTET(marker->data[3]) == 0x62 && + GETJOCTET(marker->data[4]) == 0x65) + continue; /* reject duplicate Adobe */ +#ifdef NEED_FAR_POINTERS + /* We could use jpeg_write_marker if the data weren't FAR... */ + { + unsigned int i; + jpeg_write_m_header(dstinfo, marker->marker, marker->data_length); + for (i = 0; i < marker->data_length; i++) + jpeg_write_m_byte(dstinfo, marker->data[i]); + } +#else + jpeg_write_marker(dstinfo, marker->marker, + marker->data, marker->data_length); +#endif + } +} + +} // namespace Digikam + +#endif // JPEG_LIB_VERSION >= 80 diff --git a/src/libs/jpegutils/transupp.h b/src/libs/jpegutils/transupp.h new file mode 100644 index 00000000..7dd3fad6 --- /dev/null +++ b/src/libs/jpegutils/transupp.h @@ -0,0 +1,373 @@ +#if JPEG_LIB_VERSION >= 80 + +/* + * transupp.h + * + * Copyright (C) 1997-2009, Thomas G. Lane, Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for image transformation routines and + * other utility code used by the jpegtran sample application. These are + * NOT part of the core JPEG library. But we keep these routines separate + * from jpegtran.c to ease the task of maintaining jpegtran-like programs + * that have other user interfaces. + * + * NOTE: all the routines declared here have very specific requirements + * about when they are to be executed during the reading and writing of the + * source and destination files. See the comments in transupp.c, or see + * jpegtran.c for an example of correct usage. + */ + +#ifndef TRANSUPP_H +#define TRANSUPP_H + +namespace Digikam +{ + +/* If you happen not to want the image transform support, disable it here */ +#ifndef TRANSFORMS_SUPPORTED +#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */ +#endif + +/* + * Although rotating and flipping data expressed as DCT coefficients is not + * hard, there is an asymmetry in the JPEG format specification for images + * whose dimensions aren't multiples of the iMCU size. The right and bottom + * image edges are padded out to the next iMCU boundary with junk data; but + * no padding is possible at the top and left edges. If we were to flip + * the whole image including the pad data, then pad garbage would become + * visible at the top and/or left, and real pixels would disappear into the + * pad margins --- perhaps permanently, since encoders & decoders may not + * bother to preserve DCT blocks that appear to be completely outside the + * nominal image area. So, we have to exclude any partial iMCUs from the + * basic transformation. + * + * Transpose is the only transformation that can handle partial iMCUs at the + * right and bottom edges completely cleanly. flip_h can flip partial iMCUs + * at the bottom, but leaves any partial iMCUs at the right edge untouched. + * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched. + * The other transforms are defined as combinations of these basic transforms + * and process edge blocks in a way that preserves the equivalence. + * + * The "trim" option causes untransformable partial iMCUs to be dropped; + * this is not strictly lossless, but it usually gives the best-looking + * result for odd-size images. Note that when this option is active, + * the expected mathematical equivalences between the transforms may not hold. + * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim + * followed by -rot 180 -trim trims both edges.) + * + * We also offer a lossless-crop option, which discards data outside a given + * image region but losslessly preserves what is inside. Like the rotate and + * flip transforms, lossless crop is restricted by the JPEG format: the upper + * left corner of the selected region must fall on an iMCU boundary. If this + * does not hold for the given crop parameters, we silently move the upper left + * corner up and/or left to make it so, simultaneously increasing the region + * dimensions to keep the lower right crop corner unchanged. (Thus, the + * output image covers at least the requested region, but may cover more.) + * + * We also provide a lossless-resize option, which is kind of a lossless-crop + * operation in the DCT coefficient block domain - it discards higher-order + * coefficients and losslessly preserves lower-order coefficients of a + * sub-block. + * + * Rotate/flip transform, resize, and crop can be requested together in a + * single invocation. The crop is applied last --- that is, the crop region + * is specified in terms of the destination image after transform/resize. + * + * We also offer a "force to grayscale" option, which simply discards the + * chrominance channels of a YCbCr image. This is lossless in the sense that + * the luminance channel is preserved exactly. It's not the same kind of + * thing as the rotate/flip transformations, but it's convenient to handle it + * as part of this package, mainly because the transformation routines have to + * be aware of the option to know how many components to work on. + */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jtransform_parse_crop_spec jTrParCrop +#define jtransform_request_workspace jTrRequest +#define jtransform_adjust_parameters jTrAdjust +#define jtransform_execute_transform jTrExec +#define jtransform_perfect_transform jTrPerfect +#define jcopy_markers_setup jCMrkSetup +#define jcopy_markers_execute jCMrkExec +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * Codes for supported types of image transformations. + */ + +typedef enum { + JXFORM_NONE, /* no transformation */ + JXFORM_FLIP_H, /* horizontal flip */ + JXFORM_FLIP_V, /* vertical flip */ + JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */ + JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */ + JXFORM_ROT_90, /* 90-degree clockwise rotation */ + JXFORM_ROT_180, /* 180-degree rotation */ + JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */ +} JXFORM_CODE; + +/* + * Codes for crop parameters, which can individually be unspecified, + * positive, or negative. (Negative width or height makes no sense, though.) + */ + +typedef enum { + JCROP_UNSET, + JCROP_POS, + JCROP_NEG +} JCROP_CODE; + +/* + * Transform parameters struct. + * NB: application must not change any elements of this struct after + * calling jtransform_request_workspace. + */ + +typedef struct { + /* Options: set by caller */ + JXFORM_CODE transform; /* image transform operator */ + boolean perfect; /* if TRUE, fail if partial MCUs are requested */ + boolean trim; /* if TRUE, trim partial MCUs as needed */ + boolean force_grayscale; /* if TRUE, convert color image to grayscale */ + boolean crop; /* if TRUE, crop source image */ + + /* Crop parameters: application need not set these unless crop is TRUE. + * These can be filled in by jtransform_parse_crop_spec(). + */ + JDIMENSION crop_width; /* Width of selected region */ + JCROP_CODE crop_width_set; + JDIMENSION crop_height; /* Height of selected region */ + JCROP_CODE crop_height_set; + JDIMENSION crop_xoffset; /* X offset of selected region */ + JCROP_CODE crop_xoffset_set; /* (negative measures from right edge) */ + JDIMENSION crop_yoffset; /* Y offset of selected region */ + JCROP_CODE crop_yoffset_set; /* (negative measures from bottom edge) */ + + /* Internal workspace: caller should not touch these */ + int num_components; /* # of components in workspace */ + jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */ + JDIMENSION output_width; /* cropped destination dimensions */ + JDIMENSION output_height; + JDIMENSION x_crop_offset; /* destination crop offsets measured in iMCUs */ + JDIMENSION y_crop_offset; + int iMCU_sample_width; /* destination iMCU size */ + int iMCU_sample_height; +} jpeg_transform_info; + + +#if TRANSFORMS_SUPPORTED + +/* Parse a crop specification (written in X11 geometry style) */ +EXTERN(boolean) jtransform_parse_crop_spec + JPP((jpeg_transform_info *info, const char *spec)); +/* Request any required workspace */ +EXTERN(boolean) jtransform_request_workspace + JPP((j_decompress_ptr srcinfo, jpeg_transform_info *info)); +/* Adjust output image parameters */ +EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); +/* Execute the actual transformation, if any */ +EXTERN(void) jtransform_execute_transform + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); +/* Determine whether lossless transformation is perfectly + * possible for a specified image and transformation. + */ +EXTERN(boolean) jtransform_perfect_transform + JPP((JDIMENSION image_width, JDIMENSION image_height, + int MCU_width, int MCU_height, + JXFORM_CODE transform)); + +/* jtransform_execute_transform used to be called + * jtransform_execute_transformation, but some compilers complain about + * routine names that long. This macro is here to avoid breaking any + * old source code that uses the original name... + */ +#define jtransform_execute_transformation jtransform_execute_transform + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* + * Support for copying optional markers from source to destination file. + */ + +typedef enum { + JCOPYOPT_NONE, /* copy no optional markers */ + JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */ + JCOPYOPT_ALL /* copy all optional markers */ +} JCOPY_OPTION; + +#define JCOPYOPT_DEFAULT JCOPYOPT_COMMENTS /* recommended default */ + +/* Setup decompression object to save desired markers in memory */ +EXTERN(void) jcopy_markers_setup + JPP((j_decompress_ptr srcinfo, JCOPY_OPTION option)); +/* Copy markers saved in the given source object to the destination object */ +EXTERN(void) jcopy_markers_execute + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option)); + + +} // namespace DigiKam + +#endif // TRANSUPP_H + +#else // JPEG_LIB_VERSION >= 80 + +/* + * transupp.h + * + * Copyright (C) 1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for image transformation routines and + * other utility code used by the jpegtran sample application. These are + * NOT part of the core JPEG library. But we keep these routines separate + * from jpegtran.c to ease the task of maintaining jpegtran-like programs + * that have other user interfaces. + * + * NOTE: all the routines declared here have very specific requirements + * about when they are to be executed during the reading and writing of the + * source and destination files. See the comments in transupp.c, or see + * jpegtran.c for an example of correct usage. + */ + +#ifndef TRANSUPP_H +#define TRANSUPP_H + +namespace Digikam +{ + +/* If you happen not to want the image transform support, disable it here */ +#ifndef TRANSFORMS_SUPPORTED +#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */ +#endif + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jtransform_request_workspace jTrRequest +#define jtransform_adjust_parameters jTrAdjust +#define jtransform_execute_transformation jTrExec +#define jcopy_markers_setup jCMrkSetup +#define jcopy_markers_execute jCMrkExec +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * Codes for supported types of image transformations. + */ + +typedef enum { + JXFORM_NONE, /* no transformation */ + JXFORM_FLIP_H, /* horizontal flip */ + JXFORM_FLIP_V, /* vertical flip */ + JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */ + JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */ + JXFORM_ROT_90, /* 90-degree clockwise rotation */ + JXFORM_ROT_180, /* 180-degree rotation */ + JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */ +} JXFORM_CODE; + +/* + * Although rotating and flipping data expressed as DCT coefficients is not + * hard, there is an asymmetry in the JPEG format specification for images + * whose dimensions aren't multiples of the iMCU size. The right and bottom + * image edges are padded out to the next iMCU boundary with junk data; but + * no padding is possible at the top and left edges. If we were to flip + * the whole image including the pad data, then pad garbage would become + * visible at the top and/or left, and real pixels would disappear into the + * pad margins --- perhaps permanently, since encoders & decoders may not + * bother to preserve DCT blocks that appear to be completely outside the + * nominal image area. So, we have to exclude any partial iMCUs from the + * basic transformation. + * + * Transpose is the only transformation that can handle partial iMCUs at the + * right and bottom edges completely cleanly. flip_h can flip partial iMCUs + * at the bottom, but leaves any partial iMCUs at the right edge untouched. + * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched. + * The other transforms are defined as combinations of these basic transforms + * and process edge blocks in a way that preserves the equivalence. + * + * The "trim" option causes untransformable partial iMCUs to be dropped; + * this is not strictly lossless, but it usually gives the best-looking + * result for odd-size images. Note that when this option is active, + * the expected mathematical equivalences between the transforms may not hold. + * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim + * followed by -rot 180 -trim trims both edges.) + * + * We also offer a "force to grayscale" option, which simply discards the + * chrominance channels of a YCbCr image. This is lossless in the sense that + * the luminance channel is preserved exactly. It's not the same kind of + * thing as the rotate/flip transformations, but it's convenient to handle it + * as part of this package, mainly because the transformation routines have to + * be aware of the option to know how many components to work on. + */ + +typedef struct { + /* Options: set by caller */ + JXFORM_CODE transform; /* image transform operator */ + boolean trim; /* if TRUE, trim partial MCUs as needed */ + boolean force_grayscale; /* if TRUE, convert color image to grayscale */ + + /* Internal workspace: caller should not touch these */ + int num_components; /* # of components in workspace */ + jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */ +} jpeg_transform_info; + + +#if TRANSFORMS_SUPPORTED + +/* Request any required workspace */ +EXTERN(void) jtransform_request_workspace + JPP((j_decompress_ptr srcinfo, jpeg_transform_info *info)); +/* Adjust output image parameters */ +EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); +/* Execute the actual transformation, if any */ +EXTERN(void) jtransform_execute_transformation + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* + * Support for copying optional markers from source to destination file. + */ + +typedef enum { + JCOPYOPT_NONE, /* copy no optional markers */ + JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */ + JCOPYOPT_ALL /* copy all optional markers */ +} JCOPY_OPTION; + +#define JCOPYOPT_DEFAULT JCOPYOPT_COMMENTS /* recommended default */ + +/* Setup decompression object to save desired markers in memory */ +EXTERN(void) jcopy_markers_setup + JPP((j_decompress_ptr srcinfo, JCOPY_OPTION option)); +/* Copy markers saved in the given source object to the destination object */ +EXTERN(void) jcopy_markers_execute + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option)); + +} // namespace DigiKam + +#endif // TRANSUPP_H + +#endif // JPEG_LIB_VERSION >= 80 diff --git a/src/libs/levels/Makefile.am b/src/libs/levels/Makefile.am new file mode 100644 index 00000000..d86db7c3 --- /dev/null +++ b/src/libs/levels/Makefile.am @@ -0,0 +1,17 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = liblevels.la + +liblevels_la_SOURCES = imagelevels.cpp + +liblevels_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_TQT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor + +INCLUDES = -I$(top_srcdir)/src/libs/dimg \ + -I$(top_srcdir)/src/libs/histogram \ + -I$(top_srcdir)/src/ \ + -I$(top_srcdir)/src/digikam \ + $(all_includes) + +digikaminclude_HEADERS = imagelevels.h + +digikamincludedir = $(includedir)/digikam diff --git a/src/libs/levels/imagelevels.cpp b/src/libs/levels/imagelevels.cpp new file mode 100644 index 00000000..ac995927 --- /dev/null +++ b/src/libs/levels/imagelevels.cpp @@ -0,0 +1,715 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-07-29 + * Description : image levels manipulation methods. + * + * Copyright (C) 2004-2008 by Gilles Caulier + * + * Some code parts are inspired from gimp 2.0 + * app/base/levels.c, gimplut.c, and app/base/gimpleveltool.c + * source files. + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +// TQt includes. + +#include + +// C++ includes. + +#include +#include +#include +#include +#include + +// Local includes. + +#include "ddebug.h" +#include "imagehistogram.h" +#include "imagelevels.h" + +namespace Digikam +{ + +class ImageLevelsPriv +{ + +public: + + enum PixelType + { + RedPixel = 0, + GreenPixel, + BluePixel, + AlphaPixel + }; + + struct _Levels + { + double gamma[5]; + + int low_input[5]; + int high_input[5]; + + int low_output[5]; + int high_output[5]; + }; + + struct _Lut + { + unsigned short **luts; + int nchannels; + }; + +public: + + ImageLevelsPriv() + { + levels = 0; + lut = 0; + dirty = false; + } + + // Levels data. + struct _Levels *levels; + + // Lut data. + struct _Lut *lut; + + bool sixteenBit; + bool dirty; +}; + +ImageLevels::ImageLevels(bool sixteenBit) +{ + d = new ImageLevelsPriv; + d->lut = new ImageLevelsPriv::_Lut; + d->levels = new ImageLevelsPriv::_Levels; + d->sixteenBit = sixteenBit; + + memset(d->levels, 0, sizeof(struct ImageLevelsPriv::_Levels)); + d->lut->luts = NULL; + d->lut->nchannels = 0; + + reset(); +} + +ImageLevels::~ImageLevels() +{ + if (d->lut) + { + if (d->lut->luts) + { + for (int i = 0 ; i < d->lut->nchannels ; i++) + delete [] d->lut->luts[i]; + + delete [] d->lut->luts; + } + + delete d->lut; + } + + if (d->levels) + delete d->levels; + + delete d; +} + +bool ImageLevels::isDirty() +{ + return d->dirty; +} + +bool ImageLevels::isSixteenBits() +{ + return d->sixteenBit; +} + +void ImageLevels::reset() +{ + for (int channel = 0 ; channel < 5 ; channel++) + levelsChannelReset(channel); +} + +void ImageLevels::levelsChannelReset(int channel) +{ + if (!d->levels) return; + + d->levels->gamma[channel] = 1.0; + d->levels->low_input[channel] = 0; + d->levels->high_input[channel] = d->sixteenBit ? 65535 : 255; + d->levels->low_output[channel] = 0; + d->levels->high_output[channel] = d->sixteenBit ? 65535 : 255; + d->dirty = false; +} + +void ImageLevels::levelsAuto(ImageHistogram *hist) +{ + if (!d->levels || !hist) return; + + levelsChannelReset(ImageHistogram::ValueChannel); + + for (int channel = ImageHistogram::RedChannel ; + channel <= ImageHistogram::BlueChannel ; + channel++) + { + levelsChannelAuto(hist, channel); + } + d->dirty = true; +} + +void ImageLevels::levelsChannelAuto(ImageHistogram *hist, int channel) +{ + int i; + double count, new_count, percentage, next_percentage; + + if (!d->levels || !hist) return; + + d->levels->gamma[channel] = 1.0; + d->levels->low_output[channel] = 0; + d->levels->high_output[channel] = d->sixteenBit ? 65535 : 255; + + count = hist->getCount(channel, 0, d->sixteenBit ? 65535 : 255); + + if (count == 0.0) + { + d->levels->low_input[channel] = 0; + d->levels->high_input[channel] = 0; + } + else + { + // Set the low input + + new_count = 0.0; + + for (i = 0 ; i < (d->sixteenBit ? 65535 : 255) ; i++) + { + new_count += hist->getValue(channel, i); + percentage = new_count / count; + next_percentage = (new_count + hist->getValue(channel, i + 1)) / count; + + if (fabs (percentage - 0.006) < fabs (next_percentage - 0.006)) + { + d->levels->low_input[channel] = i + 1; + break; + } + } + + // Set the high input + + new_count = 0.0; + + for (i = (d->sixteenBit ? 65535 : 255) ; i > 0 ; i--) + { + new_count += hist->getValue(channel, i); + percentage = new_count / count; + next_percentage = (new_count + hist->getValue(channel, i - 1)) / count; + + if (fabs (percentage - 0.006) < fabs (next_percentage - 0.006)) + { + d->levels->high_input[channel] = i - 1; + break; + } + } + } + d->dirty = true; +} + +int ImageLevels::levelsInputFromColor(int channel, const DColor& color) +{ + switch (channel) + { + case ImageHistogram::ValueChannel: + return TQMAX (TQMAX (color.red(), color.green()), color.blue()); + + case ImageHistogram::RedChannel: + return color.red(); + + case ImageHistogram::GreenChannel: + return color.green(); + + case ImageHistogram::BlueChannel: + return color.blue(); + } + + return 0; // just to please the compiler. +} + +void ImageLevels::levelsBlackToneAdjustByColors(int channel, const DColor& color) +{ + if (!d->levels) return; + + d->levels->low_input[channel] = levelsInputFromColor(channel, color); + d->dirty = true; +} + +void ImageLevels::levelsWhiteToneAdjustByColors(int channel, const DColor& color) +{ + if (!d->levels) return; + + d->levels->high_input[channel] = levelsInputFromColor(channel, color); + d->dirty = true; +} + +void ImageLevels::levelsGrayToneAdjustByColors(int channel, const DColor& color) +{ + if (!d->levels) return; + + int input; + int range; + double inten; + double out_light; + unsigned short lightness; + + // Calculate lightness value. + + lightness = (unsigned short)LEVELS_RGB_INTENSITY (color.red(), color.green(), color.blue()); + + input = levelsInputFromColor(channel, color); + + range = d->levels->high_input[channel] - d->levels->low_input[channel]; + + if (range <= 0) + return; + + input -= d->levels->low_input[channel]; + + if (input < 0) + return; + + // Normalize input and lightness. + + inten = (double) input / (double) range; + out_light = (double) lightness/ (double) range; + + if (out_light <= 0) + return; + + // Map selected color to corresponding lightness. + + d->levels->gamma[channel] = log (inten) / log (out_light); + d->dirty = true; +} + +void ImageLevels::levelsCalculateTransfers() +{ + double inten; + int i, j; + + if (!d->levels) return; + + // Recalculate the levels arrays. + + for (j = 0 ; j < 5 ; j++) + { + for (i = 0; i <= (d->sixteenBit ? 65535 : 255); i++) + { + // determine input intensity. + + if (d->levels->high_input[j] != d->levels->low_input[j]) + { + inten = ((double) (i - d->levels->low_input[j]) / + (double) (d->levels->high_input[j] - d->levels->low_input[j])); + } + else + { + inten = (double) (i - d->levels->low_input[j]); + } + + inten = CLAMP (inten, 0.0, 1.0); + + if (d->levels->gamma[j] != 0.0) + inten = pow (inten, (1.0 / d->levels->gamma[j])); + } + } +} + +float ImageLevels::levelsLutFunc(int n_channels, int channel, float value) +{ + double inten; + int j; + + if (!d->levels) return 0.0; + + if (n_channels == 1) + j = 0; + else + j = channel + 1; + + inten = value; + + // For color images this runs through the loop with j = channel +1 + // the first time and j = 0 the second time. + // + // For bw images this runs through the loop with j = 0 the first and + // only time. + + for ( ; j >= 0 ; j -= (channel + 1) ) + { + // Don't apply the overall curve to the alpha channel. + + if (j == 0 && (n_channels == 2 || n_channels == 4) + && channel == n_channels -1) + return inten; + + // Determine input intensity. + + if (d->levels->high_input[j] != d->levels->low_input[j]) + inten = ((double) ((float)(d->sixteenBit ? 65535 : 255) * inten - d->levels->low_input[j]) / + (double) (d->levels->high_input[j] - d->levels->low_input[j])); + else + inten = (double) ((float)(d->sixteenBit ? 65535 : 255) * inten - d->levels->low_input[j]); + + if (d->levels->gamma[j] != 0.0) + { + if (inten >= 0.0) + inten = pow ( inten, (1.0 / d->levels->gamma[j])); + else + inten = -pow (-inten, (1.0 / d->levels->gamma[j])); + } + + // determine the output intensity. + + if (d->levels->high_output[j] >= d->levels->low_output[j]) + inten = (double) (inten * (d->levels->high_output[j] - + d->levels->low_output[j]) + d->levels->low_output[j]); + + else if (d->levels->high_output[j] < d->levels->low_output[j]) + inten = (double) (d->levels->low_output[j] - inten * + (d->levels->low_output[j] - d->levels->high_output[j])); + + inten /= (float)(d->sixteenBit ? 65535 : 255); + } + + return inten; +} + +void ImageLevels::levelsLutSetup(int nchannels) +{ + int i; + uint v; + double val; + + if (d->lut->luts) + { + for (i = 0 ; i < d->lut->nchannels ; i++) + delete [] d->lut->luts[i]; + + delete [] d->lut->luts; + } + + d->lut->nchannels = nchannels; + d->lut->luts = new unsigned short*[d->lut->nchannels]; + + for (i = 0 ; i < d->lut->nchannels ; i++) + { + d->lut->luts[i] = new unsigned short[(d->sixteenBit ? 65535 : 255) + 1]; + + for (v = 0 ; v <= (d->sixteenBit ? 65535 : 255) ; v++) + { + // to add gamma correction use func(v ^ g) ^ 1/g instead. + + val = (float)(d->sixteenBit ? 65535 : 255) * + levelsLutFunc( d->lut->nchannels, i, v/(float)(d->sixteenBit ? 65535 : 255)) + 0.5; + + d->lut->luts[i][v] = (unsigned short)CLAMP (val, 0, (d->sixteenBit ? 65535 : 255)); + } + } +} + +void ImageLevels::levelsLutProcess(uchar *srcPR, uchar *destPR, int w, int h) +{ + unsigned short *lut0 = NULL, *lut1 = NULL, *lut2 = NULL, *lut3 = NULL; + + int i; + + if (d->lut->nchannels > 0) + lut0 = d->lut->luts[0]; + if (d->lut->nchannels > 1) + lut1 = d->lut->luts[1]; + if (d->lut->nchannels > 2) + lut2 = d->lut->luts[2]; + if (d->lut->nchannels > 3) + lut3 = d->lut->luts[3]; + + if (!d->sixteenBit) // 8 bits image. + { + uchar red, green, blue, alpha; + uchar *ptr = srcPR; + uchar *dst = destPR; + + for (i = 0 ; i < w*h ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + alpha = ptr[3]; + + if ( d->lut->nchannels > 0 ) + red = lut0[red]; + + if ( d->lut->nchannels > 1 ) + green = lut1[green]; + + if ( d->lut->nchannels > 2 ) + blue = lut2[blue]; + + if ( d->lut->nchannels > 3 ) + alpha = lut3[alpha]; + + dst[0] = blue; + dst[1] = green; + dst[2] = red; + dst[3] = alpha; + + ptr += 4; + dst += 4; + } + } + else // 16 bits image. + { + unsigned short red, green, blue, alpha; + unsigned short *ptr = (unsigned short *)srcPR; + unsigned short *dst = (unsigned short *)destPR; + + for (i = 0 ; i < w*h ; i++) + { + blue = ptr[0]; + green = ptr[1]; + red = ptr[2]; + alpha = ptr[3]; + + if ( d->lut->nchannels > 0 ) + red = lut0[red]; + + if ( d->lut->nchannels > 1 ) + green = lut1[green]; + + if ( d->lut->nchannels > 2 ) + blue = lut2[blue]; + + if ( d->lut->nchannels > 3 ) + alpha = lut3[alpha]; + + dst[0] = blue; + dst[1] = green; + dst[2] = red; + dst[3] = alpha; + + ptr += 4; + dst += 4; + } + } +} + +void ImageLevels::setLevelGammaValue(int Channel, double val) +{ + if ( d->levels && Channel>=0 && Channel<5 ) + { + d->levels->gamma[Channel] = val; + d->dirty = true; + } +} + +void ImageLevels::setLevelLowInputValue(int Channel, int val) +{ + if ( d->levels && Channel>=0 && Channel<5 ) + { + d->levels->low_input[Channel] = val; + d->dirty = true; + } +} + +void ImageLevels::setLevelHighInputValue(int Channel, int val) +{ + if ( d->levels && Channel>=0 && Channel<5 ) + { + d->levels->high_input[Channel] = val; + d->dirty = true; + } +} + +void ImageLevels::setLevelLowOutputValue(int Channel, int val) +{ + if ( d->levels && Channel>=0 && Channel<5 ) + { + d->levels->low_output[Channel] = val; + d->dirty = true; + } +} + +void ImageLevels::setLevelHighOutputValue(int Channel, int val) +{ + if ( d->levels && Channel>=0 && Channel<5 ) + { + d->levels->high_output[Channel] = val; + d->dirty = true; + } +} + +double ImageLevels::getLevelGammaValue(int Channel) +{ + if ( d->levels && Channel>=0 && Channel<5 ) + return (d->levels->gamma[Channel]); + + return 0.0; +} + +int ImageLevels::getLevelLowInputValue(int Channel) +{ + if ( d->levels && Channel>=0 && Channel<5 ) + return (d->levels->low_input[Channel]); + + return 0; +} + +int ImageLevels::getLevelHighInputValue(int Channel) +{ + if ( d->levels && Channel>=0 && Channel<5 ) + return (d->levels->high_input[Channel]); + + return 0; +} + +int ImageLevels::getLevelLowOutputValue(int Channel) +{ + if ( d->levels && Channel>=0 && Channel<5 ) + return (d->levels->low_output[Channel]); + + return 0; +} + +int ImageLevels::getLevelHighOutputValue(int Channel) +{ + if ( d->levels && Channel>=0 && Channel<5 ) + return (d->levels->high_output[Channel]); + + return 0; +} + +bool ImageLevels::loadLevelsFromGimpLevelsFile(const KURL& fileUrl) +{ + // TODO : support KURL ! + + FILE *file; + int low_input[5]; + int high_input[5]; + int low_output[5]; + int high_output[5]; + double gamma[5]; + int i, fields; + char buf[50]; + char *nptr; + + file = fopen(TQFile::encodeName(fileUrl.path()), "r"); + + if (!file) + return false; + + if (! fgets (buf, sizeof (buf), file)) + { + fclose(file); + return false; + } + + if (strcmp (buf, "# GIMP Levels File\n") != 0) + { + fclose(file); + return false; + } + + for (i = 0 ; i < 5 ; i++) + { + fields = fscanf (file, "%d %d %d %d ", + &low_input[i], + &high_input[i], + &low_output[i], + &high_output[i]); + + if (fields != 4) + { + DWarning() << "Invalid Gimp levels file!" << endl; + fclose(file); + return false; + } + + if (!fgets (buf, 50, file)) + { + DWarning() << "Invalid Gimp levels file!" << endl; + fclose(file); + return false; + } + + gamma[i] = strtod (buf, &nptr); + + if (buf == nptr || errno == ERANGE) + { + DWarning() << "Invalid Gimp levels file!" << endl; + fclose(file); + return false; + } + } + + for (i = 0 ; i < 5 ; i++) + { + setLevelGammaValue(i, gamma[i]); + setLevelLowInputValue(i, d->sixteenBit ? low_input[i]*255 : low_input[i]); + setLevelHighInputValue(i, d->sixteenBit ? high_input[i]*255 : high_input[i]); + setLevelLowOutputValue(i, d->sixteenBit ? low_output[i]*255 : low_output[i]); + setLevelHighOutputValue(i, d->sixteenBit ? high_output[i]*255 : high_output[i]); + } + + fclose(file); + return true; +} + +bool ImageLevels::saveLevelsToGimpLevelsFile(const KURL& fileUrl) +{ + // TODO : support KURL ! + + FILE *file; + int i; + + file = fopen(TQFile::encodeName(fileUrl.path()), "w"); + + if (!file) + return false; + + fprintf (file, "# GIMP Levels File\n"); + + for (i = 0 ; i < 5 ; i++) + { + char buf[256]; + sprintf (buf, "%f", getLevelGammaValue(i)); + + fprintf (file, "%d %d %d %d %s\n", + d->sixteenBit ? getLevelLowInputValue(i)/255 : getLevelLowInputValue(i), + d->sixteenBit ? getLevelHighInputValue(i)/255 : getLevelHighInputValue(i), + d->sixteenBit ? getLevelLowOutputValue(i)/255 : getLevelLowOutputValue(i), + d->sixteenBit ? getLevelHighInputValue(i)/255 : getLevelHighInputValue(i), + buf); + } + + fflush(file); + fclose(file); + + return true; +} + +} // NameSpace Digikam diff --git a/src/libs/levels/imagelevels.h b/src/libs/levels/imagelevels.h new file mode 100644 index 00000000..04d10d97 --- /dev/null +++ b/src/libs/levels/imagelevels.h @@ -0,0 +1,105 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-07-29 + * Description : image levels manipulation methods. + * + * Copyright (C) 2004-2008 by Gilles Caulier + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#ifndef IMAGELEVELS_H +#define IMAGELEVELS_H + +#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) + +/* Map RGB to intensity */ + +#define LEVELS_RGB_INTENSITY_RED 0.30 +#define LEVELS_RGB_INTENSITY_GREEN 0.59 +#define LEVELS_RGB_INTENSITY_BLUE 0.11 +#define LEVELS_RGB_INTENSITY(r,g,b) ((r) * LEVELS_RGB_INTENSITY_RED + \ + (g) * LEVELS_RGB_INTENSITY_GREEN + \ + (b) * LEVELS_RGB_INTENSITY_BLUE) + +// KDE includes. + +#include + +// Local includes. + +#include "dcolor.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class ImageLevelsPriv; +class ImageHistogram; + +class DIGIKAM_EXPORT ImageLevels +{ + +public: + + ImageLevels(bool sixteenBit); + ~ImageLevels(); + + bool isDirty(); + bool isSixteenBits(); + void reset(); + + // Methods for to manipulate the levels data. + + void levelsChannelReset(int channel); + void levelsAuto(ImageHistogram *hist); + void levelsChannelAuto(ImageHistogram *hist, int channel); + int levelsInputFromColor(int channel, const DColor& color); + void levelsBlackToneAdjustByColors(int channel, const DColor& color); + void levelsGrayToneAdjustByColors(int channel, const DColor& color); + void levelsWhiteToneAdjustByColors(int channel, const DColor& color); + void levelsCalculateTransfers(); + float levelsLutFunc(int n_channels, int channel, float value); + void levelsLutSetup(int nchannels); + void levelsLutProcess(uchar *srcPR, uchar *destPR, int w, int h); + + // Methods for to set manually the levels values. + + void setLevelGammaValue(int Channel, double val); + void setLevelLowInputValue(int Channel, int val); + void setLevelHighInputValue(int Channel, int val); + void setLevelLowOutputValue(int Channel, int val); + void setLevelHighOutputValue(int Channel, int val); + + double getLevelGammaValue(int Channel); + int getLevelLowInputValue(int Channel); + int getLevelHighInputValue(int Channel); + int getLevelLowOutputValue(int Channel); + int getLevelHighOutputValue(int Channel); + + // Methods for to save/load the levels values to/from a Gimp levels text file. + + bool saveLevelsToGimpLevelsFile(const KURL& fileUrl); + bool loadLevelsFromGimpLevelsFile(const KURL& fileUrl); + +private: + + ImageLevelsPriv* d; +}; + +} // NameSpace Digikam + +#endif /* IMAGELEVELS_H */ diff --git a/src/libs/lprof/Makefile.am b/src/libs/lprof/Makefile.am new file mode 100644 index 00000000..e7251d2c --- /dev/null +++ b/src/libs/lprof/Makefile.am @@ -0,0 +1,15 @@ +# Gilles Caulier 12/01/06: lprof implementation is in C not C++ and do not +# support 'nofinal' compilation option. +KDE_OPTIONS = nofinal + +INCLUDES = $(all_includes) + +noinst_LTLIBRARIES = liblprof.la + +noinst_HEADERS = lcmsprf.h + +liblprof_la_CXXFLAGS = -w -fomit-frame-pointer + +liblprof_la_SOURCES = cmshull.cpp cmslm.cpp cmslnr.cpp cmsmatn.cpp \ + cmsmkmsh.cpp cmsmntr.cpp cmsoutl.cpp cmspcoll.cpp \ + cmsprf.cpp cmsreg.cpp cmsscn.cpp cmssheet.cpp diff --git a/src/libs/lprof/cmshull.cpp b/src/libs/lprof/cmshull.cpp new file mode 100644 index 00000000..05b8dfa3 --- /dev/null +++ b/src/libs/lprof/cmshull.cpp @@ -0,0 +1,1480 @@ +/* */ +/* Little cms - profiler construction set */ +/* Copyright (C) 1998-2001 Marti Maria */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */ +/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */ +/* */ +/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */ +/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */ +/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */ +/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */ +/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */ +/* OF THIS SOFTWARE. */ +/* */ +/* This file is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ +/* General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* */ +/* As a special exception to the GNU General Public License, if you */ +/* distribute this file as part of a program that contains a */ +/* configuration script generated by Autoconf, you may include it under */ +/* the same distribution terms that you use for the rest of that program. */ +/* */ +/* Version 1.09a */ + + +#include "lcmsprf.h" + +/* Convex hull management */ + +LCMSHANDLE cdecl cmsxHullInit(void); +void cdecl cmsxHullDone(LCMSHANDLE hHull); +BOOL cdecl cmsxHullAddPoint(LCMSHANDLE hHull, int x, int y, int z); +BOOL cdecl cmsxHullComputeHull(LCMSHANDLE hHull); +char cdecl cmsxHullCheckpoint(LCMSHANDLE hHull, int x, int y, int z); +BOOL cdecl cmsxHullDumpVRML(LCMSHANDLE hHull, const char* fname); + +/* --------------------------------------------------------------------- */ + + + +/* This method is described in "Computational Geometry in C" Chapter 4. */ +/* */ +/* -------------------------------------------------------------------- */ +/* This code is Copyright 1998 by Joseph O'Rourke. It may be freely */ +/* redistributed in its entirety provided that this copyright notice is */ +/* not removed. */ +/* -------------------------------------------------------------------- */ + +#define SWAP(t,x,y) { t = x; x = y; y = t; } + +#define XFREE(p) +/* if (p) { free ((char *) p); p = NULL; } */ + + +#define ADD( head, p ) if ( head ) { \ + p->Next = head; \ + p->Prev = head->Prev; \ + head->Prev = p; \ + p->Prev->Next = p; \ + } \ + else { \ + head = p; \ + head->Next = head->Prev = p; \ + } + +#define XDELETE( head, p ) if ( head ) { \ + if ( head == head->Next ) \ + head = NULL; \ + else if ( p == head ) \ + head = head->Next; \ + p->Next->Prev = p->Prev; \ + p->Prev->Next = p->Next; \ + XFREE( p ); \ + } + +/* Define Vertex indices. */ +#define X 0 +#define Y 1 +#define Z 2 + +/* Define structures for vertices, edges and faces */ + +typedef struct _vertex_struct VERTEX,FAR *LPVERTEX; +typedef struct _edge_struct EDGE, FAR *LPEDGE; +typedef struct _face_struct FACE, FAR *LPFACE; + + +struct _edge_struct { + + LPFACE AdjFace[2]; + LPVERTEX EndPts[2]; + LPFACE NewFace; /* pointer to incident cone face. */ + BOOL DoDelete; /* T iff Edge should be delete. */ + + LPEDGE Next, Prev; +}; + +struct _face_struct { + + LPEDGE Edge[3]; + LPVERTEX Vertex[3]; + BOOL Visible; /* T iff face Visible from new point. */ + + LPFACE Next, Prev; +}; + +struct _vertex_struct { + + int v[3]; + int vnum; + LPEDGE duplicate; /* pointer to incident cone Edge (or NULL) */ + BOOL onhull; /* T iff point on hull. */ + BOOL mark; /* T iff point already processed. */ + + LPVERTEX Next, Prev; +}; + +/* Define flags */ + +#define ONHULL true +#define REMOVED true +#define VISIBLE true +#define PROCESSED true +#define SAFE 1000000 /* Range of safe coord values. */ + +#define DIM 3 /* Dimension of points */ +typedef int VEC3I[DIM]; /* Type integer point */ + +#define PMAX 10000 /* Max # of pts */ + +typedef struct { + + /* Global variable definitions */ + LPVERTEX vertices; + LPEDGE edges; + LPFACE faces; + + VEC3I Vertices[PMAX]; /* All the points */ + VEC3I Faces[PMAX]; /* Each triangle face is 3 indices */ + VEC3I Box[PMAX][2]; /* Box around each face */ + + + VEC3I bmin, bmax; + int radius; + int vnumCounter; + + int nfaces; + int nvertex; + +} HULL, FAR* LPHULL; + +/* static HULL Global; */ + + +/*--------------------------------------------------------------------- +MakeNullVertex: Makes a Vertex, nulls out fields. +---------------------------------------------------------------------*/ + +static +LPVERTEX MakeNullVertex(LPHULL hull) +{ + LPVERTEX v; + + v = (LPVERTEX) malloc(sizeof(VERTEX)); + if (!v) return NULL; + + v->duplicate = NULL; + v->onhull = !ONHULL; + v->mark = !PROCESSED; + ADD( hull->vertices, v ); + + return v; +} + + + +/*--------------------------------------------------------------------- +MakeNullEdge creates a new cell and initializes all pointers to NULL +and sets all flags to off. It returns a pointer to the empty cell. +---------------------------------------------------------------------*/ +static +LPEDGE MakeNullEdge(LPHULL hull) +{ + LPEDGE e; + + e = (LPEDGE) malloc(sizeof(EDGE)); + if (!e) return NULL; + + e->AdjFace[0] = e->AdjFace[1] = e->NewFace = NULL; + e->EndPts[0] = e->EndPts[1] = NULL; + e->DoDelete = !REMOVED; + ADD( hull->edges, e ); + return e; +} + +/*-------------------------------------------------------------------- +MakeNullFace creates a new face structure and initializes all of its +flags to NULL and sets all the flags to off. It returns a pointer +to the empty cell. +---------------------------------------------------------------------*/ +static +LPFACE MakeNullFace(LPHULL hull) +{ + LPFACE f; + int i; + + f = (LPFACE) malloc(sizeof(FACE)); + if (!f) return NULL; + + for ( i=0; i < 3; ++i ) { + f->Edge[i] = NULL; + f->Vertex[i] = NULL; + } + f->Visible = !VISIBLE; + ADD( hull->faces, f ); + return f; +} + + + +/*--------------------------------------------------------------------- +MakeFace creates a new face structure from three vertices (in ccw +order). It returns a pointer to the face. +---------------------------------------------------------------------*/ +static +LPFACE MakeFace(LPHULL hull, LPVERTEX v0, LPVERTEX v1, LPVERTEX v2, LPFACE fold) +{ + LPFACE f; + LPEDGE e0, e1, e2; + + /* Create edges of the initial triangle. */ + if( !fold ) { + e0 = MakeNullEdge(hull); + e1 = MakeNullEdge(hull); + e2 = MakeNullEdge(hull); + } + else { /* Copy from fold, in reverse order. */ + e0 = fold->Edge[2]; + e1 = fold->Edge[1]; + e2 = fold->Edge[0]; + } + e0->EndPts[0] = v0; e0->EndPts[1] = v1; + e1->EndPts[0] = v1; e1->EndPts[1] = v2; + e2->EndPts[0] = v2; e2->EndPts[1] = v0; + + /* Create face for triangle. */ + f = MakeNullFace(hull); + f->Edge[0] = e0; f->Edge[1] = e1; f->Edge[2] = e2; + f->Vertex[0] = v0; f->Vertex[1] = v1; f->Vertex[2] = v2; + + /* Link edges to face. */ + e0->AdjFace[0] = e1->AdjFace[0] = e2->AdjFace[0] = f; + + return f; +} + +/*--------------------------------------------------------------------- +Collinear checks to see if the three points given are collinear, +by checking to see if each element of the cross product is zero. +---------------------------------------------------------------------*/ +static +BOOL Collinear( LPVERTEX a, LPVERTEX b, LPVERTEX c ) +{ + return + ( c->v[Z] - a->v[Z] ) * ( b->v[Y] - a->v[Y] ) - + ( b->v[Z] - a->v[Z] ) * ( c->v[Y] - a->v[Y] ) == 0 + && ( b->v[Z] - a->v[Z] ) * ( c->v[X] - a->v[X] ) - + ( b->v[X] - a->v[X] ) * ( c->v[Z] - a->v[Z] ) == 0 + && ( b->v[X] - a->v[X] ) * ( c->v[Y] - a->v[Y] ) - + ( b->v[Y] - a->v[Y] ) * ( c->v[X] - a->v[X] ) == 0 ; +} + +/*--------------------------------------------------------------------- +VolumeSign returns the sign of the volume of the tetrahedron determined by f +and p. VolumeSign is +1 iff p is on the negative side of f, +where the positive side is determined by the rh-rule. So the volume +is positive if the ccw normal to f points outside the tetrahedron. +The final fewer-multiplications form is due to Bob Williamson. +---------------------------------------------------------------------*/ +int VolumeSign( LPFACE f, LPVERTEX p ) +{ + double vol; + double ax, ay, az, bx, by, bz, cx, cy, cz; + + ax = f->Vertex[0]->v[X] - p->v[X]; + ay = f->Vertex[0]->v[Y] - p->v[Y]; + az = f->Vertex[0]->v[Z] - p->v[Z]; + bx = f->Vertex[1]->v[X] - p->v[X]; + by = f->Vertex[1]->v[Y] - p->v[Y]; + bz = f->Vertex[1]->v[Z] - p->v[Z]; + cx = f->Vertex[2]->v[X] - p->v[X]; + cy = f->Vertex[2]->v[Y] - p->v[Y]; + cz = f->Vertex[2]->v[Z] - p->v[Z]; + + vol = ax * (by*cz - bz*cy) + + ay * (bz*cx - bx*cz) + + az * (bx*cy - by*cx); + + + /* The volume should be an integer. */ + if ( vol > 0.5 ) return 1; + else if ( vol < -0.5 ) return -1; + else return 0; +} + + + +/*--------------------------------------------------------------------- +CleanEdges runs through the Edge list and cleans up the structure. +If there is a NewFace then it will put that face in place of the +Visible face and NULL out NewFace. It also deletes so marked edges. +---------------------------------------------------------------------*/ +static +void CleanEdges(LPHULL hull) +{ + LPEDGE e; /* Primary index into Edge list. */ + LPEDGE t; /* Temporary Edge pointer. */ + + /* Integrate the NewFace's into the data structure. */ + /* Check every Edge. */ + + e = hull ->edges; + do { + if ( e->NewFace ) { + + if ( e->AdjFace[0]->Visible ) + e->AdjFace[0] = e->NewFace; + else + e->AdjFace[1] = e->NewFace; + + e->NewFace = NULL; + } + + e = e->Next; + + } while ( e != hull ->edges ); + + /* Delete any edges marked for deletion. */ + while ( hull ->edges && hull ->edges->DoDelete ) { + + e = hull ->edges; + + XDELETE( hull ->edges, e ); + } + + e = hull ->edges->Next; + + do { + if ( e->DoDelete ) { + + t = e; + e = e->Next; + XDELETE( hull ->edges, t ); + } + else e = e->Next; + + } while ( e != hull ->edges ); +} + +/*--------------------------------------------------------------------- +CleanFaces runs through the face list and deletes any face marked Visible. +---------------------------------------------------------------------*/ +static +void CleanFaces(LPHULL hull) +{ + LPFACE f; /* Primary pointer into face list. */ + LPFACE t; /* Temporary pointer, for deleting. */ + + + while ( hull ->faces && hull ->faces->Visible ) { + + f = hull ->faces; + XDELETE( hull ->faces, f ); + } + + f = hull ->faces->Next; + + do { + if ( f->Visible ) { + + t = f; + f = f->Next; + XDELETE( hull ->faces, t ); + } + else f = f->Next; + + } while ( f != hull ->faces ); +} + + + +/*--------------------------------------------------------------------- +CleanVertices runs through the Vertex list and deletes the +vertices that are marked as processed but are not incident to any +undeleted edges. +---------------------------------------------------------------------*/ +static +void CleanVertices(LPHULL hull) +{ + LPEDGE e; + LPVERTEX v, t; + + /* Mark all vertices incident to some undeleted Edge as on the hull. */ + + e = hull ->edges; + do { + e->EndPts[0]->onhull = e->EndPts[1]->onhull = ONHULL; + e = e->Next; + + } while (e != hull ->edges); + + + /* Delete all vertices that have been processed but + are not on the hull. */ + + while ( hull ->vertices && hull->vertices->mark && !hull ->vertices->onhull ) { + + v = hull ->vertices; + XDELETE(hull ->vertices, v ); + } + + + v = hull ->vertices->Next; + do { + if (v->mark && !v->onhull ) { + t = v; + v = v->Next; + XDELETE(hull ->vertices, t ) + } + else + v = v->Next; + + } while ( v != hull ->vertices ); + + + /* Reset flags. */ + + v = hull ->vertices; + do { + v->duplicate = NULL; + v->onhull = !ONHULL; + v = v->Next; + + } while (v != hull->vertices ); + +} + + + + +/*--------------------------------------------------------------------- +MakeCcw puts the vertices in the face structure in counterclock wise +order. We want to store the vertices in the same +order as in the Visible face. The third Vertex is always p. + +Although no specific ordering of the edges of a face are used +by the code, the following condition is maintained for each face f: +one of the two endpoints of f->Edge[i] matches f->Vertex[i]. +But note that this does not imply that f->Edge[i] is between +f->Vertex[i] and f->Vertex[(i+1)%3]. (Thanks to Bob Williamson.) +---------------------------------------------------------------------*/ + +static +void MakeCcw(LPFACE f, LPEDGE e, LPVERTEX p) +{ + LPFACE fv; /* The Visible face adjacent to e */ + int i; /* Index of e->endpoint[0] in fv. */ + LPEDGE s; /* Temporary, for swapping */ + + if (e->AdjFace[0]->Visible) + + fv = e->AdjFace[0]; + else + fv = e->AdjFace[1]; + + /* Set Vertex[0] & [1] of f to have the same orientation + as do the corresponding vertices of fv. */ + + for ( i=0; fv->Vertex[i] != e->EndPts[0]; ++i ) + ; + + /* Orient f the same as fv. */ + + if ( fv->Vertex[ (i+1) % 3 ] != e->EndPts[1] ) { + + f->Vertex[0] = e->EndPts[1]; + f->Vertex[1] = e->EndPts[0]; + } + else { + f->Vertex[0] = e->EndPts[0]; + f->Vertex[1] = e->EndPts[1]; + SWAP( s, f->Edge[1], f->Edge[2] ); + } + + /* This swap is tricky. e is Edge[0]. Edge[1] is based on endpt[0], + Edge[2] on endpt[1]. So if e is oriented "forwards," we + need to move Edge[1] to follow [0], because it precedes. */ + + f->Vertex[2] = p; +} + +/*--------------------------------------------------------------------- +MakeConeFace makes a new face and two new edges between the +Edge and the point that are passed to it. It returns a pointer to +the new face. +---------------------------------------------------------------------*/ + +static +LPFACE MakeConeFace(LPHULL hull, LPEDGE e, LPVERTEX p) +{ + LPEDGE new_edge[2]; + LPFACE new_face; + int i, j; + + /* Make two new edges (if don't already exist). */ + + for ( i=0; i < 2; ++i ) + /* If the Edge exists, copy it into new_edge. */ + if ( !( new_edge[i] = e->EndPts[i]->duplicate) ) { + + /* Otherwise (duplicate is NULL), MakeNullEdge. */ + new_edge[i] = MakeNullEdge(hull); + new_edge[i]->EndPts[0] = e->EndPts[i]; + new_edge[i]->EndPts[1] = p; + e->EndPts[i]->duplicate = new_edge[i]; + } + + /* Make the new face. */ + new_face = MakeNullFace(hull); + new_face->Edge[0] = e; + new_face->Edge[1] = new_edge[0]; + new_face->Edge[2] = new_edge[1]; + MakeCcw( new_face, e, p ); + + /* Set the adjacent face pointers. */ + for ( i=0; i < 2; ++i ) + for ( j=0; j < 2; ++j ) + /* Only one NULL link should be set to new_face. */ + if ( !new_edge[i]->AdjFace[j] ) { + new_edge[i]->AdjFace[j] = new_face; + break; + } + + return new_face; +} + + +/*--------------------------------------------------------------------- +AddOne is passed a Vertex. It first determines all faces Visible from +that point. If none are Visible then the point is marked as not +onhull. Next is a loop over edges. If both faces adjacent to an Edge +are Visible, then the Edge is marked for deletion. If just one of the +adjacent faces is Visible then a new face is constructed. +---------------------------------------------------------------------*/ +static +BOOL AddOne(LPHULL hull, LPVERTEX p) +{ + LPFACE f; + LPEDGE e, temp; + int vol; + BOOL vis = false; + + + /* Mark faces Visible from p. */ + f = hull -> faces; + + do { + + vol = VolumeSign(f, p); + + if ( vol < 0 ) { + f->Visible = VISIBLE; + vis = true; + } + + f = f->Next; + + } while ( f != hull ->faces ); + + /* If no faces are Visible from p, then p is inside the hull. */ + + if ( !vis ) { + + p->onhull = !ONHULL; + return false; + } + + /* Mark edges in interior of Visible region for deletion. + Erect a NewFace based on each border Edge. */ + + e = hull ->edges; + + do { + + temp = e->Next; + + if ( e->AdjFace[0]->Visible && e->AdjFace[1]->Visible ) + /* e interior: mark for deletion. */ + e->DoDelete = REMOVED; + + else + if ( e->AdjFace[0]->Visible || e->AdjFace[1]->Visible ) + /* e border: make a new face. */ + e->NewFace = MakeConeFace(hull, e, p ); + + e = temp; + + } while ( e != hull ->edges ); + + return true; +} + + +/*--------------------------------------------------------------------- + DoubleTriangle builds the initial double triangle. It first finds 3 + noncollinear points and makes two faces out of them, in opposite order. + It then finds a fourth point that is not coplanar with that face. The + vertices are stored in the face structure in counterclockwise order so + that the volume between the face and the point is negative. Lastly, the + 3 newfaces to the fourth point are constructed and the data structures + are cleaned up. +---------------------------------------------------------------------*/ + +static +BOOL DoubleTriangle(LPHULL hull) +{ + LPVERTEX v0, v1, v2, v3; + LPFACE f0, f1 = NULL; + int vol; + + /* Find 3 noncollinear points. */ + v0 = hull ->vertices; + while ( Collinear( v0, v0->Next, v0->Next->Next ) ) + if ( ( v0 = v0->Next ) == hull->vertices ) + return false; /* All points are Collinear! */ + + v1 = v0->Next; + v2 = v1->Next; + + /* Mark the vertices as processed. */ + v0->mark = PROCESSED; + v1->mark = PROCESSED; + v2->mark = PROCESSED; + + /* Create the two "twin" faces. */ + f0 = MakeFace(hull, v0, v1, v2, f1 ); + f1 = MakeFace(hull, v2, v1, v0, f0 ); + + /* Link adjacent face fields. */ + f0->Edge[0]->AdjFace[1] = f1; + f0->Edge[1]->AdjFace[1] = f1; + f0->Edge[2]->AdjFace[1] = f1; + f1->Edge[0]->AdjFace[1] = f0; + f1->Edge[1]->AdjFace[1] = f0; + f1->Edge[2]->AdjFace[1] = f0; + + /* Find a fourth, noncoplanar point to form tetrahedron. */ + v3 = v2->Next; + vol = VolumeSign( f0, v3 ); + + while ( !vol ) { + + if ( ( v3 = v3->Next ) == v0 ) + return false; /* All points are coplanar! */ + + vol = VolumeSign( f0, v3 ); + } + + /* Insure that v3 will be the first added. */ + hull ->vertices = v3; + return true; +} + + + +/*--------------------------------------------------------------------- +ConstructHull adds the vertices to the hull one at a time. The hull +vertices are those in the list marked as onhull. +---------------------------------------------------------------------*/ +static +void ConstructHull(LPHULL hull) +{ + LPVERTEX v, vnext; + BOOL changed; /* T if addition changes hull; not used. */ + + v = hull->vertices; + + do { + vnext = v->Next; + + changed = false; + + if (!v->mark ) { + + v->mark = PROCESSED; + changed = AddOne(hull, v ); + + CleanEdges(hull); + CleanFaces(hull); + CleanVertices(hull); + } + + v = vnext; + + } while (v != hull->vertices ); + +} + + + +/*-------------------------------------------------------------------*/ + + +static +void AddVec( VEC3I q, VEC3I ray ) +{ + int i; + + for( i = 0; i < DIM; i++ ) + ray[i] = q[i] + ray[i]; +} + +/*--------------------------------------------------------------------- +a - b ==> c. +---------------------------------------------------------------------*/ +static +void SubVec( VEC3I a, VEC3I b, VEC3I c ) +{ + int i; + + for( i = 0; i < DIM; i++ ) + c[i] = a[i] - b[i]; +} + + +/*--------------------------------------------------------------------- +Returns the dot product of the two input vectors. +---------------------------------------------------------------------*/ +static +double Dot( VEC3I a, LPVEC3 b ) +{ + int i; + double sum = 0.0; + + for( i = 0; i < DIM; i++ ) + sum += a[i] * b->n[i]; + + return sum; +} + +/*--------------------------------------------------------------------- +Compute the cross product of (b-a)x(c-a) and place into N. +---------------------------------------------------------------------*/ +static +void NormalVec( VEC3I a, VEC3I b, VEC3I c, LPVEC3 N ) +{ + N->n[X] = ( c[Z] - a[Z] ) * ( b[Y] - a[Y] ) - + ( b[Z] - a[Z] ) * ( c[Y] - a[Y] ); + N->n[Y] = ( b[Z] - a[Z] ) * ( c[X] - a[X] ) - + ( b[X] - a[X] ) * ( c[Z] - a[Z] ); + N->n[Z] = ( b[X] - a[X] ) * ( c[Y] - a[Y] ) - + ( b[Y] - a[Y] ) * ( c[X] - a[X] ); +} + + + + +static +int InBox( VEC3I q, VEC3I bmin, VEC3I bmax ) +{ + + if( ( bmin[X] <= q[X] ) && ( q[X] <= bmax[X] ) && + ( bmin[Y] <= q[Y] ) && ( q[Y] <= bmax[Y] ) && + ( bmin[Z] <= q[Z] ) && ( q[Z] <= bmax[Z] ) ) + return true; + + return false; +} + + + +/* + This function returns a char: + '0': the segment [ab] does not intersect (completely misses) the + bounding box surrounding the n-th triangle T. It lies + strictly to one side of one of the six supporting planes. + '?': status unknown: the segment may or may not intersect T. +*/ + +static +char BoxTest(LPHULL hull, int n, VEC3I a, VEC3I b) +{ + int i; /* Coordinate index */ + int w; + + for ( i=0; i < DIM; i++ ) { + + w = hull ->Box[ n ][0][i]; /* min: lower left */ + + if ( (a[i] < w) && (b[i] < w) ) return '0'; + + w = hull ->Box[ n ][1][i]; /* max: upper right */ + + if ( (a[i] > w) && (b[i] > w) ) return '0'; + } + + return '?'; +} + + + +/* Return a random ray endpoint */ + +static +void RandomRay( VEC3I ray, int radius ) +{ + double x, y, z, w, t; + + /* Generate a random point on a sphere of radius 1. */ + /* the sphere is sliced at z, and a random point at angle t + generated on the circle of intersection. */ + + z = 2.0 * (double) rand() / RAND_MAX - 1.0; + t = 2.0 * M_PI * (double) rand() / RAND_MAX; + w = sqrt( 1 - z*z ); + x = w * cos( t ); + y = w * sin( t ); + + ray[X] = (int) ( radius * x ); + ray[Y] = (int) ( radius * y ); + ray[Z] = (int) ( radius * z ); +} + + + +static +int ComputeBox(LPHULL hull, int F, VEC3I bmin, VEC3I bmax ) +{ + int i, j; + double radius; + + for( i = 0; i < F; i++ ) + for( j = 0; j < DIM; j++ ) { + + if( hull ->Vertices[i][j] < bmin[j] ) + bmin[j] = hull ->Vertices[i][j]; + + if( hull ->Vertices[i][j] > bmax[j] ) + bmax[j] = hull ->Vertices[i][j]; + } + + radius = sqrt( pow( (double)(bmax[X] - bmin[X]), 2.0 ) + + pow( (double)(bmax[Y] - bmin[Y]), 2.0 ) + + pow( (double)(bmax[Z] - bmin[Z]), 2.0 ) ); + + return (int)( radius +1 ) + 1; +} + + +/*--------------------------------------------------------------------- +Computes N & D and returns index m of largest component. +---------------------------------------------------------------------*/ +static +int PlaneCoeff(LPHULL hull, VEC3I T, LPVEC3 N, double *D ) +{ + int i; + double t; /* Temp storage */ + double biggest = 0.0; /* Largest component of normal vector. */ + int m = 0; /* Index of largest component. */ + + NormalVec(hull ->Vertices[T[0]], hull ->Vertices[T[1]], hull ->Vertices[T[2]], N ); + *D = Dot( hull ->Vertices[T[0]], N ); + + /* Find the largest component of N. */ + for ( i = 0; i < DIM; i++ ) { + t = fabs( N->n[i] ); + if ( t > biggest ) { + biggest = t; + m = i; + } + } + return m; +} + +/*--------------------------------------------------------------------- + 'p': The segment lies wholly within the plane. + 'q': The q endpoint is on the plane (but not 'p'). + 'r': The r endpoint is on the plane (but not 'p'). + '0': The segment lies strictly to one side or the other of the plane. + '1': The segement intersects the plane, and 'p' does not hold. +---------------------------------------------------------------------*/ +static +char SegPlaneInt(LPHULL hull, VEC3I T, VEC3I q, VEC3I r, LPVEC3 p, int *m) +{ + VEC3 N; double D; + VEC3I rq; + double num, denom, t; + int i; + + *m = PlaneCoeff(hull, T, &N, &D ); + num = D - Dot( q, &N ); + SubVec( r, q, rq ); + denom = Dot( rq, &N ); + + if ( denom == 0.0 ) { /* Segment is parallel to plane. */ + if ( num == 0.0 ) /* q is on plane. */ + return 'p'; + else + return '0'; + } + else + t = num / denom; + + for( i = 0; i < DIM; i++ ) + p->n[i] = q[i] + t * ( r[i] - q[i] ); + + if ( (0.0 < t) && (t < 1.0) ) + return '1'; + else if ( num == 0.0 ) /* t == 0 */ + return 'q'; + else if ( num == denom ) /* t == 1 */ + return 'r'; + else return '0'; +} + + + +static +int AreaSign( VEC3I a, VEC3I b, VEC3I c ) +{ + double area2; + + area2 = ( b[0] - a[0] ) * (double)( c[1] - a[1] ) - + ( c[0] - a[0] ) * (double)( b[1] - a[1] ); + + /* The area should be an integer. */ + if ( area2 > 0.5 ) return 1; + else if ( area2 < -0.5 ) return -1; + else return 0; +} + + +static +char InTri2D( VEC3I Tp[3], VEC3I pp ) +{ + int area0, area1, area2; + + /* compute three AreaSign() values for pp w.r.t. each Edge of the face in 2D */ + area0 = AreaSign( pp, Tp[0], Tp[1] ); + area1 = AreaSign( pp, Tp[1], Tp[2] ); + area2 = AreaSign( pp, Tp[2], Tp[0] ); + + if ( (( area0 == 0 ) && ( area1 > 0 ) && ( area2 > 0 )) || + (( area1 == 0 ) && ( area0 > 0 ) && ( area2 > 0 )) || + (( area2 == 0 ) && ( area0 > 0 ) && ( area1 > 0 )) ) + return 'E'; + + if ( (( area0 == 0 ) && ( area1 < 0 ) && ( area2 < 0 )) || + (( area1 == 0 ) && ( area0 < 0 ) && ( area2 < 0 )) || + (( area2 == 0 ) && ( area0 < 0 ) && ( area1 < 0 ))) + return 'E'; + + if ( (( area0 > 0 ) && ( area1 > 0 ) && ( area2 > 0 )) || + (( area0 < 0 ) && ( area1 < 0 ) && ( area2 < 0 ))) + return 'F'; + + if ( ( area0 == 0 ) && ( area1 == 0 ) && ( area2 == 0 ) ) + return '?'; /* Error in InTriD */ + + if ( (( area0 == 0 ) && ( area1 == 0 )) || + (( area0 == 0 ) && ( area2 == 0 )) || + (( area1 == 0 ) && ( area2 == 0 )) ) + return 'V'; + + else + return '0'; +} + +/* Assumption: p lies in the plane containing T. + Returns a char: + 'V': the query point p coincides with a Vertex of triangle T. + 'E': the query point p is in the relative interior of an Edge of triangle T. + 'F': the query point p is in the relative interior of a Face of triangle T. + '0': the query point p does not intersect (misses) triangle T. +*/ + +static +char InTri3D(LPHULL hull, VEC3I T, int m, VEC3I p ) +{ + int i; /* Index for X,Y,Z */ + int j; /* Index for X,Y */ + int k; /* Index for triangle Vertex */ + VEC3I pp; /* projected p */ + VEC3I Tp[3]; /* projected T: three new vertices */ + + /* Project out coordinate m in both p and the triangular face */ + j = 0; + for ( i = 0; i < DIM; i++ ) { + if ( i != m ) { /* skip largest coordinate */ + pp[j] = p[i]; + for ( k = 0; k < 3; k++ ) + Tp[k][j] = hull->Vertices[T[k]][i]; + j++; + } + } + return( InTri2D( Tp, pp ) ); +} + + + +static +int VolumeSign2( VEC3I a, VEC3I b, VEC3I c, VEC3I d ) +{ + double vol; + double ax, ay, az, bx, by, bz, cx, cy, cz, dx, dy, dz; + double bxdx, bydy, bzdz, cxdx, cydy, czdz; + + ax = a[X]; + ay = a[Y]; + az = a[Z]; + bx = b[X]; + by = b[Y]; + bz = b[Z]; + cx = c[X]; + cy = c[Y]; + cz = c[Z]; + dx = d[X]; + dy = d[Y]; + dz = d[Z]; + + bxdx=bx-dx; + bydy=by-dy; + bzdz=bz-dz; + cxdx=cx-dx; + cydy=cy-dy; + czdz=cz-dz; + vol = (az-dz) * (bxdx*cydy - bydy*cxdx) + + (ay-dy) * (bzdz*cxdx - bxdx*czdz) + + (ax-dx) * (bydy*czdz - bzdz*cydy); + + + /* The volume should be an integer. */ + if ( vol > 0.5 ) return 1; + else if ( vol < -0.5 ) return -1; + else return 0; +} + + + + +/*--------------------------------------------------------------------- +The signed volumes of three tetrahedra are computed, determined +by the segment qr, and each Edge of the triangle. +Returns a char: + 'v': the open segment includes a Vertex of T. + 'e': the open segment includes a point in the relative interior of an Edge + of T. + 'f': the open segment includes a point in the relative interior of a face + of T. + '0': the open segment does not intersect triangle T. +---------------------------------------------------------------------*/ + +static +char SegTriCross(LPHULL hull, VEC3I T, VEC3I q, VEC3I r ) +{ + int vol0, vol1, vol2; + + vol0 = VolumeSign2( q, hull->Vertices[ T[0] ], hull->Vertices[ T[1] ], r ); + vol1 = VolumeSign2( q, hull->Vertices[ T[1] ], hull->Vertices[ T[2] ], r ); + vol2 = VolumeSign2( q, hull->Vertices[ T[2] ], hull->Vertices[ T[0] ], r ); + + + /* Same sign: segment intersects interior of triangle. */ + if ( ( ( vol0 > 0 ) && ( vol1 > 0 ) && ( vol2 > 0 ) ) || + ( ( vol0 < 0 ) && ( vol1 < 0 ) && ( vol2 < 0 ) ) ) + return 'f'; + + /* Opposite sign: no intersection between segment and triangle */ + if ( ( ( vol0 > 0 ) || ( vol1 > 0 ) || ( vol2 > 0 ) ) && + ( ( vol0 < 0 ) || ( vol1 < 0 ) || ( vol2 < 0 ) ) ) + return '0'; + + else if ( ( vol0 == 0 ) && ( vol1 == 0 ) && ( vol2 == 0 ) ) + return '?'; /* Error 1 in SegTriCross */ + + /* Two zeros: segment intersects Vertex. */ + else if ( ( ( vol0 == 0 ) && ( vol1 == 0 ) ) || + ( ( vol0 == 0 ) && ( vol2 == 0 ) ) || + ( ( vol1 == 0 ) && ( vol2 == 0 ) ) ) + return 'v'; + + /* One zero: segment intersects Edge. */ + else if ( ( vol0 == 0 ) || ( vol1 == 0 ) || ( vol2 == 0 ) ) + return 'e'; + + else + return '?'; /* Error 2 in SegTriCross */ +} + + + +static +char SegTriInt(LPHULL hull, VEC3I T, VEC3I q, VEC3I r, LPVEC3 p ) +{ + int code; + int m = -1; + + code = SegPlaneInt(hull, T, q, r, p, &m ); + + if ( code == '0') return '0'; + else if ( code == 'q') return InTri3D(hull, T, m, q ); + else if ( code == 'r') return InTri3D(hull, T, m, r ); + else if ( code == 'p') return 'p'; + else if ( code == '1' ) return SegTriCross(hull, T, q, r ); + else + return code; /* Error */ +} + + + + +/* + This function returns a char: + 'i': the query point a is strictly interior to polyhedron P. + 'o': the query point a is strictly exterior to( or outside of) polyhedron P. +*/ +char InPolyhedron(LPHULL hull, VEC3I q) +{ + int F = hull->nfaces; + VEC3I Ray; /* Ray endpoint. */ + VEC3 p; /* Intersection point; not used. */ + int f, k = 0, crossings = 0; + char code = '?'; + + + /* If query point is outside bounding box, finished. */ + if ( !InBox( q, hull->bmin, hull->bmax ) ) + return 'o'; + + LOOP: + while( k++ < F ) { + + crossings = 0; + + RandomRay(Ray, hull->radius ); + + AddVec( q, Ray ); + + + for ( f = 0; f < F; f++ ) { /* Begin check each face */ + + if ( BoxTest(hull, f, q, Ray ) == '0' ) { + code = '0'; + + } + else code = SegTriInt(hull, hull->Faces[f], q, Ray, &p ); + + + /* If ray is degenerate, then goto outer while to generate another. */ + if ( code == 'p' || code == 'v' || code == 'e' ) { + + goto LOOP; + } + + /* If ray hits face at interior point, increment crossings. */ + else if ( code == 'f' ) { + crossings++; + + } + + /* If query endpoint q sits on a V/E/F, return inside. */ + else if ( code == 'V' || code == 'E' || code == 'F' ) + return code; /* 'i'; MM2 */ + + /* If ray misses triangle, do nothing. */ + else if ( code == '0' ) + ; + + else + return '?'; /* Error */ + + } + /* No degeneracies encountered: ray is generic, so finished. */ + break; + + } /* End while loop */ + + + /* q strictly interior to polyhedron iff an odd number of crossings. */ + if( ( crossings % 2 ) == 1 ) + return 'i'; + + else return 'o'; +} + + +/*/ ---------------------------------------------------------------------------------- */ + + + + +static +void StoreResults(LPHULL hull) +{ + + int i, w; + LPVERTEX v; + LPFACE f; + int V = 0, F = 0; + int j, k; + + /* Vertices */ + + v = hull ->vertices; + V = 0; + do { + + v -> vnum = V; + hull ->Vertices[V][X] = v -> v[X]; + hull ->Vertices[V][Y] = v -> v[Y]; + hull ->Vertices[V][Z] = v -> v[Z]; + + v = v->Next; + V++; + + } while ( v != hull ->vertices ); + + hull ->nvertex = V; + + /* Faces */ + f = hull ->faces; + F = 0; + do { + + hull ->Faces[F][0] = f->Vertex[0]->vnum; + hull ->Faces[F][1] = f->Vertex[1]->vnum; + hull ->Faces[F][2] = f->Vertex[2]->vnum; + + for ( j=0; j < 3; j++ ) { + + hull ->Box[F][0][j] = hull ->Vertices[ hull ->Faces[F][0] ][j]; + hull ->Box[F][1][j] = hull ->Vertices[ hull ->Faces[F][0] ][j]; + } + + /* Check k=1,2 vertices of face. */ + for ( k=1; k < 3; k++ ) + for ( j=0; j < 3; j++ ) { + + w = hull ->Vertices[ hull ->Faces[F][k] ][j]; + if ( w < hull ->Box[F][0][j] ) hull ->Box[F][0][j] = w; + if ( w > hull ->Box[F][1][j] ) hull ->Box[F][1][j] = w; + } + + + f = f->Next; F++; + + } while ( f != hull ->faces ); + + + hull ->nfaces = F; + + + /* Initialize the bounding box */ + for ( i = 0; i < DIM; i++ ) + hull ->bmin[i] = hull ->bmax[i] = hull ->Vertices[0][i]; + + hull ->radius = ComputeBox(hull, V, hull ->bmin, hull ->bmax ); + + +} + + +LCMSHANDLE cmsxHullInit(void) +{ + LPHULL hull = (LPHULL) malloc(sizeof(HULL)); + + ZeroMemory(hull, sizeof(HULL)); + + hull->vnumCounter = 0; + hull->vertices = NULL; + hull->edges = NULL; + hull->faces = NULL; + hull->nfaces = 0; + hull->nvertex = 0; + + return (LCMSHANDLE) (LPSTR) hull; +} + + +void cmsxHullDone(LCMSHANDLE hHull) +{ + LPHULL hull = (LPHULL) (LPSTR) hHull; + + if (hull) + free((LPVOID) hull); +} + + +BOOL cmsxHullAddPoint(LCMSHANDLE hHull, int x, int y, int z) +{ + LPVERTEX v; + LPHULL hull = (LPHULL) (LPSTR) hHull; + + + v = MakeNullVertex(hull); + v->v[X] = x; + v->v[Y] = y; + v->v[Z] = z; + v->vnum = hull->vnumCounter++; + + return true; +} + +BOOL cmsxHullComputeHull(LCMSHANDLE hHull) +{ + + LPHULL hull = (LPHULL) (LPSTR) hHull; + + if (!DoubleTriangle(hull)) return false; + + ConstructHull(hull); + StoreResults(hull); + + return true; +} + + +char cmsxHullCheckpoint(LCMSHANDLE hHull, int x, int y, int z) +{ + VEC3I q; + LPHULL hull = (LPHULL) (LPSTR) hHull; + + q[X] = x; q[Y] = y; q[Z] = z; + + return InPolyhedron(hull, q ) ; +} + + +BOOL cmsxHullDumpVRML(LCMSHANDLE hHull, const char* fname) +{ + FILE* fp; + int i; + LPHULL hull = (LPHULL) (LPSTR) hHull; + + fp = fopen (fname, "wt"); + if (fp == NULL) + return false; + + fprintf (fp, "#VRML V2.0 utf8\n"); + + /* set the viewing orientation and distance */ + fprintf (fp, "DEF CamTest Group {\n"); + fprintf (fp, "\tchildren [\n"); + fprintf (fp, "\t\tDEF Cameras Group {\n"); + fprintf (fp, "\t\t\tchildren [\n"); + fprintf (fp, "\t\t\t\tDEF DefaultView Viewpoint {\n"); + fprintf (fp, "\t\t\t\t\tposition 0 0 340\n"); + fprintf (fp, "\t\t\t\t\torientation 0 0 1 0\n"); + fprintf (fp, "\t\t\t\t\tdescription \"default view\"\n"); + fprintf (fp, "\t\t\t\t}\n"); + fprintf (fp, "\t\t\t]\n"); + fprintf (fp, "\t\t},\n"); + fprintf (fp, "\t]\n"); + fprintf (fp, "}\n"); + + /* Output the background stuff */ + fprintf (fp, "Background {\n"); + fprintf (fp, "\tskyColor [\n"); + fprintf (fp, "\t\t.5 .5 .5\n"); + fprintf (fp, "\t]\n"); + fprintf (fp, "}\n"); + + /* Output the shape stuff */ + fprintf (fp, "Transform {\n"); + fprintf (fp, "\tscale 8 8 8\n"); + fprintf (fp, "\tchildren [\n"); + + /* Draw the axes as a shape: */ + fprintf (fp, "\t\tShape {\n"); + fprintf (fp, "\t\t\tappearance Appearance {\n"); + fprintf (fp, "\t\t\t\tmaterial Material {\n"); + fprintf (fp, "\t\t\t\t\tdiffuseColor 0 0.8 0\n"); + fprintf (fp, "\t\t\t\t\temissiveColor 1.0 1.0 1.0\n"); + fprintf (fp, "\t\t\t\t\tshininess 0.8\n"); + fprintf (fp, "\t\t\t\t}\n"); + fprintf (fp, "\t\t\t}\n"); + fprintf (fp, "\t\t\tgeometry IndexedLineSet {\n"); + fprintf (fp, "\t\t\t\tcoord Coordinate {\n"); + fprintf (fp, "\t\t\t\t\tpoint [\n"); + fprintf (fp, "\t\t\t\t\t0.0 0.0 0.0,\n"); + fprintf (fp, "\t\t\t\t\t%f 0.0 0.0,\n", 255.0); + fprintf (fp, "\t\t\t\t\t0.0 %f 0.0,\n", 255.0); + fprintf (fp, "\t\t\t\t\t0.0 0.0 %f]\n", 255.0); + fprintf (fp, "\t\t\t\t}\n"); + fprintf (fp, "\t\t\t\tcoordIndex [\n"); + fprintf (fp, "\t\t\t\t\t0, 1, -1\n"); + fprintf (fp, "\t\t\t\t\t0, 2, -1\n"); + fprintf (fp, "\t\t\t\t\t0, 3, -1]\n"); + fprintf (fp, "\t\t\t}\n"); + fprintf (fp, "\t\t}\n"); + + + /* Draw the triangles as a shape: */ + fprintf (fp, "\t\tShape {\n"); + fprintf (fp, "\t\t\tappearance Appearance {\n"); + fprintf (fp, "\t\t\t\tmaterial Material {\n"); + fprintf (fp, "\t\t\t\t\tdiffuseColor 0 0.8 0\n"); + fprintf (fp, "\t\t\t\t\temissiveColor 0 0 0\n"); + fprintf (fp, "\t\t\t\t\tshininess 0.8\n"); + fprintf (fp, "\t\t\t\t}\n"); + fprintf (fp, "\t\t\t}\n"); + fprintf (fp, "\t\t\tgeometry IndexedFaceSet {\n"); + fprintf (fp, "\t\t\t\tsolid false\n"); + + /* fill in the points here */ + fprintf (fp, "\t\t\t\tcoord Coordinate {\n"); + fprintf (fp, "\t\t\t\t\tpoint [\n"); + + for (i = 0; i < hull->nvertex; ++i) + { + fprintf (fp, "\t\t\t\t\t%g %g %g%c\n", + (double) hull->Vertices[i][X], (double) hull->Vertices[i][Y], (double) hull->Vertices[i][Z], + i == hull->nvertex-1? ']': ','); + } + fprintf (fp, "\t\t\t\t}\n"); + + /* fill in the Vertex indices (followed by -1) */ + + + fprintf (fp, "\t\t\t\tcoordIndex [\n"); + for (i = 0; i < hull->nfaces; ++i) + { + fprintf (fp, "\t\t\t\t\t%d, %d, %d, -1\n", + hull->Faces[i][0], hull->Faces[i][1], hull->Faces[i][2]); + + } + fprintf (fp, "]\n"); + + + /* fill in the face colors */ + fprintf (fp, "\t\t\t\tcolor Color {\n"); + fprintf (fp, "\t\t\t\t\tcolor [\n"); + for (i = 0; i < hull->nfaces; ++i) + { + int vx, vy, vz; + double r, g, b; + + vx = hull->Faces[i][0]; vy = hull->Faces[i][1]; vz = hull->Faces[i][2]; + r = (double) (hull->Vertices[vx][X] + hull->Vertices[vy][X] + hull->Vertices[vz][X]) / (3* 255); + g = (double) (hull->Vertices[vx][Y] + hull->Vertices[vy][Y] + hull->Vertices[vz][Y]) / (3* 255); + b = (double) (hull->Vertices[vx][Z] + hull->Vertices[vy][Z] + hull->Vertices[vz][Z]) / (3* 255); + + fprintf (fp, "\t\t\t\t\t%g %g %g%c\n", r, g, b, + i == hull->nfaces-1? ']': ','); + } + fprintf (fp, "\t\t\t}\n"); + + fprintf (fp, "\t\t\tcolorPerVertex false\n"); + + fprintf (fp, "\t\t\t}\n"); + fprintf (fp, "\t\t}\n"); + fprintf (fp, "\t]\n"); + fprintf (fp, "}\n"); + + fclose (fp); + + return true; +} diff --git a/src/libs/lprof/cmslm.cpp b/src/libs/lprof/cmslm.cpp new file mode 100644 index 00000000..81d86ba6 --- /dev/null +++ b/src/libs/lprof/cmslm.cpp @@ -0,0 +1,288 @@ +/* */ +/* Little cms - profiler construction set */ +/* Copyright (C) 1998-2001 Marti Maria */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */ +/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */ +/* */ +/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */ +/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */ +/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */ +/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */ +/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */ +/* OF THIS SOFTWARE. */ +/* */ +/* This file is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ +/* General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, ma 02110-1301, USA. */ +/* */ +/* As a special exception to the GNU General Public License, if you */ +/* distribute this file as part of a program that contains a */ +/* configuration script generated by Autoconf, you may include it under */ +/* the same distribution terms that you use for the rest of that program. */ +/* */ +/* Version 1.09a */ + +#include "lcmsprf.h" + + +/* From "numerical recipes in C" */ +/* */ +/* Levenberg-Marquardt method, attempting to reduce the value X2 of a */ +/* fit between a set of data points x[1..ndata], y[1..ndata] with individual */ +/* standard deviations sig[1..ndata], and a nonlinear function dependent */ +/* on ma coefficients a[1..ma]. The input array ia[1..ma] */ +/* indicates by nonzero entries those components of a that should be */ +/* fitted for, and by zero entries those components that should be held */ +/* fixed at their input values. The program returns current best-fitt */ +/* values for the parameters a[1..ma], and chisq. The arrays */ +/* covar[1..ma][1..ma], alpha[1..ma][1..ma] are used as */ +/* working space during most iterations. Supply a routine */ +/* funcs(x, a, yfit, dyda, ma) */ +/* that evaluates the fitting function yfit, and its derivatives dyda[1..ma] */ +/* with respect to the fitting parameters a at x. On the first call provide */ +/* an initial guess for the parameters a, and set alamda<0 for initialization */ +/* (which then sets alamda=.001). If a step succeeds chisq becomes smaller */ +/* and alamda decreases by a factor of 10. If a step fails alamda grows by */ +/* a factor of 10. You must call this routine repeatedly until convergence */ +/* is achieved. Then, make one final call with alamda=0, so that */ +/* covar[1..ma][1..ma] returns the covar matrix, and alpha the */ +/* alpha matrix. (Parameters held fixed will return zero covariances.) */ + + +LCMSHANDLE cdecl cmsxLevenbergMarquardtInit(LPSAMPLEDCURVE x, LPSAMPLEDCURVE y, double sig, + double a[], + int ma, + void (*funcs)(double, double[], double*, double[], int) + ); + +double cdecl cmsxLevenbergMarquardtAlamda(LCMSHANDLE hMRQ); +double cdecl cmsxLevenbergMarquardtChiSq(LCMSHANDLE hMRQ); +BOOL cdecl cmsxLevenbergMarquardtIterate(LCMSHANDLE hMRQ); +BOOL cdecl cmsxLevenbergMarquardtFree(LCMSHANDLE hMRQ); + +/* ---------------------------------------------------------------------------- */ + + + +typedef struct { + + LPSAMPLEDCURVE x; + LPSAMPLEDCURVE y; + int ndata; + double* a; + int ma; + LPMATN covar; + LPMATN alpha; + double* atry; + LPMATN beta; + LPMATN oneda; + double* dyda; + double ochisq; + double sig; + + + void (*funcs)(double, double[], double*, double[], int); + + double alamda; + double chisq; + +} LMRTQMIN, FAR* LPLMRTQMIN; + + + + +static +void mrqcof(LPLMRTQMIN pLM, double *a, LPMATN alpha, LPMATN beta, double *chisq) +{ + int i, j, k; + double ymod, wt, sig2i, dy; + + for(j = 0; j < pLM->ma; j++) + { + for(k = 0; k <= j; k++) + alpha->Values[j][k] = 0.0; + + beta->Values[j][0] = 0.0; + } + + *chisq = 0.0; + sig2i = 1.0 / (pLM->sig * pLM->sig); + + for(i = 0; i < pLM->ndata; i++) + { + (*(pLM->funcs))(pLM->x ->Values[i], a, &ymod, pLM->dyda, pLM->ma); + + dy = pLM->y->Values[i] - ymod; + + for(j = 0; j < pLM->ma; j++) + { + wt = pLM->dyda[j] * sig2i; + + for(k = 0; k <= j; k++) + alpha->Values[j][k] += wt * pLM->dyda[k]; + + beta->Values[j][0] += dy * wt; + } + + *chisq += dy * dy * sig2i; + } + + for(j = 1; j < pLM->ma; j++) /* Fill in the symmetric side. */ + for(k = 0; k < j; k++) + alpha->Values[k][j] = alpha->Values[j][k]; +} + + + +static +void FreeStruct(LPLMRTQMIN pLM) +{ + if(pLM == NULL) return; + + if(pLM->covar) MATNfree (pLM->covar); + if(pLM->alpha) MATNfree (pLM->alpha); + if(pLM->atry) free(pLM->atry); + if(pLM->beta) MATNfree (pLM->beta); + if(pLM->oneda) MATNfree (pLM->oneda); + if(pLM->dyda) free(pLM->dyda); + free(pLM); +} + + + +LCMSHANDLE cmsxLevenbergMarquardtInit(LPSAMPLEDCURVE x, LPSAMPLEDCURVE y, double sig, + double a[], + int ma, + void (*funcs)(double, double[], double*, double[], int)) + +{ + int i; + LPLMRTQMIN pLM; + + if (x ->nItems != y ->nItems) return NULL; + + pLM = (LPLMRTQMIN) malloc(sizeof(LMRTQMIN)); + if(!pLM) + return NULL; + + ZeroMemory(pLM, sizeof(LMRTQMIN)); + + if((pLM->atry = (double*)malloc(ma * sizeof(double))) == NULL) goto failed; + if((pLM->beta = MATNalloc (ma, 1)) == NULL) goto failed; + if((pLM->oneda = MATNalloc (ma, 1)) == NULL) goto failed; + + + + if((pLM->covar = MATNalloc(ma, ma)) == NULL) goto failed; + if((pLM->alpha = MATNalloc(ma, ma)) == NULL) goto failed; + if((pLM->dyda = (double*)malloc(ma * sizeof(double))) == NULL) goto failed; + + pLM->alamda = 0.001; + + pLM->ndata = x ->nItems; + pLM->x = x; + pLM->y = y; + pLM->ma = ma; + pLM->a = a; + pLM->funcs = funcs; + pLM->sig = sig; + + mrqcof(pLM, a, pLM->alpha, pLM->beta, &pLM->chisq); + pLM->ochisq = (pLM->chisq); + + for(i = 0; i < ma; i++) pLM->atry[i] = a[i]; + + return (LCMSHANDLE) pLM; + +failed: + FreeStruct(pLM); + return NULL; +} + + +BOOL cmsxLevenbergMarquardtFree(LCMSHANDLE hMRQ) +{ + LPLMRTQMIN pLM = (LPLMRTQMIN)hMRQ; + if(!pLM) + return false; + + FreeStruct(pLM); + return true; +} + + +BOOL cmsxLevenbergMarquardtIterate(LCMSHANDLE hMRQ) +{ + int j, k; + BOOL sts; + LPLMRTQMIN pLM = (LPLMRTQMIN)hMRQ; + if(!pLM) + return false; + + for(j = 0; j < pLM->ma; j++) /* Alter linearized fitting matrix, by augmenting diagonal elements. */ + { + for(k = 0; k < pLM->ma; k++) + pLM->covar->Values[j][k] = pLM->alpha->Values[j][k]; + + pLM->covar->Values[j][j] = pLM->alpha->Values[j][j] * (1.0 + pLM ->alamda); + pLM->oneda->Values[j][0] = pLM->beta->Values[j][0]; + } + + if((sts = MATNsolve (pLM->covar, pLM->oneda)) != true) /* Matrix solution. */ + return sts; + + for(j = 0; j < pLM->ma; j++) /* Did the trial succeed? */ + pLM->atry[j] = pLM->a[j] + pLM->oneda->Values[j][0]; + + mrqcof(pLM, pLM->atry, pLM->covar, pLM->oneda, &pLM -> chisq); + + if (pLM->chisq < pLM->ochisq) { /* Success, accept the new solution. */ + + pLM->alamda *= 0.1; + pLM->ochisq = pLM->chisq; + + for(j = 0; j < pLM->ma; j++) + { + for(k = 0; k < pLM->ma; k++) + pLM->alpha->Values[j][k] = pLM->covar->Values[j][k]; + + pLM->beta->Values[j][0] = pLM->oneda->Values[j][0]; + } + + for (j=0; j < pLM ->ma; j++) pLM->a[j] = pLM->atry[j]; + } + else /* Failure, increase alamda and return. */ + { + pLM -> alamda *= 10.0; + pLM->chisq = pLM->ochisq; + } + + return true; +} + + +double cmsxLevenbergMarquardtAlamda(LCMSHANDLE hMRQ) +{ + LPLMRTQMIN pLM = (LPLMRTQMIN)hMRQ; + + return pLM ->alamda; +} + +double cmsxLevenbergMarquardtChiSq(LCMSHANDLE hMRQ) +{ + LPLMRTQMIN pLM = (LPLMRTQMIN)hMRQ; + + return pLM ->chisq; +} diff --git a/src/libs/lprof/cmslnr.cpp b/src/libs/lprof/cmslnr.cpp new file mode 100644 index 00000000..dddd8e38 --- /dev/null +++ b/src/libs/lprof/cmslnr.cpp @@ -0,0 +1,560 @@ +/* */ +/* Little cms - profiler construction set */ +/* Copyright (C) 1998-2001 Marti Maria */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */ +/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */ +/* */ +/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */ +/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */ +/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */ +/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */ +/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */ +/* OF THIS SOFTWARE. */ +/* */ +/* This file is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ +/* General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* */ +/* As a special exception to the GNU General Public License, if you */ +/* distribute this file as part of a program that contains a */ +/* configuration script generated by Autoconf, you may include it under */ +/* the same distribution terms that you use for the rest of that program. */ +/* */ +/* Version 1.09a */ + + +#include "lcmsprf.h" + + +LPGAMMATABLE cdecl cmsxEstimateGamma(LPSAMPLEDCURVE X, LPSAMPLEDCURVE Y, int nResultingPoints); +void cdecl cmsxCompleteLabOfPatches(LPMEASUREMENT m, SETOFPATCHES Valids, int Medium); + +void cdecl cmsxComputeLinearizationTables(LPMEASUREMENT m, + int ColorSpace, + LPGAMMATABLE Lin[3], + int nResultingPoints, + int Medium); + + +void cdecl cmsxApplyLinearizationTable(double In[3], + LPGAMMATABLE Gamma[3], + double Out[3]); + +void cdecl cmsxApplyLinearizationGamma(WORD In[3], LPGAMMATABLE Gamma[3], WORD Out[3]); + + + +/* ------------------------------------------------------------- Implementation */ + + +#define EPSILON 0.00005 +#define LEVENBERG_MARQUARDT_ITERATE_MAX 150 + +/* In order to track linearization tables, we use following procedure */ +/* */ +/* We first assume R', G' and B' does exhibit a non-linear behaviour */ +/* that can be separated for each channel as Yr(R'), Yg(G'), Yb(B') */ +/* This is the shaper step */ +/* */ +/* R = Lr(R') */ +/* G = Lg(G') */ +/* B = Lb(B') (0.0) */ +/* */ +/* After this step, RGB is converted to XYZ by a matrix multiplication */ +/* */ +/* |X| |R| */ +/* |Y| = [M]·|G| */ +/* |Z| |B| (1.0) */ +/* */ +/* In order to extract Lr,Lg,Lb tables, we are interested only on Y part */ +/* */ +/* Y = (m1 * R + m2 * G + m3 * B) (1.1) */ +/* */ +/* The total intensity for maximum RGB = (1, 1, 1) should be 1, */ +/* */ +/* 1 = m1 * 1 + m2 * 1 + m3 * 1, so */ +/* */ +/* m1 + m2 + m3 = 1.0 (1.2) */ +/* */ +/* We now impose that for neutral (gray) patches, RGB components must be equal */ +/* */ +/* R = G = B = Gray */ +/* */ +/* So, substituting in (1.1): */ +/* */ +/* Y = (m1 + m2 + m3) Gray */ +/* */ +/* and for (1.2), (m1+m2+m3) = 1, so */ +/* */ +/* Y = Gray = Lr(R') = Lg(G') = Lb(B') */ +/* */ +/* That is, after prelinearization, RGB of gray patches should give */ +/* same values for R, G and B. And this value is Y. */ +/* */ +/* */ + + +static +LPSAMPLEDCURVE NormalizeTo(LPSAMPLEDCURVE X, double N, BOOL lAddEndPoint) +{ + int i, nItems; + LPSAMPLEDCURVE XNorm; + + nItems = X ->nItems; + if (lAddEndPoint) nItems++; + + XNorm = cmsAllocSampledCurve(nItems); + + for (i=0; i < X ->nItems; i++) { + + XNorm ->Values[i] = X ->Values[i] / N; + } + + if (lAddEndPoint) + XNorm -> Values[X ->nItems] = 1.0; + + return XNorm; +} + + +/* */ +/* ------------------------------------------------------------------------------ */ +/* */ +/* Our Monitor model. We assume gamma has a general expression of */ +/* */ +/* Fn(x) = (Gain * x + offset) ^ gamma | for x >= 0 */ +/* Fn(x) = 0 | for x < 0 */ +/* */ +/* First partial derivatives are */ +/* */ +/* dFn/dGamma = Fn * ln(Base) */ +/* dFn/dGain = gamma * x * ((Gain * x + Offset) ^ (gamma -1)) */ +/* dFn/dOffset = gamma * ((Gain * x + Offset) ^ (gamma -1)) */ +/* */ + +static +void GammaGainOffsetFn(double x, double *a, double *y, double *dyda, int na) +{ + double Gamma,Gain,Offset; + double Base; + + Gamma = a[0]; + Gain = a[1]; + Offset = a[2]; + + Base = Gain * x + Offset; + + if (Base < 0) { + + Base = 0.0; + *y = 0.0; + dyda[0] = 0.0; + dyda[1] = 0.0; + dyda[2] = 0.0; + + + } else { + + + /* The function itself */ + *y = pow(Base, Gamma); + + /* dyda[0] is partial derivative across Gamma */ + dyda[0] = *y * log(Base); + + /* dyda[1] is partial derivative across gain */ + dyda[1] = (x * Gamma) * pow(Base, Gamma-1.0); + + /* dyda[2] is partial derivative across offset */ + dyda[2] = Gamma * pow(Base, Gamma-1.0); + } +} + + +/* Fit curve to our gamma-gain-offset model. */ + +static +BOOL OneTry(LPSAMPLEDCURVE XNorm, LPSAMPLEDCURVE YNorm, double a[]) +{ + LCMSHANDLE h; + double ChiSq, OldChiSq; + int i; + BOOL Status = true; + + /* initial guesses */ + + a[0] = 3.0; /* gamma */ + a[1] = 4.0; /* gain */ + a[2] = 6.0; /* offset */ + a[3] = 0.0; /* Thereshold */ + a[4] = 0.0; /* Black */ + + + /* Significance = 0.02 gives good results */ + + h = cmsxLevenbergMarquardtInit(XNorm, YNorm, 0.02, a, 3, GammaGainOffsetFn); + if (h == NULL) return false; + + + OldChiSq = cmsxLevenbergMarquardtChiSq(h); + + for(i = 0; i < LEVENBERG_MARQUARDT_ITERATE_MAX; i++) { + + if (!cmsxLevenbergMarquardtIterate(h)) { + Status = false; + break; + } + + ChiSq = cmsxLevenbergMarquardtChiSq(h); + + if(OldChiSq != ChiSq && (OldChiSq - ChiSq) < EPSILON) + break; + + OldChiSq = ChiSq; + } + + cmsxLevenbergMarquardtFree(h); + + return Status; +} + +/* Tries to fit gamma as per IEC 61966-2.1 using Levenberg-Marquardt method */ +/* */ +/* Y = (aX + b)^Gamma | X >= d */ +/* Y = cX | X < d */ + +LPGAMMATABLE cmsxEstimateGamma(LPSAMPLEDCURVE X, LPSAMPLEDCURVE Y, int nResultingPoints) +{ + double a[5]; + LPSAMPLEDCURVE XNorm, YNorm; + double e, Max; + + + /* Coarse approximation, to find maximum. */ + /* We have only a portion of curve. It is likely */ + /* maximum will not fall on exactly 100. */ + + if (!OneTry(X, Y, a)) + return 0; + + /* Got parameters. Compute maximum. */ + e = a[1]* 255.0 + a[2]; + if (e < 0) return 0; + Max = pow(e, a[0]); + + + /* Normalize values to maximum */ + XNorm = NormalizeTo(X, 255.0, false); + YNorm = NormalizeTo(Y, Max, false); + + /* Do the final fitting */ + if (!OneTry(XNorm, YNorm, a)) + return 0; + + /* Type 3 = IEC 61966-2.1 (sRGB) */ + /* Y = (aX + b)^Gamma | X >= d */ + /* Y = cX | X < d */ + return cmsBuildParametricGamma(nResultingPoints, 3, a); +} + + + + + +/* A dumb bubble sort */ + +static +void Bubble(LPSAMPLEDCURVE C, LPSAMPLEDCURVE L) +{ +#define SWAP(a, b) { tmp = (a); (a) = (b); (b) = tmp; } + + BOOL lSwapped; + int i, nItems; + double tmp; + + nItems = C -> nItems; + do { + lSwapped = false; + + for (i= 0; i < nItems - 1; i++) { + + if (C->Values[i] > C->Values[i+1]) { + + SWAP(C->Values[i], C->Values[i+1]); + SWAP(L->Values[i], L->Values[i+1]); + lSwapped = true; + } + } + + } while (lSwapped); + +#undef SWAP +} + + + +/* Check for monotonicity. Force it if is not the case. */ + +static +void CheckForMonotonicSampledCurve(LPSAMPLEDCURVE t) +{ + int n = t ->nItems; + int i; + double last; + + last = t ->Values[n-1]; + for (i = n-2; i >= 0; --i) { + + if (t ->Values[i] > last) + + t ->Values[i] = last; + else + last = t ->Values[i]; + + } + +} + +/* The main gamma inferer. Tries first by gamma-gain-offset, */ +/* if not proper reverts to curve guessing. */ + +static +LPGAMMATABLE BuildGammaTable(LPSAMPLEDCURVE C, LPSAMPLEDCURVE L, int nResultingPoints) +{ + LPSAMPLEDCURVE Cw, Lw, Cn, Ln; + LPSAMPLEDCURVE out; + LPGAMMATABLE Result; + double Lmax, Lend, Cmax; + + /* Try to see if it can be fitted */ + Result = cmsxEstimateGamma(C, L, nResultingPoints); + if (Result) + return Result; + + + /* No... build curve from scratch. Since we have not */ + /* endpoints, a coarse linear extrapolation should be */ + /* applied in order to get the expected maximum. */ + + Cw = cmsDupSampledCurve(C); + Lw = cmsDupSampledCurve(L); + + Bubble(Cw, Lw); + + /* Get endpoint */ + Lmax = Lw->Values[Lw ->nItems - 1]; + Cmax = Cw->Values[Cw ->nItems - 1]; + + /* Linearly extrapolate */ + Lend = (255 * Lmax) / Cmax; + + Ln = NormalizeTo(Lw, Lend, true); + Cn = NormalizeTo(Cw, 255.0, true); + + cmsFreeSampledCurve(Cw); + cmsFreeSampledCurve(Lw); + + /* Add endpoint */ + out = cmsJoinSampledCurves(Cn, Ln, nResultingPoints); + + cmsFreeSampledCurve(Cn); + cmsFreeSampledCurve(Ln); + + CheckForMonotonicSampledCurve(out); + + cmsSmoothSampledCurve(out, nResultingPoints*4.); + cmsClampSampledCurve(out, 0, 1.0); + + Result = cmsConvertSampledCurveToGamma(out, 1.0); + + cmsFreeSampledCurve(out); + return Result; +} + + + + +void cmsxCompleteLabOfPatches(LPMEASUREMENT m, SETOFPATCHES Valids, int Medium) +{ + LPPATCH White; + cmsCIEXYZ WhiteXYZ; + int i; + + if (Medium == MEDIUM_REFLECTIVE_D50) + { + WhiteXYZ.X = D50X * 100.; + WhiteXYZ.Y = D50Y * 100.; + WhiteXYZ.Z = D50Z * 100.; + } + else { + + White = cmsxPCollFindWhite(m, Valids, NULL); + if (!White) return; + + WhiteXYZ = White ->XYZ; + } + + /* For all patches with XYZ and without Lab, add Lab values. */ + /* Transmissive profiles does need to locate its own white */ + /* point for device gray. Reflective does use D50 */ + + for (i=0; i < m -> nPatches; i++) { + + if (Valids[i]) { + + LPPATCH p = m -> Patches + i; + + if ((p ->dwFlags & PATCH_HAS_XYZ) && + (!(p ->dwFlags & PATCH_HAS_Lab) || (Medium == MEDIUM_TRANSMISSIVE))) { + + cmsXYZ2Lab(&WhiteXYZ, &p->Lab, &p->XYZ); + p -> dwFlags |= PATCH_HAS_Lab; + } + } + } +} + + +/* Compute linearization tables, trying to fit in a pure */ +/* exponential gamma. If gamma cannot be accurately infered, */ +/* then does build a smooth, monotonic curve that does the job. */ + +void cmsxComputeLinearizationTables(LPMEASUREMENT m, + int ColorSpace, + LPGAMMATABLE Lin[3], + int nResultingPoints, + int Medium) + +{ + LPSAMPLEDCURVE R, G, B, L; + LPGAMMATABLE gr, gg, gb; + SETOFPATCHES Neutrals; + int nGrays; + int i; + + /* We need Lab for grays. */ + cmsxCompleteLabOfPatches(m, m->Allowed, Medium); + + /* Add neutrals, normalize to max */ + Neutrals = cmsxPCollBuildSet(m, false); + cmsxPCollPatchesNearNeutral(m, m ->Allowed, 15, Neutrals); + + nGrays = cmsxPCollCountSet(m, Neutrals); + + R = cmsAllocSampledCurve(nGrays); + G = cmsAllocSampledCurve(nGrays); + B = cmsAllocSampledCurve(nGrays); + L = cmsAllocSampledCurve(nGrays); + + nGrays = 0; + + /* Collect patches */ + for (i=0; i < m -> nPatches; i++) { + + if (Neutrals[i]) { + + LPPATCH gr = m -> Patches + i; + + + R -> Values[nGrays] = gr -> Colorant.RGB[0]; + G -> Values[nGrays] = gr -> Colorant.RGB[1]; + B -> Values[nGrays] = gr -> Colorant.RGB[2]; + L -> Values[nGrays] = gr -> XYZ.Y; + + nGrays++; + } + + } + + + gr = BuildGammaTable(R, L, nResultingPoints); + gg = BuildGammaTable(G, L, nResultingPoints); + gb = BuildGammaTable(B, L, nResultingPoints); + + cmsFreeSampledCurve(R); + cmsFreeSampledCurve(G); + cmsFreeSampledCurve(B); + cmsFreeSampledCurve(L); + + if (ColorSpace == PT_Lab) { + + LPGAMMATABLE Gamma3 = cmsBuildGamma(nResultingPoints, 3.0); + + Lin[0] = cmsJoinGammaEx(gr, Gamma3, nResultingPoints); + Lin[1] = cmsJoinGammaEx(gg, Gamma3, nResultingPoints); + Lin[2] = cmsJoinGammaEx(gb, Gamma3, nResultingPoints); + + cmsFreeGamma(gr); cmsFreeGamma(gg); cmsFreeGamma(gb); + cmsFreeGamma(Gamma3); + } + else { + + + LPGAMMATABLE Gamma1 = cmsBuildGamma(nResultingPoints, 1.0); + + Lin[0] = cmsJoinGammaEx(gr, Gamma1, nResultingPoints); + Lin[1] = cmsJoinGammaEx(gg, Gamma1, nResultingPoints); + Lin[2] = cmsJoinGammaEx(gb, Gamma1, nResultingPoints); + + cmsFreeGamma(gr); cmsFreeGamma(gg); cmsFreeGamma(gb); + cmsFreeGamma(Gamma1); + + } + +} + + + +/* Apply linearization. WORD encoded version */ + +void cmsxApplyLinearizationGamma(WORD In[3], LPGAMMATABLE Gamma[3], WORD Out[3]) +{ + L16PARAMS Lut16; + + cmsCalcL16Params(Gamma[0] -> nEntries, &Lut16); + + Out[0] = cmsLinearInterpLUT16(In[0], Gamma[0] -> GammaTable, &Lut16); + Out[1] = cmsLinearInterpLUT16(In[1], Gamma[1] -> GammaTable, &Lut16); + Out[2] = cmsLinearInterpLUT16(In[2], Gamma[2] -> GammaTable, &Lut16); + + +} + + + +/* Apply linearization. double version */ + +void cmsxApplyLinearizationTable(double In[3], LPGAMMATABLE Gamma[3], double Out[3]) +{ + WORD rw, gw, bw; + double rd, gd, bd; + L16PARAMS Lut16; + + + cmsCalcL16Params(Gamma[0] -> nEntries, &Lut16); + + rw = (WORD) floor(_cmsxSaturate255To65535(In[0]) + .5); + gw = (WORD) floor(_cmsxSaturate255To65535(In[1]) + .5); + bw = (WORD) floor(_cmsxSaturate255To65535(In[2]) + .5); + + rd = cmsLinearInterpLUT16(rw , Gamma[0] -> GammaTable, &Lut16); + gd = cmsLinearInterpLUT16(gw, Gamma[1] -> GammaTable, &Lut16); + bd = cmsLinearInterpLUT16(bw, Gamma[2] -> GammaTable, &Lut16); + + Out[0] = _cmsxSaturate65535To255(rd); /* back to 0..255 */ + Out[1] = _cmsxSaturate65535To255(gd); + Out[2] = _cmsxSaturate65535To255(bd); +} + diff --git a/src/libs/lprof/cmsmatn.cpp b/src/libs/lprof/cmsmatn.cpp new file mode 100644 index 00000000..bca52717 --- /dev/null +++ b/src/libs/lprof/cmsmatn.cpp @@ -0,0 +1,323 @@ +/* */ +/* Little cms - profiler construction set */ +/* Copyright (C) 1998-2001 Marti Maria */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */ +/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */ +/* */ +/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */ +/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */ +/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */ +/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */ +/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */ +/* OF THIS SOFTWARE. */ +/* */ +/* This file is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ +/* General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* */ +/* As a special exception to the GNU General Public License, if you */ +/* distribute this file as part of a program that contains a */ +/* configuration script generated by Autoconf, you may include it under */ +/* the same distribution terms that you use for the rest of that program. */ +/* */ +/* Version 1.09a */ + + +#include "lcmsprf.h" + + +LPMATN cdecl MATNalloc(int Rows, int Cols); +void cdecl MATNfree (LPMATN mat); +LPMATN cdecl MATNmult(LPMATN a1, LPMATN a2); +double cdecl MATNcross(LPMATN a); +void cdecl MATNscalar (LPMATN a, double scl, LPMATN b); +LPMATN cdecl MATNtranspose (LPMATN a); +BOOL cdecl MATNsolve(LPMATN a, LPMATN b); + + +/* ------------------------------------------------------------ Implementation */ + +/* Free matrix */ + +void MATNfree(LPMATN mat) +{ + int i; + + if (mat == NULL) return; + + for (i = 0; i < mat->Rows; i++) + { + if (mat -> Values[i] != NULL) + free (mat->Values[i]); + } + + free(mat->Values); + free(mat); +} + + +/* Allocate (and Zero) a new matrix */ + +LPMATN MATNalloc(int Rows, int Cols) +{ + int i; + + LPMATN mat = (LPMATN) malloc (sizeof (MATN)); + if (mat == NULL) return mat; + + ZeroMemory(mat, sizeof(MATN)); + + mat->Rows = Rows; + mat->Cols = Cols; + mat->Values = (double**) malloc(Rows * sizeof (double*)); + + if (mat->Values == NULL) { + free(mat); + return NULL; + } + + ZeroMemory(mat -> Values, Rows * sizeof (double*)); + + for (i = 0; i < Rows; i++) + { + mat-> Values [i] = (double*) malloc(Cols * sizeof (double)); + if (mat -> Values[i] == NULL) { + MATNfree(mat); + return NULL; + } + + } + + return mat; +} + +#define DO_SWAP(a, b, tmp) { tmp = (a); (a) = (b); (b) = tmp; } + +/* Gauss-Jordan elimination. There is also a more */ +/* exahustive non-singular matrix checking part. */ + +BOOL MATNsolve(LPMATN a, LPMATN b) +{ + BOOL status; + int n = a->Rows; + int i, iCol=0, iRow=0, j, k; + double fMax, fAbs, fSave, fInf, temp; + int* aiColIndex; + int* aiRowIndex=0; + int* aiPivoted=0; + + + if (a->Rows != a->Cols) return false; + + status = false; + if((aiColIndex = (int*) malloc(n * sizeof(int))) == NULL) + goto GotError; + + if((aiRowIndex = (int*) malloc(n * sizeof(int))) == NULL) + goto GotError; + + if((aiPivoted = (int*) malloc(n * sizeof(int))) == NULL) + goto GotError; + + ZeroMemory(aiPivoted, n * sizeof(int)); + + + for(i = 0; i < n; i++) { + + /* search matrix (excluding pivoted rows) for maximum absolute entry */ + + fMax = 0.0; + for (j = 0; j < n; j++) + if (aiPivoted[j] != 1) + for (k = 0; k < n; k++) + { + fAbs = fabs(a->Values[j][k]); + if (fAbs >= fMax) { + + fMax = fAbs; + iRow = j; + iCol = k; + } + else + if (aiPivoted[k] > 1) { + + status = false; + goto GotError; + } + } + + aiPivoted[iCol]++; + + /* swap rows so that A[iCol][iCol] contains the pivot entry */ + + if (iRow != iCol) { + + for(j = 0; j < n; j++) + DO_SWAP(a->Values[iRow][j], a->Values[iCol][j], temp) + + DO_SWAP(b->Values[iRow][0], b->Values[iCol][0], temp) + } + + /* keep track of the permutations of the rows */ + + aiRowIndex[i] = iRow; + aiColIndex[i] = iCol; + + if (a->Values[iCol][iCol] == 0.0) + { + status = false; + goto GotError; + } + + /* scale the row so that the pivot entry is 1 */ + + fInf = 1.0 / a->Values[iCol][iCol]; + a->Values[iCol][iCol] = 1.0; + + for(j = 0; j < n; j++) + a->Values[iCol][j] *= fInf; + + b->Values[iCol][0] *= fInf; + + /* zero out the pivot column locations in the other rows */ + + for(j = 0; j < n; j++) + if (j != iCol) { + + fSave = a->Values[j][iCol]; + a->Values[j][iCol] = 0.0; + + for(k = 0; k < n; k++) + a->Values[j][k] -= a->Values[iCol][k] * fSave; + + b->Values[j][0] -= b->Values[iCol][0] * fSave; + } + } + + /* reorder rows so that A[][] stores the inverse of the original matrix */ + + for(i = n - 1; i >= 0; i--) { + + if(aiRowIndex[i] != aiColIndex[i]) + for(j = 0; j < n; j++) + DO_SWAP(a->Values[j][aiRowIndex[i]], a->Values[j][aiColIndex[i]], temp) + } + + status = true; + +GotError: + if(aiColIndex) free(aiColIndex); + if(aiRowIndex) free(aiRowIndex); + if(aiPivoted) free(aiPivoted); + return status; + +} + +#undef DO_SWAP + + +LPMATN MATNmult(LPMATN a1, LPMATN a2) +{ + int i, j, k; + LPMATN b; + + if (a1->Cols != a2->Rows) + return NULL; + + b = MATNalloc (a1->Rows, a2->Cols); + if (b == NULL) + return NULL; + + for (i = 0; i < b->Rows; i++) { + + for (j = 0; j < b->Cols; j++) { + + b->Values[i][j] = 0.0; + + for (k = 0; k < a1->Cols; k++) { + + b->Values[i][j] += a1->Values[i][k] * a2->Values[k][j]; + } + } + } + + return b; +} + + +double MATNcross(LPMATN a) +{ + int i; + double prod = 0.0; + + for (i = 0; i < a->Rows; i++) { + + prod += a->Values[i][0]*a->Values[i][0]; + } + return prod; +} + + +void MATNscalar(LPMATN a, double scl, LPMATN b) +{ + int i, j; + + if (a->Rows != b->Rows || a->Cols != b->Cols) + return; + + for (i = 0; i < a->Rows; i++) { + + for (j = 0; j < a->Cols; j++) + b->Values[i][j] = a->Values[i][j] * scl; + } +} + + +LPMATN MATNtranspose(LPMATN a) +{ + LPMATN b = MATNalloc(a->Cols, a->Rows); + if (b != NULL) { + + int i, j; + + for (i = 0; i < a->Rows; i++) + { + for (j = 0; j < a->Cols; j++) + b->Values[j][i] = a->Values [i][j]; + } + } + return b; +} + + + +/* Used for debug purposes */ +#ifdef DEBUG +void MATNprintf(char* name, LPMATN mat) +{ + int i, j; + + printf ("%s:\n", name); + for (i= 0; i < mat->Rows; i++) { + + printf ("%3d", i); + for (j = 0; j < mat->Cols; j++) + printf (" %.5f", mat->Values[i][j]); + printf ("\n"); + } +} +#endif + + diff --git a/src/libs/lprof/cmsmkmsh.cpp b/src/libs/lprof/cmsmkmsh.cpp new file mode 100644 index 00000000..c0e3d4c3 --- /dev/null +++ b/src/libs/lprof/cmsmkmsh.cpp @@ -0,0 +1,346 @@ +/* */ +/* Little cms - profiler construction set */ +/* Copyright (C) 1998-2001 Marti Maria */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */ +/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */ +/* */ +/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */ +/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */ +/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */ +/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */ +/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */ +/* OF THIS SOFTWARE. */ +/* */ +/* This file is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ +/* General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* */ +/* As a special exception to the GNU General Public License, if you */ +/* distribute this file as part of a program that contains a */ +/* configuration script generated by Autoconf, you may include it under */ +/* the same distribution terms that you use for the rest of that program. */ +/* */ +/* Version 1.08a */ + + +#include "lcmsprf.h" + + +BOOL cdecl cmsxComputeMatrixShaper(const char* ReferenceSheet, + const char* MeasurementSheet, + int Medium, + LPGAMMATABLE TransferCurves[3], + LPcmsCIEXYZ WhitePoint, + LPcmsCIEXYZ BlackPoint, + LPcmsCIExyYTRIPLE Primaries); + + + +/* ------------------------------------------------------------- Implementation */ + + +static +void Div100(LPcmsCIEXYZ xyz) +{ + xyz -> X /= 100; xyz -> Y /= 100; xyz -> Z /= 100; +} + + + +/* Compute endpoints */ + +static +BOOL ComputeWhiteAndBlackPoints(LPMEASUREMENT Linearized, + LPGAMMATABLE TransferCurves[3], + LPcmsCIEXYZ Black, LPcmsCIEXYZ White) +{ + + double Zeroes[3], Ones[3], lmin[3], lmax[3]; + + SETOFPATCHES Neutrals = cmsxPCollBuildSet(Linearized, false); + + cmsxPCollPatchesNearNeutral(Linearized, Linearized->Allowed, + 15, Neutrals); + + Zeroes[0] = Zeroes[1] = Zeroes[2] = 0.0; + Ones[0] = Ones[1] = Ones[2] = 255.0; + + + cmsxApplyLinearizationTable(Zeroes, TransferCurves, lmin); + cmsxApplyLinearizationTable(Ones, TransferCurves, lmax); + + + /* Global regression to find White & Black points */ + if (!cmsxRegressionInterpolatorRGB(Linearized, PT_XYZ, + 4, + true, + 12, + lmin[0], lmin[1], lmin[2], + Black)) return false; + + if (!cmsxRegressionInterpolatorRGB(Linearized, PT_XYZ, + 4, + true, + 12, + lmax[0], lmax[1], lmax[2], + White)) return false; + + _cmsxClampXYZ100(White); + _cmsxClampXYZ100(Black); + + return true; + +} + + +/* Study convergence of primary axis */ + +static +BOOL ComputePrimary(LPMEASUREMENT Linearized, + LPGAMMATABLE TransferCurves[3], + int n, + LPcmsCIExyY Primary) +{ + + double Ones[3], lmax[3]; + cmsCIEXYZ PrimXYZ; + SETOFPATCHES SetPrimary; + int nR; + + + /* At first, try to see if primaries are already in measurement */ + + SetPrimary = cmsxPCollBuildSet(Linearized, false); + nR = cmsxPCollPatchesNearPrimary(Linearized, Linearized->Allowed, + n, 32, SetPrimary); + + Ones[0] = Ones[1] = Ones[2] = 0; + Ones[n] = 255.0; + + cmsxApplyLinearizationTable(Ones, TransferCurves, lmax); + + /* Do incremental regression to find primaries */ + if (!cmsxRegressionInterpolatorRGB(Linearized, PT_XYZ, + 4, + false, + 12, + lmax[0], lmax[1], lmax[2], + &PrimXYZ)) return false; + + _cmsxClampXYZ100(&PrimXYZ); + cmsXYZ2xyY(Primary, &PrimXYZ); + return true; + + +} + + + +/* Does compute a matrix-shaper based on patches. */ + +static +double Clip(double d) +{ + return d > 0 ? d: 0; +} + + +BOOL cmsxComputeMatrixShaper(const char* ReferenceSheet, + const char* MeasurementSheet, + int Medium, + LPGAMMATABLE TransferCurves[3], + LPcmsCIEXYZ WhitePoint, + LPcmsCIEXYZ BlackPoint, + LPcmsCIExyYTRIPLE Primaries) +{ + + MEASUREMENT Linearized; + cmsCIEXYZ Black, White; + cmsCIExyYTRIPLE PrimarySet; + LPPATCH PatchWhite, PatchBlack; + LPPATCH PatchRed, PatchGreen, PatchBlue; + double Distance; + + /* Load sheets */ + + if (!cmsxPCollBuildMeasurement(&Linearized, + ReferenceSheet, + MeasurementSheet, + PATCH_HAS_XYZ|PATCH_HAS_RGB)) return false; + + + + /* Any patch to deal of? */ + if (cmsxPCollCountSet(&Linearized, Linearized.Allowed) <= 0) return false; + + + /* Try to see if proper primaries, white and black already present */ + PatchWhite = cmsxPCollFindWhite(&Linearized, Linearized.Allowed, &Distance); + if (Distance != 0) + PatchWhite = NULL; + + PatchBlack = cmsxPCollFindBlack(&Linearized, Linearized.Allowed, &Distance); + if (Distance != 0) + PatchBlack = NULL; + + PatchRed = cmsxPCollFindPrimary(&Linearized, Linearized.Allowed, 0, &Distance); + if (Distance != 0) + PatchRed = NULL; + + PatchGreen = cmsxPCollFindPrimary(&Linearized, Linearized.Allowed, 1, &Distance); + if (Distance != 0) + PatchGreen = NULL; + + PatchBlue = cmsxPCollFindPrimary(&Linearized, Linearized.Allowed, 2, &Distance); + if (Distance != 0) + PatchBlue= NULL; + + /* If we got primaries, then we can also get prelinearization */ + /* by Levenberg-Marquardt. This applies on monitor profiles */ + + if (PatchWhite && PatchRed && PatchGreen && PatchBlue) { + + /* Build matrix with primaries */ + + MAT3 Mat, MatInv; + LPSAMPLEDCURVE Xr,Yr, Xg, Yg, Xb, Yb; + int i, nRes, cnt; + + VEC3init(&Mat.v[0], PatchRed->XYZ.X, PatchGreen->XYZ.X, PatchBlue->XYZ.X); + VEC3init(&Mat.v[1], PatchRed->XYZ.Y, PatchGreen->XYZ.Y, PatchBlue->XYZ.Y); + VEC3init(&Mat.v[2], PatchRed->XYZ.Z, PatchGreen->XYZ.Z, PatchBlue->XYZ.Z); + + /* Invert matrix */ + MAT3inverse(&Mat, &MatInv); + + nRes = cmsxPCollCountSet(&Linearized, Linearized.Allowed); + + Xr = cmsAllocSampledCurve(nRes); + Yr = cmsAllocSampledCurve(nRes); + Xg = cmsAllocSampledCurve(nRes); + Yg = cmsAllocSampledCurve(nRes); + Xb = cmsAllocSampledCurve(nRes); + Yb = cmsAllocSampledCurve(nRes); + + /* Convert XYZ of all patches to RGB */ + cnt = 0; + for (i=0; i < Linearized.nPatches; i++) { + + if (Linearized.Allowed[i]) { + + VEC3 RGBprime, XYZ; + LPPATCH p; + + p = Linearized.Patches + i; + XYZ.n[0] = p -> XYZ.X; + XYZ.n[1] = p -> XYZ.Y; + XYZ.n[2] = p -> XYZ.Z; + + MAT3eval(&RGBprime, &MatInv, &XYZ); + + Xr ->Values[cnt] = p ->Colorant.RGB[0]; + Yr ->Values[cnt] = Clip(RGBprime.n[0]); + + Xg ->Values[cnt] = p ->Colorant.RGB[1]; + Yg ->Values[cnt] = Clip(RGBprime.n[1]); + + Xb ->Values[cnt] = p ->Colorant.RGB[2]; + Yb ->Values[cnt] = Clip(RGBprime.n[2]); + + cnt++; + + } + } + + TransferCurves[0] = cmsxEstimateGamma(Xr, Yr, 1024); + TransferCurves[1] = cmsxEstimateGamma(Xg, Yg, 1024); + TransferCurves[2] = cmsxEstimateGamma(Xb, Yb, 1024); + + if (WhitePoint) { + + WhitePoint->X = PatchWhite->XYZ.X; + WhitePoint->Y= PatchWhite ->XYZ.Y; + WhitePoint->Z= PatchWhite ->XYZ.Z; + } + + if (BlackPoint && PatchBlack) { + + BlackPoint->X = PatchBlack ->XYZ.X; + BlackPoint->Y = PatchBlack ->XYZ.Y; + BlackPoint->Z = PatchBlack ->XYZ.Z; + } + + if (Primaries) { + + cmsXYZ2xyY(&Primaries->Red, &PatchRed ->XYZ); + cmsXYZ2xyY(&Primaries->Green, &PatchGreen ->XYZ); + cmsXYZ2xyY(&Primaries->Blue, &PatchBlue ->XYZ); + + } + + + cmsFreeSampledCurve(Xr); + cmsFreeSampledCurve(Yr); + cmsFreeSampledCurve(Xg); + cmsFreeSampledCurve(Yg); + cmsFreeSampledCurve(Xb); + cmsFreeSampledCurve(Yb); + + cmsxPCollFreeMeasurements(&Linearized); + + return true; + } + + + + + /* Compute prelinearization */ + cmsxComputeLinearizationTables(&Linearized, PT_XYZ, TransferCurves, 1024, Medium); + + /* Linearize measurements */ + cmsxPCollLinearizePatches(&Linearized, Linearized.Allowed, TransferCurves); + + + /* Endpoints */ + ComputeWhiteAndBlackPoints(&Linearized, TransferCurves, &Black, &White); + + /* Primaries */ + ComputePrimary(&Linearized, TransferCurves, 0, &PrimarySet.Red); + ComputePrimary(&Linearized, TransferCurves, 1, &PrimarySet.Green); + ComputePrimary(&Linearized, TransferCurves, 2, &PrimarySet.Blue); + + + if (BlackPoint) { + *BlackPoint = Black; + Div100(BlackPoint); + } + + if (WhitePoint) { + *WhitePoint = White; + Div100(WhitePoint); + } + + + if (Primaries) { + *Primaries = PrimarySet; + } + + cmsxPCollFreeMeasurements(&Linearized); + + return true; +} + + + diff --git a/src/libs/lprof/cmsmntr.cpp b/src/libs/lprof/cmsmntr.cpp new file mode 100644 index 00000000..ed14ed50 --- /dev/null +++ b/src/libs/lprof/cmsmntr.cpp @@ -0,0 +1,371 @@ +/* */ +/* Little cms - profiler construction set */ +/* Copyright (C) 1998-2001 Marti Maria */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */ +/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */ +/* */ +/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */ +/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */ +/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */ +/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */ +/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */ +/* OF THIS SOFTWARE. */ +/* */ +/* This file is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ +/* General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* */ +/* As a special exception to the GNU General Public License, if you */ +/* distribute this file as part of a program that contains a */ +/* configuration script generated by Autoconf, you may include it under */ +/* the same distribution terms that you use for the rest of that program. */ +/* */ +/* Version 1.09a */ + + +#include "lcmsprf.h" + + +static +void ClampRGB(LPVEC3 RGB) +{ + int i; + + for (i=0; i < 3; i++) { + + if (RGB->n[i] > 1.0) + RGB->n[i] = 1.0; + if (RGB->n[i] < 0) + RGB->n[i] = 0; + } +} + + +static +int RegressionSamplerA2B(WORD In[], WORD Out[], LPVOID Cargo) +{ + cmsCIEXYZ xyz; + cmsCIELab Lab; + VEC3 RGB, RGBlinear, vxyz; + LPMONITORPROFILERDATA sys = (LPMONITORPROFILERDATA) Cargo; + + + RGB.n[0] = _cmsxSaturate65535To255(In[0]); + RGB.n[1] = _cmsxSaturate65535To255(In[1]); + RGB.n[2] = _cmsxSaturate65535To255(In[2]); + + cmsxApplyLinearizationTable(RGB.n, sys->PreLab, RGBlinear.n); + cmsxApplyLinearizationTable(RGBlinear.n, sys->Prelinearization, RGBlinear.n); + + RGBlinear.n[0] /= 255.; + RGBlinear.n[1] /= 255.; + RGBlinear.n[2] /= 255.; + + MAT3eval(&vxyz, &sys->PrimariesMatrix, &RGBlinear); + + xyz.X = vxyz.n[0]; + xyz.Y = vxyz.n[1]; + xyz.Z = vxyz.n[2]; + + cmsxChromaticAdaptationAndNormalization(&sys ->hdr, &xyz, false); + + + /* To PCS encoding */ + + cmsXYZ2Lab(NULL, &Lab, &xyz); + cmsFloat2LabEncoded(Out, &Lab); + + + return true; /* And done witch success */ +} + + + + +static +int RegressionSamplerB2A(WORD In[], WORD Out[], LPVOID Cargo) +{ + cmsCIELab Lab; + cmsCIEXYZ xyz; + VEC3 vxyz, RGB; + /* cmsJCh JCh; */ + WORD Lin[3], Llab[3]; + LPMONITORPROFILERDATA sys = (LPMONITORPROFILERDATA) Cargo; + double L; + + + /* Pass L back to 0..0xff00 domain */ + + L = (double) (In[0] * 65280.0) / 65535.0; + In[0] = (WORD) floor(L + .5); + + + /* To float values */ + cmsLabEncoded2Float(&Lab, In); + cmsLab2XYZ(NULL, &xyz, &Lab); + + + cmsxChromaticAdaptationAndNormalization(&sys ->hdr, &xyz, true); + vxyz.n[0] = xyz.X; + vxyz.n[1] = xyz.Y; + vxyz.n[2] = xyz.Z; + + MAT3eval(&RGB, &sys-> PrimariesMatrixRev, &vxyz); + + /* Clamp RGB */ + ClampRGB(&RGB); + + /* Encode output */ + Lin[0] = (WORD) ((double) RGB.n[0] * 65535. + .5); + Lin[1] = (WORD) ((double) RGB.n[1] * 65535. + .5); + Lin[2] = (WORD) ((double) RGB.n[2] * 65535. + .5); + + cmsxApplyLinearizationGamma(Lin, sys ->ReverseTables, Llab); + cmsxApplyLinearizationGamma(Llab, sys ->PreLabRev, Out); + + + return true; /* And done witch success */ +} + + +BOOL cmsxMonitorProfilerInit(LPMONITORPROFILERDATA sys) +{ + + + if (sys == NULL) return false; + ZeroMemory(sys, sizeof(MONITORPROFILERDATA)); + + sys->hdr.DeviceClass = icSigDisplayClass; + sys->hdr.ColorSpace = icSigRgbData; + sys->hdr.PCSType = PT_Lab; + sys->hdr.Medium = MEDIUM_TRANSMISSIVE; + + + /* Default values for generation */ + + sys -> hdr.lUseCIECAM97s = false; + sys -> hdr.CLUTPoints = 16; + + /* Default viewing conditions */ + + sys -> hdr.device.Yb = 20; + sys -> hdr.device.La = 20; + sys -> hdr.device.surround = AVG_SURROUND; + sys -> hdr.device.D_value = 1; /* Complete adaptation */ + + + /* Viewing conditions of PCS */ + cmsxInitPCSViewingConditions(&sys ->hdr); + + strcpy(sys -> hdr.Description, "unknown monitor"); + strcpy(sys -> hdr.Manufacturer, "little cms profiler construction set"); + strcpy(sys -> hdr.Copyright, "No copyright, use freely"); + strcpy(sys -> hdr.Model, "(unknown)"); + + sys -> hdr.ProfileVerbosityLevel = 0; + + return true; +} + + +static +void CreatePrimaryMatrices(LPMONITORPROFILERDATA sys) +{ + cmsCIExyY White; + MAT3 tmp; + + + cmsXYZ2xyY(&White, &sys->hdr.WhitePoint); + cmsBuildRGB2XYZtransferMatrix(&sys -> PrimariesMatrix, &White, &sys->hdr.Primaries); + + CopyMemory(&tmp, &sys -> PrimariesMatrix, sizeof(MAT3)); + MAT3inverse(&tmp, &sys->PrimariesMatrixRev); + +} + + +static +BOOL CreateLUTS(LPMONITORPROFILERDATA sys, LPLUT* A2B, LPLUT* B2A) +{ + LPLUT AToB0 = cmsAllocLUT(); + LPLUT BToA0 = cmsAllocLUT(); + LPGAMMATABLE LabG; + cmsCIExyY xyY; + + + cmsAlloc3DGrid(AToB0, sys->hdr.CLUTPoints, 3, 3); + cmsAlloc3DGrid(BToA0, sys->hdr.CLUTPoints, 3, 3); + + /* cmsAllocLinearTable(AToB0, sys -> Prelinearization, 1); */ + + sys->ReverseTables[0] = cmsReverseGamma(4096, sys ->Prelinearization[0]); + sys->ReverseTables[1] = cmsReverseGamma(4096, sys ->Prelinearization[1]); + sys->ReverseTables[2] = cmsReverseGamma(4096, sys ->Prelinearization[2]); + + /* Prelinearization */ + + LabG = cmsBuildGamma(4096, 3.0); + + sys -> PreLab[0] = cmsJoinGammaEx(LabG, sys ->Prelinearization[0], 4096); + sys -> PreLab[1] = cmsJoinGammaEx(LabG, sys ->Prelinearization[1], 4096); + sys -> PreLab[2] = cmsJoinGammaEx(LabG, sys ->Prelinearization[2], 4096); + + sys -> PreLabRev[0] = cmsJoinGammaEx(sys ->Prelinearization[0], LabG, 4096); + sys -> PreLabRev[1] = cmsJoinGammaEx(sys ->Prelinearization[1], LabG, 4096); + sys -> PreLabRev[2] = cmsJoinGammaEx(sys ->Prelinearization[2], LabG, 4096); + + + cmsFreeGamma(LabG); + + + cmsAllocLinearTable(AToB0, sys->PreLabRev, 1); + cmsAllocLinearTable(BToA0, sys->PreLab, 2); + + + /* Set CIECAM97s parameters */ + + sys -> hdr.device.whitePoint.X = sys -> hdr.WhitePoint.X * 100.; + sys -> hdr.device.whitePoint.Y = sys -> hdr.WhitePoint.Y * 100.; + sys -> hdr.device.whitePoint.Z = sys -> hdr.WhitePoint.Z * 100.; + + + /* Normalize White point for CIECAM97s model */ + cmsXYZ2xyY(&xyY, &sys -> hdr.device.whitePoint); + xyY.Y = 100.; + cmsxyY2XYZ(&sys -> hdr.device.whitePoint, &xyY); + + + sys->hdr.hDevice = cmsCIECAM97sInit(&sys->hdr.device); + sys->hdr.hPCS = cmsCIECAM97sInit(&sys->hdr.PCS); + + + cmsSample3DGrid(AToB0, RegressionSamplerA2B, sys, 0); + cmsSample3DGrid(BToA0, RegressionSamplerB2A, sys, 0); + + cmsCIECAM97sDone(sys->hdr.hDevice); + cmsCIECAM97sDone(sys->hdr.hPCS); + + cmsAddTag(sys->hdr.hProfile, icSigAToB0Tag, AToB0); + cmsAddTag(sys->hdr.hProfile, icSigBToA0Tag, BToA0); + + /* This is the 0xff00 trick to map white at lattice point */ + BToA0 ->Matrix.v[0].n[0] = DOUBLE_TO_FIXED((65535.0 / 65280.0)); + + *A2B = AToB0; + *B2A = BToA0; + + cmsFreeGammaTriple(sys->ReverseTables); + cmsFreeGammaTriple(sys->PreLab); + cmsFreeGammaTriple(sys->PreLabRev); + return true; +} + + + +BOOL cmsxMonitorProfilerDo(LPMONITORPROFILERDATA sys) +{ + + cmsCIExyY White; + LPLUT AToB0, BToA0; + + AToB0 = BToA0 = NULL; + + if (!*sys -> hdr.OutputProfileFile) + return false; + + + if (sys->hdr.ReferenceSheet[0] || sys->hdr.MeasurementSheet[0]) { + + if (sys->hdr.printf) { + + sys->hdr.printf("Loading sheets..."); + + if (sys->hdr.ReferenceSheet[0]) + sys->hdr.printf("Reference sheet: %s", sys->hdr.ReferenceSheet); + if (sys->hdr.MeasurementSheet[0]) + sys->hdr.printf("Measurement sheet: %s", sys->hdr.MeasurementSheet); + } + + + if (!cmsxComputeMatrixShaper(sys -> hdr.ReferenceSheet, + sys -> hdr.MeasurementSheet, + MEDIUM_TRANSMISSIVE, + sys -> Prelinearization, + &sys -> hdr.WhitePoint, + &sys -> hdr.BlackPoint, + &sys -> hdr.Primaries)) return false; + + if (sys->hdr.printf) { + + char Buffer[1024]; + _cmsIdentifyWhitePoint(Buffer, &sys ->hdr.WhitePoint); + sys->hdr.printf("%s", Buffer); + + sys->hdr.printf("Primaries: R:%1.2g, %1.2g G:%1.2g, %1.2g B:%1.2g, %1.2g", + sys->hdr.Primaries.Red.x,sys->hdr.Primaries.Red.y, + sys->hdr.Primaries.Green.x, sys->hdr.Primaries.Green.y, + sys->hdr.Primaries.Blue.x, sys->hdr.Primaries.Blue.y); + } + + } + + + CreatePrimaryMatrices(sys); + + + cmsXYZ2xyY(&White, &sys->hdr.WhitePoint); + + sys->hdr.hProfile = cmsCreateRGBProfile(&White, + &sys-> hdr.Primaries, + sys -> Prelinearization); + + cmsSetDeviceClass(sys->hdr.hProfile, sys->hdr.DeviceClass); + + if (sys -> hdr.lUseCIECAM97s) + sys->hdr.PCSType = PT_Lab; + else + sys->hdr.PCSType = PT_XYZ; + + cmsSetPCS(sys->hdr.hProfile, _cmsICCcolorSpace(sys->hdr.PCSType)); + + if (sys -> hdr.lUseCIECAM97s) + CreateLUTS(sys, &AToB0, &BToA0); + + + cmsxEmbedTextualInfo(&sys ->hdr); + + cmsAddTag(sys->hdr.hProfile, icSigMediaWhitePointTag, &sys->hdr.WhitePoint); + cmsAddTag(sys->hdr.hProfile, icSigMediaBlackPointTag, &sys->hdr.BlackPoint); + + + if (sys->hdr.ProfileVerbosityLevel >= 2) { + + cmsxEmbedCharTarget(&sys ->hdr); + } + + + _cmsSaveProfile(sys->hdr.hProfile, sys->hdr.OutputProfileFile); + cmsCloseProfile(sys->hdr.hProfile); + sys->hdr.hProfile = NULL; + + + if (AToB0) cmsFreeLUT(AToB0); + if (BToA0) cmsFreeLUT(BToA0); + + if (sys ->Prelinearization[0]) + cmsFreeGammaTriple(sys -> Prelinearization); + + return true; +} diff --git a/src/libs/lprof/cmsoutl.cpp b/src/libs/lprof/cmsoutl.cpp new file mode 100644 index 00000000..248aaa04 --- /dev/null +++ b/src/libs/lprof/cmsoutl.cpp @@ -0,0 +1,284 @@ +/* */ +/* Little cms - profiler construction set */ +/* Copyright (C) 1998-2001 Marti Maria */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */ +/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */ +/* */ +/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */ +/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */ +/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */ +/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */ +/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */ +/* OF THIS SOFTWARE. */ +/* */ +/* This file is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ +/* General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* */ +/* As a special exception to the GNU General Public License, if you */ +/* distribute this file as part of a program that contains a */ +/* configuration script generated by Autoconf, you may include it under */ +/* the same distribution terms that you use for the rest of that program. */ +/* */ +/* Version 1.09a */ +/* */ +/* Incremental Interpolator */ + +#include "lcmsprf.h" + + +/* Res points to a result in XYZ or Lab */ + +BOOL cdecl cmsxRegressionInterpolatorRGB(LPMEASUREMENT m, + int ColorSpace, + int RegressionTerms, + BOOL lUseLocalPatches, + int MinPatchesToCollect, + double r, double g, double b, + void* Res); + + +/* -------------------------------------------------------------- Implementation */ + +/* #define DEBUG 1 */ + +/* Estimate regression matrix */ +static +void EstimateRegression(LPMEASUREMENT m, double r, double g, double b, + int ColorSpace, + LPMATN* ptfm, + int nterms, + BOOL lIncludeAllPatches, + int MinPatchesToCollect) +{ + int nCollected; + MLRSTATISTICS maxAns; + int ToCollect; + SETOFPATCHES collected = cmsxPCollBuildSet(m, false); + SETOFPATCHES allowed = cmsxPCollBuildSet(m, true); + BOOL rc; + BOOL lPatchesExhausted = false; + + + CopyMemory(allowed, m -> Allowed, m->nPatches*sizeof(BOOL)); + + *ptfm = NULL; + + ToCollect = max(MinPatchesToCollect, (nterms + 1)); + + do { + + if (lIncludeAllPatches) { + + CopyMemory(collected, allowed, m->nPatches*sizeof(BOOL)); + lPatchesExhausted = true; + ToCollect = nCollected = m->nPatches; + } + else + { + + nCollected = cmsxPCollPatchesNearRGB(m, m -> Allowed, + r, g, b, + ToCollect, collected); + + if (nCollected < ToCollect) { /* No more patches available */ + lPatchesExhausted = true; + } + else { + ToCollect = nCollected + 1; /* Start from here in next iteration */ + } + } + + /* We are going always 3 -> 3 for now.... */ + rc = cmsxRegressionCreateMatrix(m, collected, nterms, ColorSpace, ptfm, &maxAns); + + + /* Does fit? */ + if ((rc == false) || maxAns.R2adj < 0.95 || maxAns.R2adj > 1.0) { + + maxAns.R2adj = -100; /* No, repeat */ + } + + + } while (!lPatchesExhausted && maxAns.R2adj < 0.95); + +#ifdef DEBUG + printf("R2adj: %g, F: %g\n", maxAns.R2adj, maxAns.F); +#endif + + free(collected); + free(allowed); +} + + + +BOOL cmsxRegressionInterpolatorRGB(LPMEASUREMENT m, + int ColorSpace, + int RegressionTerms, + BOOL lUseLocalPatches, + int MinPatchesToCollect, + double r, double g, double b, + void* Res) +{ + LPMATN tfm = NULL; + + + EstimateRegression(m, r, g, b, ColorSpace, &tfm, RegressionTerms, + !lUseLocalPatches, MinPatchesToCollect); + + if (tfm == NULL) return false; + + switch (ColorSpace) { + + case PT_Lab: + + if (!cmsxRegressionRGB2Lab(r, g, b, tfm, (LPcmsCIELab) Res)) return false; + break; + + case PT_XYZ: + if (!cmsxRegressionRGB2XYZ(r, g, b, tfm, (LPcmsCIEXYZ) Res)) return false; + break; + + default: + return false; + } + + MATNfree(tfm); + + +#ifdef DEBUG + printf("INTERPOLATED RGB %g,%g,%g Lab %g, %g, %g \n", r , g, b, + Lab->L, Lab->a, Lab->b); + +#endif + return true; +} + + +/* Check the results of a given regression matrix */ + +static +void CheckOneRegressionMatrix(LPPROFILERCOMMONDATA hdr, LPMATN Matrix, + double* Mean, double* Std, double* Max) +{ + + cmsCIELab Lab; + cmsCIEXYZ XYZ; + double Hit, sum, sum2, n, dE; + int i; + cmsCIEXYZ D50; + + + D50.X = cmsD50_XYZ() -> X* 100.; + D50.Y = cmsD50_XYZ() -> Y* 100.; + D50.Z = cmsD50_XYZ() -> Z* 100.; + + Hit = sum = sum2 = n = 0; + for (i=0; i < hdr -> m.nPatches; i++) { + + if (hdr -> m.Allowed[i]) { + + LPPATCH p = hdr -> m.Patches + i; + + if (hdr -> PCSType == PT_Lab) { + + WORD ProfileLabEncoded[3]; + + cmsxRegressionRGB2Lab(p -> Colorant.RGB[0], + p -> Colorant.RGB[1], + p -> Colorant.RGB[2], + Matrix, &Lab); + + cmsFloat2LabEncoded(ProfileLabEncoded, &Lab); + cmsLabEncoded2Float(&Lab, ProfileLabEncoded); + + dE = cmsDeltaE(&Lab, &p ->Lab); + } + else { + cmsCIELab Lab2; + + cmsxRegressionRGB2XYZ(p -> Colorant.RGB[0], + p -> Colorant.RGB[1], + p -> Colorant.RGB[2], + Matrix, &XYZ); + _cmsxClampXYZ100(&XYZ); + + cmsXYZ2Lab(&D50, &Lab, &XYZ); + cmsXYZ2Lab(&D50, &Lab2, &p ->XYZ); + + dE = cmsDeltaE(&Lab, &Lab2); + } + + + if (dE > Hit) + Hit = dE; + + sum += dE; + sum2 += dE * dE; + n = n + 1; + + } + } + + *Mean = sum / n; + *Std = sqrt((n * sum2 - sum * sum) / (n*(n-1))); + *Max = Hit; + +} + + +/* Trial-and-error in order to get best number of terms. */ + +int cmsxFindOptimumNumOfTerms(LPPROFILERCOMMONDATA hdr, int nMaxTerms, BOOL* lAllOk) +{ + int i, BestTerms; + BOOL rc; + LPMATN Matrix = NULL; + MLRSTATISTICS Stat; + double dEmean, dEStd, dEHit, Best; + BOOL lOneFound; + + + BestTerms = 4; + Best = 1000.; + lOneFound = false; + + for (i=4; i <= nMaxTerms; i++) { /* 55 */ + + rc = cmsxRegressionCreateMatrix(&hdr -> m, hdr -> m.Allowed, + i, hdr -> PCSType, &Matrix, &Stat); + + if (rc && Stat.R2adj < 1 && Stat.R2adj > 0.6) { + + CheckOneRegressionMatrix(hdr, Matrix, &dEmean, &dEStd, &dEHit); + + if (dEStd < Best && dEHit < 50.) { + + Best = dEStd; + BestTerms = i; + lOneFound = true; + } + + } + MATNfree(Matrix); + Matrix = NULL; + } + + *lAllOk = lOneFound; + + return BestTerms; +} + + diff --git a/src/libs/lprof/cmspcoll.cpp b/src/libs/lprof/cmspcoll.cpp new file mode 100644 index 00000000..0b03fd5a --- /dev/null +++ b/src/libs/lprof/cmspcoll.cpp @@ -0,0 +1,1045 @@ +/* */ +/* Little cms - profiler construction set */ +/* Copyright (C) 1998-2001 Marti Maria */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */ +/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */ +/* */ +/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */ +/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */ +/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */ +/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */ +/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */ +/* OF THIS SOFTWARE. */ +/* */ +/* This file is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ +/* General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* */ +/* As a special exception to the GNU General Public License, if you */ +/* distribute this file as part of a program that contains a */ +/* configuration script generated by Autoconf, you may include it under */ +/* the same distribution terms that you use for the rest of that program. */ +/* */ +/* Version 1.08a */ + + +#include "lcmsprf.h" + + +/* ----------------------------------------------------------------- Patch collections */ + +BOOL cdecl cmsxPCollLoadFromSheet(LPMEASUREMENT m, LCMSHANDLE hSheet); + +BOOL cdecl cmsxPCollBuildMeasurement(LPMEASUREMENT m, + const char *ReferenceSheet, + const char *MeasurementSheet, + DWORD dwNeededSamplesType); + +LPPATCH cdecl cmsxPCollGetPatch(LPMEASUREMENT m, int n); + +LPPATCH cdecl cmsxPCollGetPatchByName(LPMEASUREMENT m, const char* Name, int* lpPos); +LPPATCH cdecl cmsxPCollGetPatchByPos(LPMEASUREMENT m, int row, int col); +LPPATCH cdecl cmsxPCollAddPatchRGB(LPMEASUREMENT m, const char *Name, + double r, double g, double b, + LPcmsCIEXYZ XYZ, LPcmsCIELab Lab); + +/* Sets of patches */ + +SETOFPATCHES cdecl cmsxPCollBuildSet(LPMEASUREMENT m, BOOL lDefault); +int cdecl cmsxPCollCountSet(LPMEASUREMENT m, SETOFPATCHES Set); +BOOL cdecl cmsxPCollValidatePatches(LPMEASUREMENT m, DWORD dwFlags); + + +/* Collect "need" patches of the specific kind, return the number of collected (that */ +/* could be less if set of patches is exhausted) */ + + +void cdecl cmsxPCollPatchesGS(LPMEASUREMENT m, SETOFPATCHES Result); + +int cdecl cmsxPCollPatchesNearRGB(LPMEASUREMENT m, SETOFPATCHES Valids, + double r, double g, double b, int need, SETOFPATCHES Result); + +int cdecl cmsxPCollPatchesNearNeutral(LPMEASUREMENT m, SETOFPATCHES Valids, + int need, SETOFPATCHES Result); + +int cdecl cmsxPCollPatchesNearPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, + int nChannel, int need, SETOFPATCHES Result); + +int cdecl cmsxPCollPatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids, + double Lmin, double LMax, double a, double b, SETOFPATCHES Result); + +int cdecl cmsxPCollPatchesInGamutLUT(LPMEASUREMENT m, SETOFPATCHES Valids, + LPLUT Gamut, SETOFPATCHES Result); + +LPPATCH cdecl cmsxPCollFindWhite(LPMEASUREMENT m, SETOFPATCHES Valids, double* Distance); +LPPATCH cdecl cmsxPCollFindBlack(LPMEASUREMENT m, SETOFPATCHES Valids, double* Distance); +LPPATCH cdecl cmsxPCollFindPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, int Channel, double* Distance); + + +/* ------------------------------------------------------------- Implementation */ + +#define IS(x) EqualsTo(c, x) + +/* A wrapper on stricmp() */ + +static +BOOL EqualsTo(const char* a, const char *b) +{ + return (stricmp(a, b) == 0); +} + + +/* Does return a bitwise mask holding the measurements contained in a Sheet */ + +static +DWORD MaskOfDataSet(LCMSHANDLE hSheet) +{ + char** Names; + int i, n; + DWORD dwMask = 0; + + n = cmsxIT8EnumDataFormat(hSheet, &Names); + + for (i=0; i < n; i++) { + + char *c = Names[i]; + + if (IS("RGB_R") || IS("RGB_G") || IS("RGB_B")) + dwMask |= PATCH_HAS_RGB; + else + if (IS("XYZ_X") || IS("XYZ_Y") || IS("XYZ_Z")) + dwMask |= PATCH_HAS_XYZ; + else + if (IS("LAB_L") || IS("LAB_A") ||IS("LAB_B")) + dwMask |= PATCH_HAS_Lab; + else + if (IS("STDEV_DE")) + dwMask |= PATCH_HAS_STD_DE; + } + + return dwMask; +} + + +/* Addition of a patch programatically */ + +LPPATCH cmsxPCollAddPatchRGB(LPMEASUREMENT m, const char *Name, + double r, double g, double b, + LPcmsCIEXYZ XYZ, LPcmsCIELab Lab) +{ + LPPATCH p; + + p = m->Patches + m->nPatches++; + + strcpy(p -> Name, Name); + + p -> Colorant.RGB[0] = r; + p -> Colorant.RGB[1] = g; + p -> Colorant.RGB[2] = b; + p -> dwFlags = PATCH_HAS_RGB; + + if (XYZ) { + + p -> XYZ = *XYZ; + p -> dwFlags |= PATCH_HAS_XYZ; + } + + if (Lab) { + p -> Lab = *Lab; + p -> dwFlags |= PATCH_HAS_Lab; + } + + + return p; +} + +/* Some vendors does store colorant data in a non-standard way, */ +/* i.e, from 0.0..1.0 or from 0.0..100.0 This routine tries to */ +/* detect such situations */ + +static +void NormalizeColorant(LPMEASUREMENT m) +{ + int i, j; + double MaxColorant=0; + double Normalize; + + + for (i=0; i < m -> nPatches; i++) { + + + LPPATCH p = m -> Patches + i; + + for (j=0; j < MAXCHANNELS; j++) { + if (p ->Colorant.Hexa[j] > MaxColorant) + MaxColorant = p ->Colorant.Hexa[j]; + } + } + + /* Ok, some heuristics */ + + if (MaxColorant < 2) + Normalize = 255.0; /* goes 0..1 */ + else + if (MaxColorant < 102) + Normalize = 2.55; /* goes 0..100 */ + else + if (MaxColorant > 300) + Normalize = (255.0 / 65535.0); /* Goes 0..65535.0 */ + else + return; /* Is ok */ + + + /* Rearrange patches */ + for (i=0; i < m -> nPatches; i++) { + + + LPPATCH p = m -> Patches + i; + for (j=0; j < MAXCHANNELS; j++) + p ->Colorant.Hexa[j] *= Normalize; + } + +} + + +/* Load a collection from a Sheet */ + +BOOL cmsxPCollLoadFromSheet(LPMEASUREMENT m, LCMSHANDLE hSheet) +{ + int i; + DWORD dwMask; + + + if (m -> nPatches == 0) { + + m -> nPatches = (int) cmsxIT8GetPropertyDbl(hSheet, "NUMBER_OF_SETS"); + m -> Patches = (PATCH*)calloc(m -> nPatches, sizeof(PATCH)); // C->C++ : cast + + if (m -> Patches == NULL) { + cmsxIT8Free(hSheet); + return false; + } + + for (i=0; i < m -> nPatches; i++) { + + LPPATCH p = m -> Patches + i; + p -> dwFlags = 0; + cmsxIT8GetPatchName(hSheet, i, p ->Name); + + } + + } + + + /* Build mask according to data format */ + + dwMask = MaskOfDataSet(hSheet); + + + /* Read items. Set flags accordly. */ + for (i = 0; i < m->nPatches; i++) { + + LPPATCH Patch = m -> Patches + i; + + /* Fill in data according to mask */ + + if (dwMask & PATCH_HAS_Lab) { + + if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "LAB_L", &Patch -> Lab.L) && + cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "LAB_A", &Patch -> Lab.a) && + cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "LAB_B", &Patch -> Lab.b)) + + Patch -> dwFlags |= PATCH_HAS_Lab; + } + + if (dwMask & PATCH_HAS_XYZ) { + + if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "XYZ_X", &Patch -> XYZ.X) && + cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "XYZ_Y", &Patch -> XYZ.Y) && + cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "XYZ_Z", &Patch -> XYZ.Z)) + + Patch -> dwFlags |= PATCH_HAS_XYZ; + + } + + if (dwMask & PATCH_HAS_RGB) { + + if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "RGB_R", &Patch -> Colorant.RGB[0]) && + cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "RGB_G", &Patch -> Colorant.RGB[1]) && + cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "RGB_B", &Patch -> Colorant.RGB[2])) + + Patch -> dwFlags |= PATCH_HAS_RGB; + } + + if (dwMask & PATCH_HAS_STD_DE) { + + if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "STDEV_DE", &Patch -> dEStd)) + + Patch -> dwFlags |= PATCH_HAS_STD_DE; + + } + + } + + NormalizeColorant(m); + return true; +} + + +/* Does save parameters to a empty sheet */ + +BOOL cmsxPCollSaveToSheet(LPMEASUREMENT m, LCMSHANDLE it8) +{ + int nNumberOfSets = cmsxPCollCountSet(m, m->Allowed); + int nNumberOfFields = 0; + DWORD dwMask = 0; + int i; + + /* Find mask of fields */ + for (i=0; i < m ->nPatches; i++) { + if (m ->Allowed[i]) { + + LPPATCH p = m ->Patches + i; + dwMask |= p ->dwFlags; + } + } + + nNumberOfFields = 1; /* SampleID */ + + if (dwMask & PATCH_HAS_RGB) + nNumberOfFields += 3; + + if (dwMask & PATCH_HAS_XYZ) + nNumberOfFields += 3; + + if (dwMask & PATCH_HAS_Lab) + nNumberOfFields += 3; + + + cmsxIT8SetPropertyDbl(it8, "NUMBER_OF_SETS", nNumberOfSets); + cmsxIT8SetPropertyDbl(it8, "NUMBER_OF_FIELDS", nNumberOfFields); + + nNumberOfFields = 0; + cmsxIT8SetDataFormat(it8, nNumberOfFields++, "SAMPLE_ID"); + + if (dwMask & PATCH_HAS_RGB) { + + cmsxIT8SetDataFormat(it8, nNumberOfFields++, "RGB_R"); + cmsxIT8SetDataFormat(it8, nNumberOfFields++, "RGB_G"); + cmsxIT8SetDataFormat(it8, nNumberOfFields++, "RGB_B"); + } + + if (dwMask & PATCH_HAS_XYZ) { + + cmsxIT8SetDataFormat(it8, nNumberOfFields++, "XYZ_X"); + cmsxIT8SetDataFormat(it8, nNumberOfFields++, "XYZ_Y"); + cmsxIT8SetDataFormat(it8, nNumberOfFields++, "XYZ_Z"); + + } + + + if (dwMask & PATCH_HAS_XYZ) { + + cmsxIT8SetDataFormat(it8, nNumberOfFields++, "LAB_L"); + cmsxIT8SetDataFormat(it8, nNumberOfFields++, "LAB_A"); + cmsxIT8SetDataFormat(it8, nNumberOfFields++, "LAB_B"); + + } + + for (i=0; i < m ->nPatches; i++) { + if (m ->Allowed[i]) { + + LPPATCH Patch = m ->Patches + i; + + cmsxIT8SetDataSet(it8, Patch->Name, "SAMPLE_ID", Patch->Name); + + if (dwMask & PATCH_HAS_RGB) { + cmsxIT8SetDataSetDbl(it8, Patch->Name, "RGB_R", Patch ->Colorant.RGB[0]); + cmsxIT8SetDataSetDbl(it8, Patch->Name, "RGB_G", Patch ->Colorant.RGB[1]); + cmsxIT8SetDataSetDbl(it8, Patch->Name, "RGB_B", Patch ->Colorant.RGB[2]); + } + + if (dwMask & PATCH_HAS_XYZ) { + cmsxIT8SetDataSetDbl(it8, Patch->Name, "XYZ_X", Patch ->XYZ.X); + cmsxIT8SetDataSetDbl(it8, Patch->Name, "XYZ_Y", Patch ->XYZ.Y); + cmsxIT8SetDataSetDbl(it8, Patch->Name, "XYZ_Z", Patch ->XYZ.Z); + } + + if (dwMask & PATCH_HAS_Lab) { + cmsxIT8SetDataSetDbl(it8, Patch->Name, "LAB_L", Patch ->Lab.L); + cmsxIT8SetDataSetDbl(it8, Patch->Name, "LAB_A", Patch ->Lab.a); + cmsxIT8SetDataSetDbl(it8, Patch->Name, "LAB_B", Patch ->Lab.b); + + } + } + } + + return true; +} + +static +void FixLabOnly(LPMEASUREMENT m) +{ + int i; + + for (i=0; i < m ->nPatches; i++) { + + LPPATCH p = m ->Patches + i; + if ((p ->dwFlags & PATCH_HAS_Lab) && + !(p ->dwFlags & PATCH_HAS_XYZ)) + { + cmsLab2XYZ(cmsD50_XYZ(), &p->XYZ, &p ->Lab); + + p ->XYZ.X *= 100.; + p ->XYZ.Y *= 100.; + p ->XYZ.Z *= 100.; + + p ->dwFlags |= PATCH_HAS_XYZ; + } + + } + +} + + +/* Higher level function. Does merge reference and measurement sheet into */ +/* a MEASUREMENT struct. Data to keep is described in dwNeededSamplesType */ +/* mask as follows: */ +/* */ +/* PATCH_HAS_Lab 0x00000001 */ +/* PATCH_HAS_XYZ 0x00000002 */ +/* PATCH_HAS_RGB 0x00000004 */ +/* PATCH_HAS_CMY 0x00000008 */ +/* PATCH_HAS_CMYK 0x00000010 */ +/* PATCH_HAS_HEXACRM 0x00000020 */ +/* PATCH_HAS_STD_Lab 0x00010000 */ +/* PATCH_HAS_STD_XYZ 0x00020000 */ +/* PATCH_HAS_STD_RGB 0x00040000 */ +/* PATCH_HAS_STD_CMY 0x00080000 */ +/* PATCH_HAS_STD_CMYK 0x00100000 */ +/* PATCH_HAS_STD_HEXACRM 0x00100000 */ +/* PATCH_HAS_MEAN_DE 0x01000000 */ +/* PATCH_HAS_STD_DE 0x02000000 */ +/* PATCH_HAS_CHISQ 0x04000000 */ +/* */ +/* See lprof.h for further info */ + + +BOOL cmsxPCollBuildMeasurement(LPMEASUREMENT m, + const char *ReferenceSheet, + const char *MeasurementSheet, + DWORD dwNeededSamplesType) +{ + LCMSHANDLE hSheet; + BOOL rc = true; + + ZeroMemory(m, sizeof(MEASUREMENT)); + + + if (ReferenceSheet != NULL && *ReferenceSheet) { + + hSheet = cmsxIT8LoadFromFile(ReferenceSheet); + if (hSheet == NULL) return false; + + rc = cmsxPCollLoadFromSheet(m, hSheet); + cmsxIT8Free(hSheet); + } + + if (!rc) return false; + + if (MeasurementSheet != NULL && *MeasurementSheet) { + + hSheet = cmsxIT8LoadFromFile(MeasurementSheet); + if (hSheet == NULL) return false; + + rc = cmsxPCollLoadFromSheet(m, hSheet); + cmsxIT8Free(hSheet); + } + + if (!rc) return false; + + + /* Fix up -- If only Lab is present, then compute */ + /* XYZ based on D50 */ + + FixLabOnly(m); + + cmsxPCollValidatePatches(m, dwNeededSamplesType); + return true; +} + + + +void cmsxPCollFreeMeasurements(LPMEASUREMENT m) +{ + if (m->Patches) + free(m->Patches); + + m->Patches = NULL; + m->nPatches = 0; + + if (m -> Allowed) + free(m -> Allowed); + +} + +/* Retrieval functions */ + +LPPATCH cmsxPCollGetPatchByName(LPMEASUREMENT m, const char* name, int* lpPos) +{ + int i; + for (i=0; i < m->nPatches; i++) + { + if (m -> Allowed) + if (!m -> Allowed[i]) + continue; + + if (EqualsTo(m->Patches[i].Name, name)) { + if (lpPos) *lpPos = i; + return m->Patches + i; + } + } + + return NULL; +} + + + + +/* -------------------------------------------------------------------- Sets */ + + +SETOFPATCHES cmsxPCollBuildSet(LPMEASUREMENT m, BOOL lDefault) +{ + SETOFPATCHES Full = (SETOFPATCHES) malloc(m -> nPatches * sizeof(BOOL)); + int i; + + for (i=0; i < m -> nPatches; i++) + Full[i] = lDefault; + + return Full; +} + +int cmsxPCollCountSet(LPMEASUREMENT m, SETOFPATCHES Set) +{ + int i, Count = 0; + + for (i = 0; i < m -> nPatches; i++) { + + if (Set[i]) + Count++; + } + + return Count; +} + + +/* Validate patches */ + +BOOL cmsxPCollValidatePatches(LPMEASUREMENT m, DWORD dwFlags) +{ + int i, n; + + if (m->Allowed) + free(m->Allowed); + + m -> Allowed = cmsxPCollBuildSet(m, true); + + /* Check for flags */ + for (i=n=0; i < m -> nPatches; i++) { + + LPPATCH p = m -> Patches + i; + m -> Allowed[i] = ((p -> dwFlags & dwFlags) == dwFlags); + + } + + return true; +} + + +/* Several filters */ + + +/* This filter does validate patches placed on 'radius' distance of a */ +/* device-color space. Currently only RGB is supported */ + +static +void PatchesByRGB(LPMEASUREMENT m, SETOFPATCHES Valids, + double R, double G, double B, double radius, SETOFPATCHES Result) +{ + int i; + double ra, rmax = sqrt(radius / 255.); + double dR, dG, dB; + LPPATCH p; + + for (i=0; i < m->nPatches; i++) { + + + if (Valids[i]) { + + p = m->Patches + i; + + dR = fabs(R - p -> Colorant.RGB[0]) / 255.; + dG = fabs(G - p -> Colorant.RGB[1]) / 255.; + dB = fabs(B - p -> Colorant.RGB[2]) / 255.; + + ra = sqrt(dR*dR + dG*dG + dB*dB); + + if (ra <= rmax) + Result[i] = true; + else + Result[i] = false; + + } + } + +} + + +/* This filter does validate patches placed at dEmax radius */ +/* in the device-independent side. */ + +static +void PatchesByLab(LPMEASUREMENT m, SETOFPATCHES Valids, + double L, double a, double b, double dEmax, SETOFPATCHES Result) +{ + int i; + double dE, dEMaxSQR = sqrt(dEmax); + double dL, da, db; + LPPATCH p; + + + for (i=0; i < m->nPatches; i++) { + + + if (Valids[i]) { + + p = m->Patches + i; + + dL = fabs(L - p -> Lab.L); + da = fabs(a - p -> Lab.a); + db = fabs(b - p -> Lab.b); + + dE = sqrt(dL*dL + da*da + db*db); + + if (dE <= dEMaxSQR) + Result[i] = true; + else + Result[i] = false; + } + } +} + + +/* Restrict Lab in a cube of variable sides. Quick and dirty out-of-gamut */ +/* stripper used in estimations. */ + +static +void PatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids, + double Lmin, double Lmax, double da, double db, SETOFPATCHES Result) +{ + int i; + + for (i=0; i < m -> nPatches; i++) { + + + if (Valids[i]) { + + LPPATCH p = m -> Patches + i; + + if ((p->Lab.L >= Lmin && p->Lab.L <= Lmax) && + (fabs(p -> Lab.a) < da) && + (fabs(p -> Lab.b) < db)) + + Result[i] = true; + else + Result[i] = false; + } + } + +} + +/* Restrict to low colorfullness */ + +static +void PatchesOfLowC(LPMEASUREMENT m, SETOFPATCHES Valids, + double Cmax, SETOFPATCHES Result) +{ + int i; + cmsCIELCh LCh; + + for (i=0; i < m -> nPatches; i++) { + + + if (Valids[i]) { + + LPPATCH p = m -> Patches + i; + + cmsLab2LCh(&LCh, &p->Lab); + + + if (LCh.C < Cmax) + Result[i] = true; + else + Result[i] = false; + } + } + +} + + + +/* Primary can be -1 for specifying device gray. Does return patches */ +/* on device-space Colorants. dEMax is the maximum allowed ratio */ + +static +void PatchesPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, + int nColorant, double dEMax, SETOFPATCHES Result) +{ + int i, j; + double n, dE; + + for (i=0; i < m -> nPatches; i++) { + + + if (Valids[i]) { + + LPPATCH p = m -> Patches + i; + + + if (nColorant < 0) /* device-grey? */ + { + /* cross. */ + + double drg = fabs(p -> Colorant.RGB[0] - p -> Colorant.RGB[1]) / 255.; + double drb = fabs(p -> Colorant.RGB[0] - p -> Colorant.RGB[2]) / 255.; + double dbg = fabs(p -> Colorant.RGB[1] - p -> Colorant.RGB[2]) / 255.; + + dE = (drg*drg + drb*drb + dbg*dbg); + + + } + else { + dE = 0.; + for (j=0; j < 3; j++) { + + if (j != nColorant) { + + n = p -> Colorant.RGB[j] / 255.; + dE += (n * n); + + + } + } + } + + + + if (sqrt(dE) < dEMax) + Result[i] = true; + else + Result[i] = false; + } + } + +} + + +/* The high level extractors ----------------------------------------------------- */ + +int cmsxPCollPatchesNearRGB(LPMEASUREMENT m, SETOFPATCHES Valids, + double r, double g, double b, + int need, SETOFPATCHES Result) +{ + double radius; + int nCollected; + + /* Collect points inside of a sphere or radius 'radius' by RGB */ + + radius = 1; + do { + PatchesByRGB(m, Valids, r, g, b, radius, Result); + + nCollected = cmsxPCollCountSet(m, Result); + if (nCollected <= need) { + + radius += 1.0; + } + + } while (nCollected <= need && radius < 256.); + + return nCollected; /* Can be less than needed! */ +} + + +int cmsxPCollPatchesNearNeutral(LPMEASUREMENT m, SETOFPATCHES Valids, + int need, SETOFPATCHES Result) +{ + int nGrays; + double Cmax; + + Cmax = 1.; + do { + + + PatchesOfLowC(m, Valids, Cmax, Result); + + nGrays = cmsxPCollCountSet(m, Result); + if (nGrays <= need) { + + Cmax += .2; + } + + } while (nGrays <= need && Cmax < 10.); + + return nGrays; +} + + +int cmsxPCollPatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids, + double Lmin, double Lmax, double a, double b, + SETOFPATCHES Result) + + +{ + PatchesInLabCube(m, Valids, Lmin, Lmax, a, b, Result); + return cmsxPCollCountSet(m, Result); +} + + + + +int cmsxPCollPatchesNearPrimary(LPMEASUREMENT m, + SETOFPATCHES Valids, + int nChannel, + int need, + SETOFPATCHES Result) +{ + double radius; + int nCollected; + + /* Collect points inside of a sphere or radius 'radius' by RGB */ + + radius = 0.05; + do { + PatchesPrimary(m, Valids, nChannel, radius, Result); + + nCollected = cmsxPCollCountSet(m, Result); + if (nCollected <= need) { + + radius += 0.01; + } + + } while (nCollected <= need && radius < 256.); + + return nCollected; + +} + + +static +void AddOneGray(LPMEASUREMENT m, int n, SETOFPATCHES Grays) +{ + LPPATCH p; + char Buffer[cmsxIT8_GRAYCOLS]; + int pos; + + if (n == 0) strcpy(Buffer, "DMIN"); + else + if (n == cmsxIT8_GRAYCOLS - 1) strcpy(Buffer, "DMAX"); + else + sprintf(Buffer, "GS%d", n); + + p = cmsxPCollGetPatchByName(m, Buffer, &pos); + + if (p) + Grays[pos] = true; +} + + + +void cmsxPCollPatchesGS(LPMEASUREMENT m, SETOFPATCHES Result) +{ + + int i; + + for (i=0; i < cmsxIT8_GRAYCOLS; i++) + AddOneGray(m, i, Result); +} + + + +/* Refresh RGB of all patches after prelinearization */ + +void cmsxPCollLinearizePatches(LPMEASUREMENT m, SETOFPATCHES Valids, LPGAMMATABLE Gamma[3]) +{ + int i; + + for (i=0; i < m -> nPatches; i++) { + + if (Valids[i]) { + + LPPATCH p = m -> Patches + i; + + cmsxApplyLinearizationTable(p -> Colorant.RGB, Gamma, p -> Colorant.RGB); + } + } + +} + + +int cmsxPCollPatchesInGamutLUT(LPMEASUREMENT m, SETOFPATCHES Valids, + LPLUT Gamut, SETOFPATCHES Result) +{ + int i; + int nCollected = 0; + + for (i=0; i < m -> nPatches; i++) { + + if (Valids[i]) { + + LPPATCH p = m -> Patches + i; + WORD EncodedLab[3]; + WORD dE; + + cmsFloat2LabEncoded(EncodedLab, &p->Lab); + cmsEvalLUT(Gamut, EncodedLab, &dE); + Result[i] = (dE < 2) ? true : false; + if (Result[i]) nCollected++; + } + } + + return nCollected; +} + +LPPATCH cmsxPCollFindWhite(LPMEASUREMENT m, SETOFPATCHES Valids, double* TheDistance) +{ + int i; + LPPATCH Candidate = NULL; + double Distance, CandidateDistance = 255; + double dR, dG, dB; + + Candidate = cmsxPCollGetPatchByName(m, "DMIN", NULL); + if (Candidate) { + + if (TheDistance) *TheDistance = 0.0; + return Candidate; + } + + for (i=0; i < m -> nPatches; i++) { + + if (Valids[i]) { + + LPPATCH p = m -> Patches + i; + + dR = fabs(255.0 - p -> Colorant.RGB[0]) / 255.0; + dG = fabs(255.0 - p -> Colorant.RGB[1]) / 255.0; + dB = fabs(255.0 - p -> Colorant.RGB[2]) / 255.0; + + Distance = sqrt(dR*dR + dG*dG + dB*dB); + + if (Distance < CandidateDistance) { + Candidate = p; + CandidateDistance = Distance; + } + } + } + + if (TheDistance) + *TheDistance = floor(CandidateDistance * 255.0 + .5); + + return Candidate; +} + +LPPATCH cmsxPCollFindBlack(LPMEASUREMENT m, SETOFPATCHES Valids, double* TheDistance) +{ + int i; + LPPATCH Candidate = NULL; + double Distance, CandidateDistance = 255; + double dR, dG, dB; + + + Candidate = cmsxPCollGetPatchByName(m, "DMAX", NULL); + if (Candidate) { + + if (TheDistance) *TheDistance = 0.0; + return Candidate; + } + + for (i=0; i < m -> nPatches; i++) { + + if (Valids[i]) { + + LPPATCH p = m -> Patches + i; + + dR = (p -> Colorant.RGB[0]) / 255.0; + dG = (p -> Colorant.RGB[1]) / 255.0; + dB = (p -> Colorant.RGB[2]) / 255.0; + + Distance = sqrt(dR*dR + dG*dG + dB*dB); + + if (Distance < CandidateDistance) { + Candidate = p; + CandidateDistance = Distance; + } + } + } + + if (TheDistance) + *TheDistance = floor(CandidateDistance * 255.0 + .5); + + return Candidate; +} + + +LPPATCH cmsxPCollFindPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, int Channel, double* TheDistance) +{ + int i; + LPPATCH Candidate = NULL; + double Distance, CandidateDistance = 255; + double dR, dG, dB; + const struct { + double r, g, b; + + } RGBPrimaries[3] = { + { 255.0, 0, 0}, + { 0, 255.0, 0}, + { 0, 0, 255 }}; + + + for (i=0; i < m -> nPatches; i++) { + + if (Valids[i]) { + + LPPATCH p = m -> Patches + i; + + dR = fabs(RGBPrimaries[Channel].r - p -> Colorant.RGB[0]) / 255.0; + dG = fabs(RGBPrimaries[Channel].g - p -> Colorant.RGB[1]) / 255.0; + dB = fabs(RGBPrimaries[Channel].b - p -> Colorant.RGB[2]) / 255.0; + + Distance = sqrt(dR*dR + dG*dG + dB*dB); + + if (Distance < CandidateDistance) { + Candidate = p; + CandidateDistance = Distance; + } + } + } + + if (TheDistance) + *TheDistance = floor(CandidateDistance * 255.0 + .5); + + return Candidate; +} diff --git a/src/libs/lprof/cmsprf.cpp b/src/libs/lprof/cmsprf.cpp new file mode 100644 index 00000000..527fc8e8 --- /dev/null +++ b/src/libs/lprof/cmsprf.cpp @@ -0,0 +1,439 @@ +/* */ +/* Little cms - profiler construction set */ +/* Copyright (C) 1998-2001 Marti Maria */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */ +/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */ +/* */ +/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */ +/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */ +/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */ +/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */ +/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */ +/* OF THIS SOFTWARE. */ +/* */ +/* */ +/* This library is free software; you can redistribute it and/or */ +/* modify it under the terms of the GNU Lesser General Public */ +/* License as published by the Free Software Foundation; either */ +/* version 2 of the License, or (at your option) any later version. */ +/* */ +/* This library is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ +/* Lesser General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU Lesser General Public */ +/* License along with this library; if not, write to the Free Software */ +/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "lcmsprf.h" + + +double cdecl _cmsxSaturate65535To255(double d); +double cdecl _cmsxSaturate255To65535(double d); + + +void cdecl _cmsxClampXYZ100(LPcmsCIEXYZ xyz); + + +BOOL cdecl cmsxEmbedCharTarget(LPPROFILERCOMMONDATA hdr); +BOOL cdecl cmsxEmbedMatrixShaper(LPPROFILERCOMMONDATA hdr); +BOOL cdecl cmsxEmbedTextualInfo(LPPROFILERCOMMONDATA hdr); + +/* ----------------------------------------------------------------- Implementation */ + + +/* Convert from 0.0..65535.0 to 0.0..255.0 */ + +double _cmsxSaturate65535To255(double d) +{ + double v; + + v = d / 257.0; + + if (v < 0) return 0; + if (v > 255.0) return 255.0; + + return v; +} + + +double _cmsxSaturate255To65535(double d) +{ + double v; + + v = d * 257.0; + + if (v < 0) return 0; + if (v > 65535.0) return 65535.0; + + return v; +} + + + +/* Cut off absurd values */ + +void _cmsxClampXYZ100(LPcmsCIEXYZ xyz) +{ + + if (xyz->X > 199.996) + xyz->X = 199.996; + + if (xyz->Y > 199.996) + xyz->Y = 199.996; + + if (xyz->Z > 199.996) + xyz->Z = 199.996; + + if (xyz->Y < 0) + xyz->Y = 0; + + if (xyz->X < 0) + xyz->X = 0; + + if (xyz->Z < 0) + xyz->Z = 0; + +} + +static +int xfilelength(int fd) +{ +#ifdef _MSC_VER + return _filelength(fd); +#else + struct stat sb; + if (fstat(fd, &sb) < 0) + return(-1); + return(sb.st_size); +#endif + + +} + + +BOOL cmsxEmbedCharTarget(LPPROFILERCOMMONDATA hdr) +{ + LCMSHANDLE it8 = cmsxIT8Alloc(); + LPBYTE mem; + size_t size, readed; + FILE* f; + BOOL lFreeOnExit = false; + + + if (!hdr->m.Patches) { + + if (!hdr ->ReferenceSheet[0] && !hdr->MeasurementSheet[0]) return false; + + if (cmsxPCollBuildMeasurement(&hdr ->m, + hdr->ReferenceSheet, + hdr->MeasurementSheet, + PATCH_HAS_RGB|PATCH_HAS_XYZ) == false) return false; + lFreeOnExit = true; + + } + + cmsxIT8SetSheetType(it8,"LCMSEMBED"); + cmsxIT8SetProperty(it8, "ORIGINATOR", (const char *) "Little cms"); + cmsxIT8SetProperty(it8, "DESCRIPTOR", (const char *) hdr -> Description); + cmsxIT8SetProperty(it8, "MANUFACTURER", (const char *) hdr ->Manufacturer); + + cmsxPCollSaveToSheet(&hdr->m, it8); + cmsxIT8SaveToFile(it8, "TMP00.IT8"); + cmsxIT8Free(it8); + + f = fopen("TMP00.IT8", "rb"); + size = xfilelength(fileno(f)); + mem = (unsigned char*) malloc(size + 1); // C->C++ : fixed cast + readed = fread(mem, 1, size, f); + fclose(f); + + mem[readed] = 0; + unlink("TMP00.IT8"); + + cmsAddTag(hdr->hProfile, icSigCharTargetTag, mem); + free(mem); + + if (lFreeOnExit) { + + cmsxPCollFreeMeasurements(&hdr->m); + } + + return true; +} + + +static +BOOL ComputeColorantMatrix(LPcmsCIEXYZTRIPLE Colorants, + LPcmsCIExyY WhitePoint, + LPcmsCIExyYTRIPLE Primaries) +{ + MAT3 MColorants; + + if (!cmsBuildRGB2XYZtransferMatrix(&MColorants, WhitePoint, Primaries)) + { + return false; + } + + + cmsAdaptMatrixToD50(&MColorants, WhitePoint); + + Colorants->Red.X = MColorants.v[0].n[0]; + Colorants->Red.Y = MColorants.v[1].n[0]; + Colorants->Red.Z = MColorants.v[2].n[0]; + + Colorants->Green.X = MColorants.v[0].n[1]; + Colorants->Green.Y = MColorants.v[1].n[1]; + Colorants->Green.Z = MColorants.v[2].n[1]; + + Colorants->Blue.X = MColorants.v[0].n[2]; + Colorants->Blue.Y = MColorants.v[1].n[2]; + Colorants->Blue.Z = MColorants.v[2].n[2]; + + return true; + +} + + +BOOL cmsxEmbedMatrixShaper(LPPROFILERCOMMONDATA hdr) +{ + cmsCIEXYZTRIPLE Colorant; + cmsCIExyY MediaWhite; + + cmsXYZ2xyY(&MediaWhite, &hdr ->WhitePoint); + + if (ComputeColorantMatrix(&Colorant, &MediaWhite, &hdr ->Primaries)) { + + cmsAddTag(hdr ->hProfile, icSigRedColorantTag, &Colorant.Red); + cmsAddTag(hdr ->hProfile, icSigGreenColorantTag, &Colorant.Green); + cmsAddTag(hdr ->hProfile, icSigBlueColorantTag, &Colorant.Blue); + } + + cmsAddTag(hdr ->hProfile, icSigRedTRCTag, hdr ->Gamma[0]); + cmsAddTag(hdr ->hProfile, icSigGreenTRCTag, hdr ->Gamma[1]); + cmsAddTag(hdr ->hProfile, icSigBlueTRCTag, hdr ->Gamma[2]); + + return true; +} + + +BOOL cmsxEmbedTextualInfo(LPPROFILERCOMMONDATA hdr) +{ + if (*hdr ->Description) + cmsAddTag(hdr ->hProfile, icSigProfileDescriptionTag, hdr ->Description); + + if (*hdr ->Copyright) + cmsAddTag(hdr ->hProfile, icSigCopyrightTag, hdr ->Copyright); + + if (*hdr ->Manufacturer) + cmsAddTag(hdr ->hProfile, icSigDeviceMfgDescTag, hdr ->Manufacturer); + + if (*hdr ->Model) + cmsAddTag(hdr ->hProfile, icSigDeviceModelDescTag, hdr ->Model); + + return true; +} + + + +void cmsxChromaticAdaptationAndNormalization(LPPROFILERCOMMONDATA hdr, LPcmsCIEXYZ xyz, BOOL lReverse) +{ + + if (hdr->lUseCIECAM97s) { + + cmsJCh JCh; + + /* Let's CIECAM97s to do the adaptation to D50 */ + + xyz->X *= 100.; + xyz->Y *= 100.; + xyz->Z *= 100.; + + _cmsxClampXYZ100(xyz); + + if (lReverse) { + cmsCIECAM97sForward(hdr->hPCS, xyz, &JCh); + cmsCIECAM97sReverse(hdr->hDevice, &JCh, xyz); + } + else { + + cmsCIECAM97sForward(hdr->hDevice, xyz, &JCh); + cmsCIECAM97sReverse(hdr->hPCS, &JCh, xyz); + } + + _cmsxClampXYZ100(xyz); + + xyz -> X /= 100.; + xyz -> Y /= 100.; + xyz -> Z /= 100.; + + } + else { + + /* Else, use Bradford */ + + if (lReverse) { + cmsAdaptToIlluminant(xyz, cmsD50_XYZ(), &hdr->WhitePoint, xyz); + } + else { + cmsAdaptToIlluminant(xyz, &hdr->WhitePoint, cmsD50_XYZ(), xyz); + } + + } + +} + + +void cmsxInitPCSViewingConditions(LPPROFILERCOMMONDATA hdr) +{ + + hdr->PCS.whitePoint.X = cmsD50_XYZ()->X * 100.; + hdr->PCS.whitePoint.Y = cmsD50_XYZ()->Y * 100.; + hdr->PCS.whitePoint.Z = cmsD50_XYZ()->Z * 100.; + + + hdr->PCS.Yb = 20; /* 20% of surround */ + hdr->PCS.La = 20; /* Adapting field luminance */ + hdr->PCS.surround = AVG_SURROUND; + hdr->PCS.D_value = 1.0; /* Complete adaptation */ + +} + + +/* Build gamut hull by geometric means */ +void cmsxComputeGamutHull(LPPROFILERCOMMONDATA hdr) +{ + int i; + int x0, y0, z0; + int Inside, Outside, Boundaries; + char code; + + + hdr -> hRGBHull = cmsxHullInit(); + + /* For all valid patches, mark RGB knots as 0 */ + for (i=0; i < hdr ->m.nPatches; i++) { + + if (hdr ->m.Allowed[i]) { + + LPPATCH p = hdr ->m.Patches + i; + + + x0 = (int) floor(p->Colorant.RGB[0] + .5); + y0 = (int) floor(p->Colorant.RGB[1] + .5); + z0 = (int) floor(p->Colorant.RGB[2] + .5); + + cmsxHullAddPoint(hdr->hRGBHull, x0, y0, z0); + } + } + + cmsxHullComputeHull(hdr ->hRGBHull); + +/* #ifdef DEBUG */ + cmsxHullDumpVRML(hdr -> hRGBHull, "rgbhull.wrl"); +/* #endif */ + + + + /* A check */ + + Inside = Outside = Boundaries = 0; + /* For all valid patches, mark RGB knots as 0 */ + for (i=0; i < hdr ->m.nPatches; i++) { + + if (hdr ->m.Allowed[i]) { + + LPPATCH p = hdr ->m.Patches + i; + + x0 = (int) floor(p->Colorant.RGB[0] + .5); + y0 = (int) floor(p->Colorant.RGB[1] + .5); + z0 = (int) floor(p->Colorant.RGB[2] + .5); + + code = cmsxHullCheckpoint(hdr -> hRGBHull, x0, y0, z0); + + switch (code) { + + case 'i': Inside++; break; + case 'o': Outside++; break; + default: Boundaries++; + } + + } + } + + if (hdr ->printf) + hdr ->printf("Gamut hull: %d inside, %d outside, %d on boundaries", Inside, Outside, Boundaries); + +} + +BOOL cmsxChoosePCS(LPPROFILERCOMMONDATA hdr) +{ + + double gamma_r, gamma_g, gamma_b; + cmsCIExyY SourceWhite; + + /* At first, compute aproximation on matrix-shaper */ + if (!cmsxComputeMatrixShaper(hdr ->ReferenceSheet, + hdr ->MeasurementSheet, + hdr -> Medium, + hdr ->Gamma, + &hdr ->WhitePoint, + &hdr ->BlackPoint, + &hdr ->Primaries)) return false; + + + + cmsXYZ2xyY(&SourceWhite, &hdr ->WhitePoint); + + gamma_r = cmsEstimateGamma(hdr ->Gamma[0]); + gamma_g = cmsEstimateGamma(hdr ->Gamma[1]); + gamma_b = cmsEstimateGamma(hdr ->Gamma[2]); + + + + if (gamma_r > 1.8 || gamma_g > 1.8 || gamma_b > 1.8 || + gamma_r == -1 || gamma_g == -1 || gamma_b == -1) { + + hdr ->PCSType = PT_Lab; + + if (hdr ->printf) + hdr ->printf("I have chosen Lab as PCS"); + + } + else { + + hdr ->PCSType = PT_XYZ; + + if (hdr ->printf) + hdr ->printf("I have chosen XYZ as PCS"); + } + + + + if (hdr ->printf) { + + char Buffer[256] = "Infered "; + + _cmsIdentifyWhitePoint(Buffer, &hdr ->WhitePoint); + hdr ->printf("%s", Buffer); + hdr ->printf("Primaries (x-y): [Red: %2.2f, %2.2f] [Green: %2.2f, %2.2f] [Blue: %2.2f, %2.2f]", + hdr ->Primaries.Red.x, hdr ->Primaries.Red.y, + hdr ->Primaries.Green.x, hdr ->Primaries.Green.y, + hdr ->Primaries.Blue.x, hdr ->Primaries.Blue.y); + + if ((gamma_r != -1) && (gamma_g != -1) && (gamma_b != -1)) { + + hdr ->printf("Estimated gamma: [Red: %2.2f] [Green: %2.2f] [Blue: %2.2f]", + gamma_r, gamma_g, gamma_b); + } + + + } + + + + return true; +} diff --git a/src/libs/lprof/cmsreg.cpp b/src/libs/lprof/cmsreg.cpp new file mode 100644 index 00000000..4e66a9ef --- /dev/null +++ b/src/libs/lprof/cmsreg.cpp @@ -0,0 +1,558 @@ +/* */ +/* Little cms - profiler construction set */ +/* Copyright (C) 1998-2001 Marti Maria */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */ +/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */ +/* */ +/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */ +/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */ +/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */ +/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */ +/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */ +/* OF THIS SOFTWARE. */ +/* */ +/* This file is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ +/* General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* */ +/* As a special exception to the GNU General Public License, if you */ +/* distribute this file as part of a program that contains a */ +/* configuration script generated by Autoconf, you may include it under */ +/* the same distribution terms that you use for the rest of that program. */ +/* */ +/* Version 1.09a */ + + +#include "lcmsprf.h" + + +/* There are three kinds of lies: */ +/* */ +/* * lies */ +/* * damn lies */ +/* * statistics */ +/* */ +/* -Some Wag */ +/* */ +/* */ +/* This module handles multiple linear regression stuff */ + + + +/* A measurement of error + +typedef struct { + + double SSE; // The error sum of squares + double MSE; // The error mean sum of squares + double SSR; // The regression sum of squares + double MSR; // The regression mean sum of squares + double SSTO; // Total sum of squares + double F; // The Fisher-F value (MSR / MSE) + double R2; // Proportion of variability explained by the regression + // (root is Pearson correlation coefficient) + + double R2adj; // The adjusted coefficient of multiple determination. + // R2-adjusted or R2adj. This is calculated as + // R2adj = 1 - (1-R2)(N-n-1)/(N-1) + // and used as multiple correlation coefficient + // (really, it should be square root) + + } MLRSTATISTICS, FAR* LPMLRSTATISTICS; + +*/ + + +int cdecl cmsxRegressionCreateMatrix(LPMEASUREMENT m, SETOFPATCHES Allowed, int nterms, + int ColorSpace, + LPMATN* lpMat, LPMLRSTATISTICS Stat); + +BOOL cdecl cmsxRegressionRGB2Lab(double r, double g, double b, + LPMATN tfm, LPcmsCIELab Lab); + +BOOL cdecl cmsxRegressionRGB2XYZ(double r, double g, double b, + LPMATN tfm, LPcmsCIEXYZ XYZ); + + +/* -------------------------------------------------------------- Implementation */ + +/* #define DEBUG 1 */ + + +/* Multiple linear regression. Also keep track of error. */ +/* Returns false if something goes wrong, or true if all Ok. */ + +static +BOOL MultipleLinearRegression(const LPMATN xi, /* Dependent variable */ + const LPMATN y, /* Independent variable */ + int nvar, /* Number of samples */ + int npar, /* Number of parameters (terms) */ + double* coeff, /* Returned coefficients */ + LPMATN vcb, /* Variance-covariance array */ + double *tvl, /* T-Values */ + LPMLRSTATISTICS ans) /* The returned statistics */ +{ + LPMATN bt, xt, a, xy, yt, b; + double sum; + LPMATN temp1, temp2; + int i; + + + /* |xt| = |xi| T */ + xt = MATNtranspose(xi); + if (xt == NULL) return false; + + + /* |a| = |xt|* |xi| */ + a = MATNmult(xt, xi); + if (a == NULL) return false; + + + /* |xy| = |xy| * |y| */ + xy = MATNmult (xt, y); + if (xy == NULL) return false; + + + /* solve system |a|*|xy| = 0 */ + if (!MATNsolve(a, xy)) return false; + + /* b will hold coefficients */ + b = MATNalloc (xy->Rows, 1); + if (b == NULL) return false; + + for (i = 0; i < npar; i++) + b->Values[i][0] = xy->Values[i][0]; + + /* Store a copy for later user */ + for (i = 0; i < npar; i++) + coeff[i] = b->Values[i][0]; + + /* Error analysis. */ + + /* SSE and MSE. */ + temp1 = MATNalloc (1,1); + if ((temp1->Values[0][0] = MATNcross(y)) == 0) return false; + + /* |bt| = |b| T */ + bt = MATNtranspose (b); + if (bt == NULL) return false; + + /* |yt| = |bt| * |xt| */ + yt = MATNmult (bt, xt); + if (yt == NULL) return false; + + + /* |temp2| = |yt|* |y| */ + temp2 = MATNmult (yt, y); + if (temp2 == NULL) return false; + + /* SSE, MSE */ + ans->SSE = temp1 -> Values[0][0] - temp2 -> Values[0][0]; + ans->MSE = ans->SSE / (double) (nvar - npar); + + /* SSTO */ + sum = 0; + for (i=0; i < nvar; i++) + sum += y->Values[i][0]; + + sum *= sum / (double) nvar; + ans->SSTO = temp1->Values[0][0] - sum; + + /* SSR, MSR, and Fisher-F */ + ans->SSR = temp2->Values[0][0] - sum; + ans->MSR = ans->SSR / (double) (npar - 1); + ans->F = ans->MSR / ans->MSE; + + /* Correlation coefficients. */ + ans->R2 = ans->SSR/ans->SSTO; + ans->R2adj = 1.0 - (ans->SSE/ans->SSTO)*((nvar-1.)/(nvar-npar)); + + /* Variance-covariance matrix */ + /* */ + /* In RGB->Lab, for example: */ + /* */ + /* Var(R) Cov(R,G) Cov(R,B) */ + /* |vcb| = Cov(R,G) Var(G) Cov(G,B) */ + /* Cov(R,B) Cov(G,B) Var(B) */ + /* */ + + MATNscalar(a, ans->MSE, vcb); + + /* Determine the T-values */ + + for (i=0; i < npar; i++) { + + temp1->Values[0][0] = fabs(vcb->Values[i][0]); + if ( temp1->Values[0][0] == 0) + tvl[i] = 0; /* This should never happen */ + else + tvl[i] = b->Values[i][0] / sqrt(temp1->Values[0][0]); + } + + + /* Ok, done */ + + MATNfree(a); MATNfree(xy); MATNfree(yt); MATNfree(b); + MATNfree(temp1); MATNfree(temp2); MATNfree(bt); MATNfree(xt); + + + return true; +} + + + +/* Does create (so, it allocates) the regression matrix, */ +/* keeping track of error as well. */ + +static +BOOL CreateRegressionMatrix(const LPMATN Input, const LPMATN Output, + LPMATN* ptrMatrix, LPMLRSTATISTICS maxErrorMeas) +{ + double* coef; + double* tval; + LPMATN ivar, dvar, vcov; + MLRSTATISTICS ErrorMeas, PeakErrorMeas; + int i, j, nIn, nOut, NumOfPatches; + + nIn = Input -> Cols; + nOut = Output -> Cols; + NumOfPatches = Input -> Rows; + + /* Checkpoint */ + if (Output -> Rows != NumOfPatches) { + + cmsSignalError(LCMS_ERRC_ABORTED, "(internal) Regression matrix mismatch"); + return false; + } + + coef = (double*) malloc(nIn * sizeof(double)); + if (coef == NULL) return false; + + tval = (double*) malloc(nIn * sizeof(double)); + if (tval == NULL) { + free(coef); + return false; + } + + ivar = MATNalloc(NumOfPatches, nIn); + dvar = MATNalloc(NumOfPatches, 1); + + /* Copy In to ivar, */ + for (i = 0; i < NumOfPatches; i++) { + + for (j = 0; j < nIn; j++) + ivar->Values[i][j] = Input->Values[i][j]; + } + + /* This is the (symmetric) Covariance matrix */ + vcov = MATNalloc(nIn, nIn); + + /* This is the regression matrix */ + *ptrMatrix = MATNalloc(nIn, nOut); + + PeakErrorMeas.R2adj = 0; + for (j = 0; j < nOut; ++j) + { + for (i = 0; i < NumOfPatches; ++i) + dvar->Values[i][0] = Output->Values[i][j]; + + if (MultipleLinearRegression(ivar, dvar, NumOfPatches, nIn, coef, vcov, tval, &ErrorMeas)) { + + /* Ok so far... store values */ + for (i = 0; i < nIn; i++) + (*ptrMatrix)->Values[i][j] = coef[i]; + } + else { + /* Boo... got error. Discard whole point. */ + MATNfree(ivar); MATNfree(dvar); MATNfree(vcov); + if (coef) free(coef); + if (tval) free(tval); + MATNfree(*ptrMatrix); *ptrMatrix = NULL; + return false; + } + + /* Did this colorant got higer error? If so, this is */ + /* the peak of all pixel */ + + if(fabs(ErrorMeas.R2adj) > fabs(PeakErrorMeas.R2adj)) + PeakErrorMeas = ErrorMeas; + } + + /* This is the peak error on all components */ + *maxErrorMeas = PeakErrorMeas; + + +#ifdef DEBUG + MATNprintf("Variance-Covariance", vcov); + printf("R2adj: %g, F: %g\n", PeakErrorMeas.R2adj, PeakErrorMeas.F); +#endif + + /* Free stuff. */ + MATNfree(ivar); MATNfree(dvar); MATNfree(vcov); + if (coef) free(coef); + if (tval) free(tval); + + return true; +} + + +/* Does compute the term of regression based on inputs. */ + +static +double Term(int n, double r, double g, double b) +{ + + switch (n) { + + /* 0 */ + case 0 : return 255.0; /* 0 0 0 */ + + /* 1 */ + case 1 : return r; /* 1 0 0 */ + case 2 : return g; /* 0 1 0 */ + case 3 : return b; /* 0 0 1 */ + + /* 2 */ + case 4 : return r * g; /* 1 1 0 */ + case 5 : return r * b; /* 1 0 1 */ + case 6 : return g * b; /* 0 1 1 */ + case 7 : return r * r; /* 2 0 0 */ + case 8 : return g * g; /* 0 2 0 */ + case 9 : return b * b; /* 0 0 2 */ + + /* 3 */ + case 10: return r * g * b; /* 1 1 1 */ + case 11: return r * r * r; /* 3 0 0 */ + case 12: return g * g * g; /* 0 3 0 */ + case 13: return b * b * b; /* 0 0 3 */ + case 14: return r * g * g; /* 1 2 0 */ + case 15: return r * r * g; /* 2 1 0 */ + case 16: return g * g * b; /* 0 2 1 */ + case 17: return b * r * r; /* 2 0 1 */ + case 18: return b * b * r; /* 1 0 2 */ + + /* 4 */ + + case 19: return r * r * g * g; /* 2 2 0 */ + case 20: return g * g * b * b; /* 0 2 2 */ + case 21: return r * r * b * b; /* 2 0 2 */ + case 22: return r * r * g * b; /* 2 1 1 */ + case 23: return r * g * g * b; /* 1 2 1 */ + case 24: return r * g * b * b; /* 1 1 2 */ + case 25: return r * r * r * g; /* 3 1 0 */ + case 26: return r * r * r * b; /* 3 0 1 */ + case 27: return r * g * g * g; /* 1 3 0 */ + case 28: return g * g * g * b; /* 0 3 1 */ + case 29: return r * b * b * b; /* 1 0 3 */ + case 30: return g * b * b * b; /* 0 1 3 */ + case 31: return r * r * r * r; /* 4 0 0 */ + case 32: return g * g * g * g; /* 0 4 0 */ + case 33: return b * b * b * b; /* 0 0 4 */ + + /* 5 */ + + case 34: return r * r * g * g * b; /* 2 2 1 */ + case 35: return r * g * g * b * b; /* 1 2 2 */ + case 36: return r * r * g * b * b; /* 2 1 2 */ + case 37: return r * r * r * g * g; /* 3 2 0 */ + case 38: return r * r * r * g * b; /* 3 1 1 */ + case 39: return r * r * r * b * b; /* 3 0 2 */ + case 40: return g * g * g * b * b; /* 0 3 2 */ + case 41: return r * r * g * g * g; /* 2 3 0 */ + case 42: return r * g * g * g * b; /* 1 3 1 */ + case 43: return r * r * b * b * b; /* 2 0 3 */ + case 44: return g * g * b * b * b; /* 0 2 3 */ + case 45: return r * g * b * b * b; /* 1 1 3 */ + case 46: return r * r * r * r * g; /* 4 1 0 */ + case 47: return r * r * r * r * b; /* 4 0 1 */ + case 48: return r * g * g * g * g; /* 1 4 0 */ + case 49: return g * g * g * g * b; /* 0 4 1 */ + case 50: return r * b * b * b * b; /* 1 0 4 */ + case 51: return g * b * b * b * b; /* 0 1 4 */ + case 52: return r * r * r * r * r; /* 5 0 0 */ + case 53: return g * g * g * g * g; /* 0 5 0 */ + case 54: return b * b * b * b * b; /* 0 0 5 */ + + + default: return 0; + } +} + + + +int cmsxRegressionCreateMatrix(LPMEASUREMENT m, SETOFPATCHES Allowed, int nterms, + int ColorSpace, + LPMATN* lpMat, LPMLRSTATISTICS Stat) +{ + LPMATN Input, Output; + int nCollected = cmsxPCollCountSet(m, Allowed); + int i, j, n, rc; + + /* We are going always 3 -> 3 for now.... */ + + Input = MATNalloc(nCollected, nterms); + Output = MATNalloc(nCollected, 3); + + /* Set independent terms */ + + for (n = i = 0; i < m -> nPatches; i++) + { + if (Allowed[i]) { + + LPPATCH p = m -> Patches + i; + + for (j=0; j < nterms; j++) + Input -> Values[n][j] = Term(j, p -> Colorant.RGB[0], p -> Colorant.RGB[1], p->Colorant.RGB[2]); + + switch (ColorSpace) { + + case PT_Lab: + + Output-> Values[n][0] = p -> Lab.L; + Output-> Values[n][1] = p -> Lab.a; + Output-> Values[n][2] = p -> Lab.b; + break; + + case PT_XYZ: + Output-> Values[n][0] = p -> XYZ.X; + Output-> Values[n][1] = p -> XYZ.Y; + Output-> Values[n][2] = p -> XYZ.Z; + break; + + + default: + cmsSignalError(LCMS_ERRC_ABORTED, "Invalid colorspace"); + } + + n++; + } + } + + + /* Apply multiple linear regression */ + + if (*lpMat) MATNfree(*lpMat); + rc = CreateRegressionMatrix(Input, Output, lpMat, Stat); + + /* Free variables */ + + MATNfree(Input); + MATNfree(Output); + + +#ifdef DEBUG + if (rc == true) + MATNprintf("tfm", *lpMat); +#endif + + return rc; +} + + +/* Convert a RGB triplet to Lab by using regression matrix */ + +BOOL cmsxRegressionRGB2Lab(double r, double g, double b, LPMATN tfm, LPcmsCIELab Lab) +{ + LPMATN inVec, outVec; + int i; + + inVec = MATNalloc(1, tfm->Rows); + if (inVec == NULL) + return false; + + /* Put terms */ + for (i=0; i < tfm->Rows; i++) + inVec -> Values[0][i] = Term(i, r, g, b); + + /* Across regression matrix */ + outVec = MATNmult(inVec, tfm); + + /* Store result */ + if (outVec != NULL) { + + Lab->L = outVec->Values[0][0]; + Lab->a = outVec->Values[0][1]; + Lab->b = outVec->Values[0][2]; + MATNfree(outVec); + } + + MATNfree(inVec); + return true; +} + + +/* Convert a RGB triplet to XYX by using regression matrix */ + +BOOL cmsxRegressionRGB2XYZ(double r, double g, double b, LPMATN tfm, LPcmsCIEXYZ XYZ) +{ + LPMATN inVec, outVec; + int i; + + inVec = MATNalloc(1, tfm->Rows); + if (inVec == NULL) + return false; + + /* Put terms */ + for (i=0; i < tfm->Rows; i++) + inVec -> Values[0][i] = Term(i, r, g, b); + + /* Across regression matrix */ + outVec = MATNmult(inVec, tfm); + + /* Store result */ + if (outVec != NULL) { + + XYZ->X = outVec->Values[0][0]; + XYZ->Y = outVec->Values[0][1]; + XYZ->Z = outVec->Values[0][2]; + MATNfree(outVec); + } + + MATNfree(inVec); + return true; +} + + +/* Convert a RGB triplet to XYX by using regression matrix */ + +BOOL cmsxRegressionXYZ2RGB(LPcmsCIEXYZ XYZ, LPMATN tfm, double RGB[3]) +{ + LPMATN inVec, outVec; + int i; + + inVec = MATNalloc(1, tfm->Rows); + if (inVec == NULL) + return false; + + /* Put terms */ + for (i=0; i < tfm->Rows; i++) + inVec -> Values[0][i] = Term(i, XYZ->X, XYZ->Y, XYZ->Z); + + /* Across regression matrix */ + outVec = MATNmult(inVec, tfm); + + /* Store result */ + if (outVec != NULL) { + + RGB[0] = outVec->Values[0][0]; + RGB[1] = outVec->Values[0][1]; + RGB[2] = outVec->Values[0][2]; + MATNfree(outVec); + } + + MATNfree(inVec); + return true; +} + diff --git a/src/libs/lprof/cmsscn.cpp b/src/libs/lprof/cmsscn.cpp new file mode 100644 index 00000000..b99fed87 --- /dev/null +++ b/src/libs/lprof/cmsscn.cpp @@ -0,0 +1,422 @@ +/* */ +/* Little cms - profiler construction set */ +/* Copyright (C) 1998-2001 Marti Maria */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */ +/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */ +/* */ +/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */ +/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */ +/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */ +/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */ +/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */ +/* OF THIS SOFTWARE. */ +/* */ +/* This file is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ +/* General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* */ +/* As a special exception to the GNU General Public License, if you */ +/* distribute this file as part of a program that contains a */ +/* configuration script generated by Autoconf, you may include it under */ +/* the same distribution terms that you use for the rest of that program. */ +/* */ +/* Version 1.08a */ + + +#include "lcmsprf.h" +#include + +/* The scanner profiler */ + + +BOOL cdecl cmsxScannerProfilerInit(LPSCANNERPROFILERDATA sys); +BOOL cdecl cmsxScannerProfilerDo(LPSCANNERPROFILERDATA sys); + +/* ------------------------------------------------------------ Implementation */ + + + +/* Does create regression matrix */ + +static +void ComputeGlobalRegression(LPSCANNERPROFILERDATA sys) +{ + BOOL lAllOk; + int nTerms; + MLRSTATISTICS Stat; + + nTerms = cmsxFindOptimumNumOfTerms(&sys ->hdr, 55, &lAllOk); + + + if (!lAllOk) { + if (sys -> hdr.printf) + sys -> hdr.printf("*** WARNING: Inconsistence found, profile may be wrong. Check the target!"); + nTerms = 4; + } + + /* Create high terms matrix used by interpolation */ + cmsxRegressionCreateMatrix(&sys -> hdr.m, + sys -> hdr.m.Allowed, + nTerms, + sys -> hdr.PCSType, + &sys -> HiTerms, + &Stat); + + if (sys -> hdr.printf) + sys -> hdr.printf("Global regression: %d terms, R2Adj = %g", nTerms, Stat.R2adj); + + /* Create low terms matrix used by extrapolation */ + cmsxRegressionCreateMatrix(&sys -> hdr.m, + sys -> hdr.m.Allowed, + (nTerms > 10 ? 10 : nTerms), + sys -> hdr.PCSType, + &sys -> LoTerms, + &Stat); + if (sys -> hdr.printf) + sys -> hdr.printf("Extrapolation: R2Adj = %g", Stat.R2adj); + +} + + +/* Fill struct with default values */ + +BOOL cmsxScannerProfilerInit(LPSCANNERPROFILERDATA sys) +{ + + + if (sys == NULL) return false; + ZeroMemory(sys, sizeof(SCANNERPROFILERDATA)); + + sys->hdr.DeviceClass = icSigInputClass; + sys->hdr.ColorSpace = icSigRgbData; + sys->hdr.PCSType = PT_Lab; + sys->hdr.Medium = MEDIUM_REFLECTIVE_D50; + + /* Default values for generation */ + + sys -> hdr.lUseCIECAM97s = false; + sys -> hdr.CLUTPoints = 16; + + + /* Default viewing conditions for scanner */ + + sys -> hdr.device.Yb = 20; + sys -> hdr.device.La = 20; + sys -> hdr.device.surround = AVG_SURROUND; + sys -> hdr.device.D_value = 1.0; /* Complete adaptation */ + + + /* Viewing conditions of PCS */ + cmsxInitPCSViewingConditions(&sys -> hdr); + + + sys -> HiTerms = NULL; + sys -> LoTerms = NULL; + + strcpy(sys -> hdr.Description, "no description"); + strcpy(sys -> hdr.Manufacturer, "little cms profiler construction set"); + strcpy(sys -> hdr.Copyright, "No copyright, use freely"); + strcpy(sys -> hdr.Model, "(unknown)"); + + sys ->lLocalConvergenceExtrapolation = false; + sys ->hdr.ProfileVerbosityLevel = 0; + + + return true; +} + +/* Auxiliar: take RGB and update gauge */ +static +void GetRGB(LPPROFILERCOMMONDATA hdr, WORD In[], double* r, double* g, double* b) +{ + static int Count = 0, n_old = 0; + double R, G, B; + int n; + + + R = _cmsxSaturate65535To255(In[0]); /* Convert from the sheet notation */ + G = _cmsxSaturate65535To255(In[1]); /* 0..255.0, to our notation */ + B = _cmsxSaturate65535To255(In[2]); /* of 0..0xffff, 0xffff/255 = 257 */ + + if (R == 0 && G == 0 && B == 0) { + Count = 0; n_old = -1; + } + + n = (int) (double) (100. * Count) / (hdr->CLUTPoints * hdr->CLUTPoints * hdr->CLUTPoints); + Count++; + + if (n > n_old) { + if (hdr->Gauger) hdr->Gauger("", 0, 100, (int) n); + } + + n_old = n; + *r = R; *g = G; *b = B; + +} + + + + + +/* The sampler for Lab */ +static +int RegressionSamplerLab(WORD In[], WORD Out[], LPVOID Cargo) +{ + cmsCIEXYZ xyz; + cmsCIELab Lab; + double r, g, b; + LPSCANNERPROFILERDATA sys = (LPSCANNERPROFILERDATA) Cargo; + char code; + + + GetRGB(&sys->hdr, In, &r, &g, &b); + + + code = cmsxHullCheckpoint(sys->hdr.hRGBHull, + (int) floor(r + .5), + (int) floor(g + .5), + (int) floor(b + .5)); + + + if (code == 'i') { /* Inside gamut */ + + if (!cmsxRegressionRGB2Lab(r, g, b, sys -> HiTerms, &Lab)) return false; + } + else + if (!sys -> lLocalConvergenceExtrapolation && code == 'o') { /* outside gamut */ + + if (!cmsxRegressionRGB2Lab(r, g, b, sys -> LoTerms, &Lab)) return false; + } + else { /* At gamut hull boundaries */ + + if (!cmsxRegressionInterpolatorRGB(&sys -> hdr.m, + PT_Lab, + 10, + true, + 30, + r, g, b, + &Lab)) return false; + } + + + /* Regression CAN deliver wrong values. Clamp these. */ + cmsClampLab(&Lab, 127.9961, -128, 127.9961, -128); + + /* Normalize */ + cmsLab2XYZ(cmsD50_XYZ(), &xyz, &Lab); + cmsxChromaticAdaptationAndNormalization(&sys->hdr, &xyz, false); + cmsXYZ2Lab(cmsD50_XYZ(), &Lab, &xyz); + + /* Clamping again, adaptation could move slightly values */ + cmsClampLab(&Lab, 127.9961, -128, 127.9961, -128); + + /* To PCS encoding */ + cmsFloat2LabEncoded(Out, &Lab); + + + return true; /* And done with success */ +} + + + + + +/* The sampler for XYZ */ +static +int RegressionSamplerXYZ(WORD In[], WORD Out[], LPVOID Cargo) +{ + cmsCIEXYZ xyz; + double r, g, b; + LPSCANNERPROFILERDATA sys = (LPSCANNERPROFILERDATA) Cargo; + char code; + + GetRGB(&sys -> hdr, In, &r, &g, &b); + + code = cmsxHullCheckpoint(sys ->hdr.hRGBHull, + (int) floor(r + .5), + (int) floor(g + .5), + (int) floor(b + .5)); + + if (code == 'i') { /* Inside gamut */ + + if (!cmsxRegressionRGB2XYZ(r, g, b, sys -> HiTerms, &xyz)) return false; + } + else + if (!sys -> lLocalConvergenceExtrapolation && code == 'o') { /* outside gamut */ + + if (!cmsxRegressionRGB2XYZ(r, g, b, sys -> LoTerms, &xyz)) return false; + } + + else { /* At gamut hull boundaries */ + + if (!cmsxRegressionInterpolatorRGB(&sys -> hdr.m, + PT_XYZ, + 10, + true, + 30, + r, g, b, + &xyz)) return false; + } + + + xyz.X /= 100.; + xyz.Y /= 100.; + xyz.Z /= 100.; + + cmsxChromaticAdaptationAndNormalization(&sys->hdr, &xyz, false); + + /* To PCS encoding. It also claps bad values */ + cmsFloat2XYZEncoded(Out, &xyz); + + return true; /* And done witch success */ +} + + + +/* The main scanner profiler */ +BOOL cmsxScannerProfilerDo(LPSCANNERPROFILERDATA sys) +{ + + LPLUT AToB0; + DWORD dwNeedSamples; + + + if (!*sys -> hdr.OutputProfileFile) + return false; + + + if (!cmsxChoosePCS(&sys->hdr)) + return false; + + dwNeedSamples = PATCH_HAS_RGB; + if (sys ->hdr.PCSType == PT_Lab) + dwNeedSamples |= PATCH_HAS_Lab; + else + dwNeedSamples |= PATCH_HAS_XYZ; + + + if (sys->hdr.printf) { + + sys->hdr.printf("Loading sheets..."); + + if (sys->hdr.ReferenceSheet[0]) + sys->hdr.printf("Reference sheet: %s", sys->hdr.ReferenceSheet); + if (sys->hdr.MeasurementSheet[0]) + sys->hdr.printf("Measurement sheet: %s", sys->hdr.MeasurementSheet); + } + + + if (!cmsxPCollBuildMeasurement(&sys->hdr.m, + sys->hdr.ReferenceSheet, + sys->hdr.MeasurementSheet, + dwNeedSamples)) return false; + + + + sys->hdr.hProfile = cmsCreateRGBProfile(NULL, NULL, NULL); + + + cmsSetDeviceClass(sys->hdr.hProfile, sys->hdr.DeviceClass); + cmsSetColorSpace(sys->hdr.hProfile, sys->hdr.ColorSpace); + cmsSetPCS(sys->hdr. hProfile, _cmsICCcolorSpace(sys->hdr.PCSType)); + + /* Save char target tag */ + if (sys->hdr.ProfileVerbosityLevel >= 2) { + + cmsxEmbedCharTarget(&sys ->hdr); + } + + AToB0 = cmsAllocLUT(); + + cmsAlloc3DGrid(AToB0, sys->hdr.CLUTPoints, 3, 3); + + cmsxComputeLinearizationTables(&sys-> hdr.m, + sys -> hdr.PCSType, + sys -> Prelinearization, + 1024, + MEDIUM_REFLECTIVE_D50); + + /* Refresh RGB of all patches. This converts all regression into */ + /* near linear RGB->Lab or XYZ */ + + cmsxPCollLinearizePatches(&sys->hdr.m, sys -> hdr.m.Allowed, sys -> Prelinearization); + + cmsxComputeGamutHull(&sys->hdr); + ComputeGlobalRegression(sys); + + cmsAllocLinearTable(AToB0, sys -> Prelinearization, 1); + + /* Set CIECAM97s parameters */ + + sys -> hdr.device.whitePoint.X = sys -> hdr.WhitePoint.X * 100.; + sys -> hdr.device.whitePoint.Y = sys -> hdr.WhitePoint.Y * 100.; + sys -> hdr.device.whitePoint.Z = sys -> hdr.WhitePoint.Z * 100.; + + + sys->hdr.hDevice = cmsCIECAM97sInit(&sys->hdr.device); + sys->hdr.hPCS = cmsCIECAM97sInit(&sys->hdr.PCS); + + + if (sys -> hdr.PCSType == PT_Lab) + cmsSample3DGrid(AToB0, RegressionSamplerLab, sys, 0); + else + cmsSample3DGrid(AToB0, RegressionSamplerXYZ, sys, 0); + + cmsCIECAM97sDone(sys->hdr.hDevice); + cmsCIECAM97sDone(sys->hdr.hPCS); + + cmsAddTag(sys->hdr.hProfile, icSigAToB0Tag, AToB0); + + + cmsxEmbedTextualInfo(&sys -> hdr); + + cmsAddTag(sys->hdr.hProfile, icSigMediaWhitePointTag, &sys->hdr.WhitePoint); + cmsAddTag(sys->hdr.hProfile, icSigMediaBlackPointTag, &sys->hdr.BlackPoint); + + + /* Save primaries & gamma curves */ + if (sys->hdr.ProfileVerbosityLevel >= 1) { + + cmsxEmbedMatrixShaper(&sys ->hdr); + } + + _cmsSaveProfile(sys->hdr.hProfile, sys->hdr.OutputProfileFile); + + cmsCloseProfile(sys->hdr.hProfile); + sys->hdr.hProfile = NULL; + + cmsxPCollFreeMeasurements(&sys->hdr.m); + + cmsFreeLUT(AToB0); + + if (sys -> HiTerms) + MATNfree(sys -> HiTerms); + sys -> HiTerms = NULL; + + + if (sys -> LoTerms) + MATNfree(sys -> LoTerms); + sys -> LoTerms = NULL; + + + + if (sys ->Prelinearization[0]) + cmsFreeGammaTriple(sys -> Prelinearization); + + if (sys ->hdr.Gamma) + cmsFreeGammaTriple(sys->hdr.Gamma); + + return true; +} diff --git a/src/libs/lprof/cmssheet.cpp b/src/libs/lprof/cmssheet.cpp new file mode 100644 index 00000000..cf8c569e --- /dev/null +++ b/src/libs/lprof/cmssheet.cpp @@ -0,0 +1,1746 @@ +/* */ +/* Little cms - profiler construction set */ +/* Copyright (C) 1998-2001 Marti Maria */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */ +/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */ +/* */ +/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */ +/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */ +/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */ +/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */ +/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */ +/* OF THIS SOFTWARE. */ +/* */ +/* This file is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ +/* General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* */ +/* As a special exception to the GNU General Public License, if you */ +/* distribute this file as part of a program that contains a */ +/* configuration script generated by Autoconf, you may include it under */ +/* the same distribution terms that you use for the rest of that program. */ +/* */ +/* Version 1.08a */ + + +#include "lcmsprf.h" + +/* #define _DEBUG 1 */ + + + +/* IT8.7 / CGATS.17-200x handling */ +LCMSHANDLE cdecl cmsxIT8Alloc(void); +void cdecl cmsxIT8Free(LCMSHANDLE IT8); + +/* Persistence */ +LCMSHANDLE cdecl cmsxIT8LoadFromFile(const char* cFileName); +LCMSHANDLE cdecl cmsxIT8LoadFromMem(void *Ptr, size_t len); +BOOL cdecl cmsxIT8SaveToFile(LCMSHANDLE IT8, const char* cFileName); + +/* Properties */ +const char* cdecl cmsxIT8GetSheetType(LCMSHANDLE hIT8); +BOOL cdecl cmsxIT8SetSheetType(LCMSHANDLE hIT8, const char* Type); + +BOOL cdecl cmsxIT8SetProperty(LCMSHANDLE hIT8, const char* cProp, const char *Str); +BOOL cdecl cmsxIT8SetPropertyDbl(LCMSHANDLE hIT8, const char* cProp, double Val); + +const char* cdecl cmsxIT8GetProperty(LCMSHANDLE hIT8, const char* cProp); +double cdecl cmsxIT8GetPropertyDbl(LCMSHANDLE hIT8, const char* cProp); +int cdecl cmsxIT8EnumProperties(LCMSHANDLE IT8, char ***PropertyNames); + + +/* Datasets */ + +BOOL cdecl cmsxIT8GetDataSetByPos(LCMSHANDLE IT8, int col, int row, char* Val, int ValBufferLen); + +BOOL cdecl cmsxIT8GetDataSet(LCMSHANDLE IT8, const char* cPatch, + const char* cSample, + char* Val, int ValBufferLen); + + +BOOL cdecl cmsxIT8GetDataSetDbl(LCMSHANDLE IT8, const char* cPatch, const char* cSample, double* d); + +BOOL cdecl cmsxIT8SetDataSet(LCMSHANDLE IT8, const char* cPatch, + const char* cSample, + char *Val); + +BOOL cdecl cmsxIT8SetDataFormat(LCMSHANDLE IT8, int n, const char *Sample); +int cdecl cmsxIT8EnumDataFormat(LCMSHANDLE IT8, char ***SampleNames); + +const char *cdecl cmsxIT8GenericPatchName(int nPatch, char* buffer); +int cdecl cmsxIT8GenericPatchNum(const char *Name); + + +/* ------------------------------------------------------------- Implementation */ + + +#define MAXID 128 /* Max length of identifier */ +#define MAXSTR 255 /* Max length of string */ + +#ifndef NON_WINDOWS +#include +#endif + +/* Symbols */ + +typedef enum { SNONE, + SINUM, /* Integer */ + SDNUM, /* Real */ + SIDENT, /* Identifier */ + SSTRING, /* string */ + SCOMMENT, /* comment */ + SEOLN, /* End of line */ + SEOF, /* End of stream */ + SSYNERROR, /* Syntax error found on stream */ + + /* Keywords */ + + SBEGIN_DATA, + SBEGIN_DATA_FORMAT, + SEND_DATA, + SEND_DATA_FORMAT, + SKEYWORD, + SSTRING_SY + + } SYMBOL; + + +/* Linked list of variable names */ + +typedef struct _KeyVal { + + struct _KeyVal* Next; + char* Keyword; /* Name of variable */ + char* Value; /* Points to value */ + + } KEYVALUE, FAR* LPKEYVALUE; + +/* Linked list of values (Memory sink) */ + +typedef struct _OwnedMem { + + struct _OwnedMem* Next; + void * Ptr; /* Point to value */ + + } OWNEDMEM, FAR* LPOWNEDMEM; + + +/* This struct hold all information about an openened */ +/* IT8 handler. Only one dataset is allowed. */ + +typedef struct { + + int nSamples, nPatches; /* Rows, Cols */ + int SampleID; /* Pos of ID */ + LPKEYVALUE HeaderList; /* The properties */ + char* FileBuffer; /* The ASCII stream */ + char** DataFormat; /* The binary stream descriptor */ + char** Data; /* The binary stream */ + LPOWNEDMEM MemorySink; /* The storage bakend */ + + /* Parser state machine */ + + SYMBOL sy; /* Current symbol */ + int ch; /* Current character */ + char* Source; /* Points to loc. being parsed */ + int inum; /* integer value */ + double dnum; /* real value */ + char id[MAXID]; /* identifier */ + char str[MAXSTR]; /* string */ + + /* Allowed keywords & datasets */ + + LPKEYVALUE ValidKeywords; + LPKEYVALUE ValidSampleID; + + char FileName[MAX_PATH]; + int lineno; /* line counter for error reporting */ + + char SheetType[MAXSTR]; /* New 1.09 */ + + } IT8, FAR* LPIT8; + + + +/* ------------------------------------------------------ IT8 parsing routines */ + + +/* A keyword */ +typedef struct { + const char *id; + SYMBOL sy; + + } KEYWORD; + +/* The keyword->symbol translation table. Sorting is required. */ +static const KEYWORD TabKeys[] = { + + {"BEGIN_DATA", SBEGIN_DATA }, + {"BEGIN_DATA_FORMAT", SBEGIN_DATA_FORMAT }, + {"END_DATA", SEND_DATA}, + {"END_DATA_FORMAT", SEND_DATA_FORMAT}, + {"KEYWORD", SKEYWORD}, + {"STRING", SSTRING_SY}}; + +#define NUMKEYS (sizeof(TabKeys)/sizeof(KEYWORD)) + +/* Predefined properties */ +static char* PredefinedProperties[] = { + + "NUMBER_OF_FIELDS", /* Required - NUMBER OF FIELDS */ + "NUMBER_OF_SETS", /* Required - NUMBER OF SETS */ + "ORIGINATOR", /* Required - Identifies the specific system, organization or individual that created the data file. */ + "CREATED", /* Required - Indicates date of creation of the data file. */ + "DESCRIPTOR", /* Required - Describes the purpose or contents of the data file. */ + "DIFFUSE_GEOMETRY", /* The diffuse geometry used. Allowed values are "sphere" or "opal". */ + "MANUFACTURER", + "MANUFACTURE", /* Some broken Fuji targets does store this value */ + "PROD_DATE", /* Identifies year and month of production of the target in the form yyyy:mm. */ + "SERIAL", /* Uniquely identifies individual physical target. */ + + "MATERIAL", /* Identifies the material on which the target was produced using a code */ + /* uniquely identifying th e material. Th is is intend ed to be used for IT8.7 */ + /* physical targets only (i.e . IT8.7/1 a nd IT8.7/2). */ + + "INSTRUMENTATION", /* Used to report the specific instrumentation used (manufacturer and */ + /* model number) to generate the data reported. This data will often */ + /* provide more information about the particular data collected than an */ + /* extensive list of specific details. This is particularly important for */ + /* spectral data or data derived from spectrophotometry. */ + + "MEASUREMENT_SOURCE", /* Illumination used for spectral measurements. This data helps provide */ + /* a guide to the potential for issues of paper fluorescence, etc. */ + + "PRINT_CONDITIONS", /* Used to define the ch aracteristics of the printed sheet being reported. */ + /* Where standard conditions have been defined (e.g., SW OP at nominal) */ + /* named conditions may suffice. Otherwise, detailed in formation is */ + /* needed. */ + + "SAMPLE_BACKING", /* Identifies the backing material used behind the sample during */ + /* measurement. Allowed values are “black”, “white”, or "na". */ + + "CHISQ_DOF" /* Degrees of freedom associated with the Chi squared statistic */ + }; + +#define NUMPREDEFINEDPROPS (sizeof(PredefinedProperties)/sizeof(char *)) + + +/* Predefined sample types on dataset */ +static char* PredefinedSampleID[] = { + + "CMYK_C", /* Cyan component of CMYK data expressed as a percentage */ + "CMYK_M", /* Magenta component of CMYK data expressed as a percentage */ + "CMYK_Y", /* Yellow component of CMYK data expressed as a percentage */ + "CMYK_K", /* Black component of CMYK data expressed as a percentage */ + "D_RED", /* Red filter density */ + "D_GREEN", /* Green filter density */ + "D_BLUE", /* Blue filter density */ + "D_VIS", /* Visual filter density */ + "D_MAJOR_FILTER", /* Major filter d ensity */ + "RGB_R", /* Red component of RGB data */ + "RGB_G", /* Green component of RGB data */ + "RGB_B", /* Blue com ponent of RGB data */ + "SPECTRAL_NM", /* Wavelength of measurement expressed in nanometers */ + "SPECTRAL_PCT", /* Percentage reflectance/transmittance */ + "SPECTRAL_DEC", /* Reflectance/transmittance */ + "XYZ_X", /* X component of tristimulus data */ + "XYZ_Y", /* Y component of tristimulus data */ + "XYZ_Z", /* Z component of tristimulus data */ + "XYY_X" /* x component of chromaticity data */ + "XYY_Y", /* y component of chromaticity data */ + "XYY_CAPY", /* Y component of tristimulus data */ + "LAB_L", /* L* component of Lab data */ + "LAB_A", /* a* component of Lab data */ + "LAB_B", /* b* component of Lab data */ + "LAB_C", /* C*ab component of Lab data */ + "LAB_H", /* hab component of Lab data */ + "LAB_DE" /* CIE dE */ + "LAB_DE_94", /* CIE dE using CIE 94 */ + "LAB_DE_CMC", /* dE using CMC */ + "LAB_DE_2000", /* CIE dE using CIE DE 2000 */ + "MEAN_DE", /* Mean Delta E (LAB_DE) of samples compared to batch average */ + /* (Used for data files for ANSI IT8.7/1 and IT8.7/2 targets) */ + "STDEV_X", /* Standard deviation of X (tristimulus data) */ + "STDEV_Y", /* Standard deviation of Y (tristimulus data) */ + "STDEV_Z", /* Standard deviation of Z (tristimulus data) */ + "STDEV_L", /* Standard deviation of L* */ + "STDEV_A" /* Standard deviation of a* */ + "STDEV_B", /* Standard deviation of b* */ + "STDEV_DE", /* Standard deviation of CIE dE */ + "CHI_STQD_PAR"}; /* The average of the standard deviations of L*, a* and b*. It is */ + /* used to derive an estimate of the chi-squared parameter which is */ + /* recommended as the predictor of the variability of dE */ + +#define NUMPREDEFINEDSAMPLEID (sizeof(PredefinedSampleID)/sizeof(char *)) + + +/* Checks whatsever if c is a valid identifier middle char. */ +static +BOOL isidchar(int c) +{ + return (isalnum(c) || c == '$' || c == '%' || c == '&' || c == '/' || c == '.' || c == '_'); + +} + +/* Checks whatsever if c is a valid identifier first char. */ +static +BOOL isfirstidchar(int c) +{ + return !isdigit(c) && isidchar(c); +} + +/* Checks if c is a separator */ +static +BOOL isseparator(int c) +{ + return (c == ' ' || c == '\t' || c == '\r'); +} + +/* a replacement for strupr(), just for compatibility sake */ + +static +void xstrupr(char *cp) +{ + for (;*cp;cp++) + if (*cp >= 'a' && *cp <= 'z') + *cp += 'A'-'a'; +} + +/* A replacement for (the nonstandard) filelength */ + +static +int xfilelength(int fd) +{ +#ifdef _MSC_VER + return _filelength(fd); +#else + struct stat sb; + if (fstat(fd, &sb) < 0) + return(-1); + return(sb.st_size); +#endif + + +} + +static +BOOL SynError(LPIT8 it8, const char *Txt, ...) +{ + char Buffer[256], ErrMsg[1024]; + va_list args; + + va_start(args, Txt); + vsprintf(Buffer, Txt, args); + va_end(args); + + sprintf(ErrMsg, "%s: Line %d, %s", it8->FileName, it8->lineno, Buffer); + it8->sy = SSYNERROR; + cmsSignalError(LCMS_ERRC_ABORTED, ErrMsg); + return false; +} + +static +BOOL Check(LPIT8 it8, SYMBOL sy, const char* Err) +{ + if (it8 -> sy != sy) + return SynError(it8, Err); + return true; +} + + + +/* Read Next character from stream */ +static +void NextCh(LPIT8 it8) +{ + it8->ch = *it8->Source; + if (it8->ch) it8->Source++; +} + + +/* Try to see if current identifier is a keyword, if so return the referred symbol */ +static +SYMBOL BinSrchKey(const char *id) +{ + int l = 1; + int r = NUMKEYS; + int x, res; + + while (r >= l) + { + x = (l+r)/2; + res = strcmp(id, TabKeys[x-1].id); + if (res == 0) return TabKeys[x-1].sy; + if (res < 0) r = x - 1; + else l = x + 1; + } + + return SNONE; +} + + +/* 10 ^n */ +static +double pow10(int n) +{ + return pow(10, n); +} + + +/* Reads a Real number, tries to follow from integer number */ +static +void ReadReal(LPIT8 it8, int inum) +{ + it8->dnum = (double) inum; + + while (isdigit(it8->ch)) { + + it8->dnum = it8->dnum * 10.0 + (it8->ch - '0'); + NextCh(it8); + } + + if (it8->ch == '.') { /* Decimal point */ + + double frac = 0.0; /* fraction */ + int prec = 0; /* precission */ + + NextCh(it8); /* Eats dec. point */ + + while (isdigit(it8->ch)) { + + frac = frac * 10.0 + (it8->ch - '0'); + prec++; + NextCh(it8); + } + + it8->dnum = it8->dnum + (frac / pow10(prec)); + } + + /* Exponent, example 34.00E+20 */ + if (toupper(it8->ch) == 'E') { + + int e; + int sgn; + + NextCh(it8); sgn = 1; + + if (it8->ch == '-') { + + sgn = -1; NextCh(it8); + } + else + if (it8->ch == '+') { + + sgn = +1; + NextCh(it8); + } + + + e = 0; + while (isdigit(it8->ch)) { + + if ((double) e * 10L < INT_MAX) + e = e * 10 + (it8->ch - '0'); + + NextCh(it8); + } + + e = sgn*e; + + it8 -> dnum = it8 -> dnum * pow10(e); + } +} + + + +/* Reads next symbol */ +static +void InSymbol(LPIT8 it8) +{ + char *idptr; + int k; + SYMBOL key; + int sng; + + do { + + while (isseparator(it8->ch)) + NextCh(it8); + + if (isfirstidchar(it8->ch)) { /* Identifier */ + + + k = 0; + idptr = it8->id; + + do { + + if (++k < MAXID) *idptr++ = (char) it8->ch; + + NextCh(it8); + + } while (isidchar(it8->ch)); + + *idptr = '\0'; + xstrupr(it8->id); + + key = BinSrchKey(it8->id); + if (key == SNONE) it8->sy = SIDENT; + else it8->sy = key; + + } + else /* Is a number? */ + if (isdigit(it8->ch) || it8->ch == '.' || it8->ch == '-' || it8->ch == '+') + { + int sign = 1; + + if (it8->ch == '-') { + sign = -1; + NextCh(it8); + } + + it8->inum = 0; + it8->sy = SINUM; + + while (isdigit(it8->ch)) + { + if ((long) it8->inum * 10L > (long) INT_MAX) + { + ReadReal(it8, it8->inum); + it8->sy = SDNUM; + it8->dnum *= sign; + return; + } + + it8->inum = it8->inum * 10 + (it8->ch - '0'); + NextCh(it8); + } + + if (it8->ch == '.') { + + ReadReal(it8, it8->inum); + it8->sy = SDNUM; + it8->dnum *= sign; + return; + } + + it8 -> inum *= sign; + return; + + } + else + switch ((int) it8->ch) { + + case '\0': + case '\x1a': + it8->sy = SEOF; + break; + + + + case '\n': + NextCh(it8); + it8->sy = SEOLN; + it8->lineno++; + break; + + /* Comment */ + + case '#': + NextCh(it8); + while (it8->ch && it8->ch != '\n') + NextCh(it8); + + it8->sy = SCOMMENT; + break; + + /* String. I will support \", \n, \t and \\. */ + /* But otherwise I hardly doubt these will be used ... */ + + case '\'': + case '\"': + idptr = it8->str; + sng = it8->ch; + k = 0; + NextCh(it8); + + while (k < MAXSTR && it8->ch != sng) { + + if (it8->ch == '\n'|| it8->ch == '\r') k = MAXSTR+1; + else { + + if (it8->ch == '\\') + { + NextCh(it8); + + switch (it8->ch) { + + case 'n': *idptr++ = '\n'; break; + case 'r': *idptr++ = '\r'; break; + case 't': *idptr++ = '\t'; break; + case '\\': *idptr++ = '\\'; break; + + default: + *idptr++ = (char) it8->ch; + } + + NextCh(it8); + } + else + { + *idptr++ = (char) it8->ch; + NextCh(it8); + } + + k++; + } + } + + it8->sy = SSTRING; + *idptr = '\0'; + NextCh(it8); + break; + + + default: + it8->sy = SSYNERROR; + NextCh(it8); + + } + + } while (it8->sy == SCOMMENT); +} + +/* Checks end of line separator */ +static +BOOL CheckEOLN(LPIT8 it8) +{ + if (!Check(it8, SEOLN, "Expected separator")) return false; + while (it8 -> sy == SEOLN) + InSymbol(it8); + return true; + +} + +/* Skip a symbol */ + +static +void Skip(LPIT8 it8, SYMBOL sy) +{ + if (it8->sy == sy && it8->sy != SEOF) + InSymbol(it8); +} + +/* Returns a string holding current value */ +static +BOOL GetVal(LPIT8 it8, char* Buffer) +{ + switch (it8->sy) { + + case SIDENT: strncpy(Buffer, it8->id, MAXID-1); break; + case SINUM: sprintf(Buffer, "%d", it8 -> inum); break; + case SDNUM: sprintf(Buffer, "%g", it8 -> dnum); break; + case SSTRING: strncpy(Buffer, it8->str, MAXSTR-1); break; + + + default: + return SynError(it8, "Sample data expected"); + } + + return true; +} + +/* ---------------------------------------------------------- Memory management */ + + + +/* Frees an allocator and owned memory */ +void cmsxIT8Free(LCMSHANDLE hIT8) +{ + LPIT8 it8 = (LPIT8) hIT8; + + if (it8 == NULL) + return; + + if (it8->MemorySink) { + + LPOWNEDMEM p; + LPOWNEDMEM n; + + for (p = it8->MemorySink; p != NULL; p = n) { + + n = p->Next; + if (p->Ptr) free(p->Ptr); + free(p); + } + } + + if (it8->FileBuffer) + free(it8->FileBuffer); + + free(it8); +} + + +/* Allocates a chunk of data, keep linked list */ +static +void* AllocChunk(LPIT8 it8, size_t size) +{ + LPOWNEDMEM ptr1; + void* ptr = malloc(size); + + if (ptr) { + + ZeroMemory(ptr, size); + ptr1 = (LPOWNEDMEM) malloc(sizeof(OWNEDMEM)); + + if (ptr1 == NULL) { + + free(ptr); + return NULL; + } + + ZeroMemory(ptr1, sizeof(OWNEDMEM)); + + ptr1-> Ptr = ptr; + ptr1-> Next = it8 -> MemorySink; + it8 -> MemorySink = ptr1; + } + + return ptr; +} + + +/* Allocates a string */ +static +char *AllocString(LPIT8 it8, const char* str) +{ + int Size = strlen(str)+1; + char *ptr; + ptr = (char *) AllocChunk(it8, Size); + if (ptr) strncpy (ptr, str, Size); + return ptr; +} + +/* Searches through linked list */ + +static +BOOL IsAvailableOnList(LPKEYVALUE p, const char* Key, LPKEYVALUE* LastPtr) +{ + for (; p != NULL; p = p->Next) { + + if (LastPtr) *LastPtr = p; + if (stricmp(Key, p->Keyword) == 0) + return true; + } + + return false; +} + + + +/* Add a property into a linked list */ +static +BOOL AddToList(LPIT8 it8, LPKEYVALUE* Head, const char *Key, const char* Value) +{ + LPKEYVALUE p; + LPKEYVALUE last; + + + /* Check if property is already in list (this is an error) */ + + if (IsAvailableOnList(*Head, Key, &last)) { + cmsSignalError(LCMS_ERRC_ABORTED, "duplicate key <%s>", Key); + return false; + } + + /* Allocate the container */ + p = (LPKEYVALUE) AllocChunk(it8, sizeof(KEYVALUE)); + if (p == NULL) + { + cmsSignalError(LCMS_ERRC_ABORTED, "AddToList: out of memory"); + return false; + } + + /* Store name and value */ + p->Keyword = AllocString(it8, Key); + + if (Value) + p->Value = AllocString(it8, Value); + else + p->Value = NULL; + + p->Next = NULL; + + /* Keep the container in our list */ + if (*Head == NULL) + *Head = p; + else + last->Next = p; + + return true; +} + +static +BOOL AddAvailableProperty(LPIT8 it8, const char* Key) +{ + return AddToList(it8, &it8->ValidKeywords, Key, NULL); +} + + +static +BOOL AddAvailableSampleID(LPIT8 it8, const char* Key) +{ + return AddToList(it8, &it8->ValidSampleID, Key, NULL); +} + + + +/* Init an empty container */ +LCMSHANDLE cmsxIT8Alloc(void) +{ + LPIT8 it8; + int i; + + it8 = (LPIT8) malloc(sizeof(IT8)); + if (it8 == NULL) return NULL; + + ZeroMemory(it8, sizeof(IT8)); + + it8->HeaderList = NULL; + it8->FileBuffer = NULL; + it8->DataFormat = NULL; + it8->Data = NULL; + it8->MemorySink = NULL; + it8->ValidKeywords = NULL; + it8->ValidSampleID = NULL; + + it8 -> sy = SNONE; + it8 -> ch = ' '; + it8 -> Source = NULL; + it8 -> inum = 0; + it8 -> dnum = 0.0; + + it8 -> lineno = 1; + + strcpy(it8->SheetType, "IT8.7/2"); + + /* Initialize predefined properties & data */ + + for (i=0; i < NUMPREDEFINEDPROPS; i++) + AddAvailableProperty(it8, PredefinedProperties[i]); + + for (i=0; i < NUMPREDEFINEDSAMPLEID; i++) + AddAvailableSampleID(it8, PredefinedSampleID[i]); + + + return (LCMSHANDLE) it8; +} + + +const char* cdecl cmsxIT8GetSheetType(LCMSHANDLE hIT8) +{ + LPIT8 it8 = (LPIT8) hIT8; + + return it8 ->SheetType; + +} + +BOOL cmsxIT8SetSheetType(LCMSHANDLE hIT8, const char* Type) +{ + LPIT8 it8 = (LPIT8) hIT8; + + strncpy(it8 ->SheetType, Type, MAXSTR-1); + return true; +} + + + +/* Sets a property */ +BOOL cmsxIT8SetProperty(LCMSHANDLE hIT8, const char* Key, const char *Val) +{ + LPIT8 it8 = (LPIT8) hIT8; + + if (!Val) return false; + if (!*Val) return false; + + return AddToList(it8, &it8 -> HeaderList, Key, Val); +} + + +BOOL cmsxIT8SetPropertyDbl(LCMSHANDLE hIT8, const char* cProp, double Val) +{ + char Buffer[256]; + + sprintf(Buffer, "%g", Val); + return cmsxIT8SetProperty(hIT8, cProp, Buffer); +} + +/* Gets a property */ +const char* cmsxIT8GetProperty(LCMSHANDLE hIT8, const char* Key) +{ + LPIT8 it8 = (LPIT8) hIT8; + LPKEYVALUE p; + + if (IsAvailableOnList(it8 -> HeaderList, Key, &p)) + { + return p -> Value; + } + return NULL; +} + + +double cmsxIT8GetPropertyDbl(LCMSHANDLE hIT8, const char* cProp) +{ + const char *v = cmsxIT8GetProperty(hIT8, cProp); + if (v) return atof(v); + else return 0.0; +} + +/* ----------------------------------------------------------------- Datasets */ + + +static +void AllocateDataFormat(LPIT8 it8) +{ + if (it8 -> DataFormat) return; /* Already allocated */ + + it8 -> nSamples = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_FIELDS")); + + if (it8 -> nSamples <= 0) { + + cmsSignalError(LCMS_ERRC_WARNING, "AllocateDataFormat: Unknown NUMBER_OF_FIELDS, assuming 10"); + it8 -> nSamples = 10; + } + + it8 -> DataFormat = (char**) AllocChunk (it8, (it8->nSamples + 1) * sizeof(char *)); + if (it8->DataFormat == NULL) + { + cmsSignalError(LCMS_ERRC_ABORTED, "AllocateDataFormat: Unable to allocate dataFormat array"); + } + +} + +static +const char *GetDataFormat(LPIT8 it8, int n) +{ + if (it8->DataFormat) + return it8->DataFormat[n]; + + return NULL; +} + +static +BOOL SetDataFormat(LPIT8 it8, int n, const char *label) +{ + if (n > it8 -> nSamples) return false; + + if (!it8->DataFormat) + AllocateDataFormat(it8); + + if (it8->DataFormat) { + + it8->DataFormat[n] = AllocString(it8, label); + } + + return true; +} + + +BOOL cmsxIT8SetDataFormat(LCMSHANDLE h, int n, const char *Sample) +{ + LPIT8 it8 = (LPIT8) h; + return SetDataFormat(it8, n, Sample); +} + +static +void AllocateDataSet(LPIT8 it8) +{ + if (it8 -> Data) return; /* Already allocated */ + + it8-> nSamples = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_FIELDS")); + it8-> nPatches = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_SETS")); + it8-> Data = (char**)AllocChunk (it8, (it8->nSamples + 1) * (it8->nPatches + 1) *sizeof (char*)); + if (it8->Data == NULL) + { + cmsSignalError(-1, "AllocateDataSet: Unable to allocate data array"); + } + +} + +static +char* GetData(LPIT8 it8, int nSet, int nField) +{ + int nSamples = it8 -> nSamples; + int nPatches = it8 -> nPatches; + + if (nSet >= nPatches || nField >= nSamples) + return NULL; + + if (!it8->Data) return NULL; + return it8->Data [nSet * nSamples + nField]; +} + +static +BOOL SetData(LPIT8 it8, int nSet, int nField, char *Val) +{ + if (!it8->Data) + AllocateDataSet(it8); + + if (!it8->Data) return false; + + + if (nSet > it8 -> nPatches) { + + SynError(it8, "Patch %d out of range, there are %d datasets", nSet, it8 -> nPatches); + return false; + } + + if (nField > it8 ->nSamples) { + SynError(it8, "Sample %d out of range, there are %d datasets", nField, it8 ->nSamples); + return false; + } + + + it8->Data [nSet * it8 -> nSamples + nField] = AllocString(it8, Val); + return true; +} + + +/* --------------------------------------------------------------- File I/O */ + + +/* Writes a string to file */ +static +void WriteStr(FILE *f, char *str) +{ + if (str == NULL) + fwrite(" ", 1, 1, f); + else + fwrite(str, 1, strlen(str), f); +} + + +/* Writes full header */ +static +void WriteHeader(LPIT8 it8, FILE *fp) +{ + LPKEYVALUE p; + + WriteStr(fp, it8->SheetType); + WriteStr(fp, "\n"); + for (p = it8->HeaderList; (p != NULL); p = p->Next) + { + if (!IsAvailableOnList(it8-> ValidKeywords, p->Keyword, NULL)) { + + WriteStr(fp, "KEYWORD\t\""); + WriteStr(fp, p->Keyword); + WriteStr(fp, "\"\n"); + + } + + WriteStr(fp, p->Keyword); + if (p->Value) { + + WriteStr(fp, "\t\""); + WriteStr(fp, p->Value); + WriteStr(fp, "\""); + } + WriteStr (fp, "\n"); + } + +} + + +/* Writes the data format */ +static +void WriteDataFormat(FILE *fp, LPIT8 it8) +{ + int i, nSamples; + + if (!it8 -> DataFormat) return; + + WriteStr(fp, "BEGIN_DATA_FORMAT\n"); + nSamples = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_FIELDS")); + + for (i = 0; i < nSamples; i++) { + + WriteStr(fp, it8->DataFormat[i]); + WriteStr(fp, (char*)((i == (nSamples-1)) ? "\n" : "\t")); // C->C++ : cast + } + + WriteStr (fp, "END_DATA_FORMAT\n"); +} + + +/* Writes data array */ +static +void WriteData(FILE *fp, LPIT8 it8) +{ + int i, j; + + if (!it8->Data) return; + + WriteStr (fp, "BEGIN_DATA\n"); + + it8->nPatches = atoi(cmsxIT8GetProperty(it8, "NUMBER_OF_SETS")); + + for (i = 0; i < it8-> nPatches; i++) { + + for (j = 0; j < it8->nSamples; j++) { + + char *ptr = it8->Data[i*it8->nSamples+j]; + + WriteStr(fp, (char*)((ptr == NULL) ? "0.00" : ptr)); // C->C++ : cast + WriteStr(fp, (char*)((j == (it8->nSamples-1)) ? "\n" : "\t")); // C->C++ : cast + } + } + WriteStr (fp, "END_DATA\n"); +} + + + +/* Saves whole file */ +BOOL cmsxIT8SaveToFile(LCMSHANDLE hIT8, const char* cFileName) +{ + FILE *fp; + LPIT8 it8 = (LPIT8) hIT8; + + fp = fopen(cFileName, "wt"); + if (!fp) return false; + WriteHeader(it8, fp); + WriteDataFormat(fp, it8); + WriteData(fp, it8); + fclose(fp); + + return true; +} + + +/* Reads whole file in a memory block */ +static +BOOL ReadFileInMemory(const char *cFileName, char **Buffer, size_t *Len) +{ + FILE *fp; + size_t Size; + char *Ptr; + + fp = fopen(cFileName, "rt"); + if (!fp) return false; + + Size = xfilelength(fileno(fp)); + if (Size <= 0) { + fclose(fp); + return false; + } + + Ptr = (char*)malloc(Size+1); // C->C++ : cast + + Size = fread(Ptr, 1, Size, fp); + fclose(fp); + Ptr[Size] = '\0'; + + *Buffer = Ptr; + *Len = Size; + return true; +} + + +/* -------------------------------------------------------------- Higer lever parsing */ + +static +BOOL DataFormatSection(LPIT8 it8) +{ + int iField = 0; + BOOL Ignoring = false; + + InSymbol(it8); /* Eats "BEGIN_DATA_FORMAT" */ + CheckEOLN(it8); + + while (it8->sy != SEND_DATA_FORMAT && + it8->sy != SEOLN && + it8->sy != SEOF && + it8->sy != SSYNERROR) + { + + if (it8->sy != SIDENT) { + + cmsSignalError(LCMS_ERRC_ABORTED, "Sample type expected"); + it8->sy = SSYNERROR; + return false; + } + + if (!Ignoring && iField > it8->nSamples) { + cmsSignalError(LCMS_ERRC_WARNING, "More than NUMBER_OF_FIELDS fields. Extra is ignored\n"); + Ignoring = true; + } + else { + if (!SetDataFormat(it8, iField, it8->id)) return false; + iField++; + } + + InSymbol(it8); + Skip(it8, SEOLN); + } + + Skip(it8, SEOLN); + Skip(it8, SEND_DATA_FORMAT); + Skip(it8, SEOLN); + return true; +} + + + +static +BOOL DataSection (LPIT8 it8) +{ + int iField = 0; + int iSet = 0; + char Buffer[256]; + + InSymbol(it8); /* Eats "BEGIN_DATA" */ + CheckEOLN(it8); + + while (it8->sy != SEND_DATA && it8->sy != SEOF) + { + if (iField >= it8 -> nSamples) { + iField = 0; + iSet++; + if (!CheckEOLN(it8)) + return false; + } + + if (it8->sy != SEND_DATA && it8->sy != SEOF) { + + if (!GetVal(it8, Buffer)) + return false; + + if (!SetData(it8, iSet, iField, Buffer)) + return false; + + iField++; + + Skip(it8, SEOLN); + InSymbol(it8); + } + } + + Skip(it8, SEOLN); + Skip(it8, SEND_DATA); + Skip(it8, SEOLN); + return true; +} + + + + + + +static +BOOL HeaderSection (LPIT8 it8) +{ + char VarName[MAXID]; + char Buffer[MAXSTR]; + + while (it8->sy != SEOF && + it8->sy != SSYNERROR && + it8->sy != SBEGIN_DATA_FORMAT && + it8->sy != SBEGIN_DATA) { + + + switch (it8 -> sy) { + + case SKEYWORD: + InSymbol(it8); + if (!Check(it8, SSTRING, "Keyword expected")) return false; + if (!AddAvailableProperty(it8, it8 -> str)) return false; + InSymbol(it8); + break; + + + case SIDENT: + strncpy(VarName, it8->id, MAXID-1); + if (!IsAvailableOnList(it8-> ValidKeywords, VarName, NULL)) + return SynError(it8, "Undefined keyword '%s'", VarName); + + InSymbol(it8); + GetVal(it8, Buffer); + cmsxIT8SetProperty((LCMSHANDLE) it8, VarName, Buffer); + InSymbol(it8); + break; + + + case SEOLN: break; + + default: + return SynError(it8, "expected keyword or identifier"); + } + + Skip(it8, SEOLN); + } + + return true; + +} + + +static +BOOL ParseIT8(LPIT8 it8) +{ + + InSymbol(it8); + + if (it8->sy == SIDENT) { + + strncpy(it8->SheetType, it8->id, MAXSTR-1); + InSymbol(it8); + + /* if (!AddAvailableProperty(it8, it8 -> id)) return false; */ + /* cmsxIT8SetProperty((LCMSHANDLE) it8, it8->id, NULL); */ + } + + Skip(it8, SEOLN); + + while (it8-> sy != SEOF && + it8-> sy != SSYNERROR) { + + switch (it8 -> sy) { + + case SBEGIN_DATA_FORMAT: + if (!DataFormatSection(it8)) return false; + break; + + case SBEGIN_DATA: + if (!DataSection(it8)) return false; + break; + + case SEOLN: + Skip(it8, SEOLN); + break; + + default: + if (!HeaderSection(it8)) return false; + } + + } + + return true; +} + + +static +void CleanPatchName(char *cell) +{ + char cleaned[256], Buffer[256], ident[256]; + char *orig = cell, *id; + int n, lOneNum; + + + id = ident; + while (*cell && isalpha(*cell)) + { + *id++ = (char) toupper(*cell); + cell++; + } + *id = 0; + strcpy(cleaned, ident); + + + n = 0; + lOneNum = false; + while (*cell && isdigit(*cell)) + { + n = n * 10 + (*cell -'0'); + cell++; + lOneNum = true; + } + + if (lOneNum) { + + sprintf(Buffer, "%d", n); + strcat(cleaned, Buffer); + } + + if (strcmp(cleaned, "GS0") == 0) + strcpy(orig, "DMIN"); + else + if (strcmp(cleaned, "GS23") == 0) + strcpy(orig, "DMAX"); + else + strcpy(orig, cleaned); +} + + +/* Init useful pointers */ + +static +void CookPointers(LPIT8 it8) +{ + int idField, i; + char* Fld; + + it8 -> SampleID = 0; + for (idField = 0; idField < it8 -> nSamples; idField++) + { + Fld = it8->DataFormat[idField]; + if (!Fld) continue; + + if (strcmp(Fld, "SAMPLE_ID") == 0) { + it8 -> SampleID = idField; + + + for (i=0; i < it8 -> nPatches; i++) { + + char *Data = GetData(it8, i, idField); + if (Data) { + char Buffer[256]; + + strncpy(Buffer, Data, 255); + CleanPatchName(Buffer); + + if (strlen(Buffer) <= strlen(Data)) + strcpy(Data, Buffer); + else + SetData(it8, i, idField, Buffer); + + } + } + + } + } + +} + + +/* ---------------------------------------------------------- Exported routines */ + + +LCMSHANDLE cmsxIT8LoadFromMem(void *Ptr, size_t len) +{ + LCMSHANDLE hIT8 = cmsxIT8Alloc(); + LPIT8 it8 = (LPIT8) hIT8; + + if (!hIT8) return NULL; + it8 ->FileBuffer = (char*) malloc(len + 1); + + strncpy(it8 ->FileBuffer, (const char*)Ptr, len); // C->C++ : cast + strncpy(it8->FileName, "", MAX_PATH-1); + it8-> Source = it8 -> FileBuffer; + + ParseIT8(it8); + CookPointers(it8); + + free(it8->FileBuffer); + it8 -> FileBuffer = NULL; + + return hIT8; + + +} + + +LCMSHANDLE cmsxIT8LoadFromFile(const char* cFileName) +{ + + LCMSHANDLE hIT8 = cmsxIT8Alloc(); + LPIT8 it8 = (LPIT8) hIT8; + size_t Len; + + if (!hIT8) return NULL; + if (!ReadFileInMemory(cFileName, &it8->FileBuffer, &Len)) return NULL; + + strncpy(it8->FileName, cFileName, MAX_PATH-1); + it8-> Source = it8 -> FileBuffer; + + ParseIT8(it8); + CookPointers(it8); + + free(it8->FileBuffer); + it8 -> FileBuffer = NULL; + + return hIT8; + +} + +int cmsxIT8EnumDataFormat(LCMSHANDLE hIT8, char ***SampleNames) +{ + LPIT8 it8 = (LPIT8) hIT8; + + *SampleNames = it8 -> DataFormat; + return it8 -> nSamples; +} + + +int cmsxIT8EnumProperties(LCMSHANDLE hIT8, char ***PropertyNames) +{ + LPIT8 it8 = (LPIT8) hIT8; + LPKEYVALUE p; + int n; + char **Props; + + /* Pass#1 - count properties */ + + n = 0; + for (p = it8 -> HeaderList; p != NULL; p = p->Next) { + n++; + } + + + Props = (char **) malloc(sizeof(char *) * n); + + /* Pass#2 - Fill pointers */ + n = 0; + for (p = it8 -> HeaderList; p != NULL; p = p->Next) { + Props[n++] = p -> Keyword; + } + + *PropertyNames = Props; + return n; +} + +static +int LocatePatch(LPIT8 it8, const char* cPatch) +{ + int i; + const char *data; + + for (i=0; i < it8-> nPatches; i++) { + + data = GetData(it8, i, it8->SampleID); + + if (data != NULL) { + + if (stricmp(data, cPatch) == 0) + return i; + } + } + + return -1; +} + + +static +int LocateEmptyPatch(LPIT8 it8, const char* cPatch) +{ + int i; + const char *data; + + for (i=0; i < it8-> nPatches; i++) { + + data = GetData(it8, i, it8->SampleID); + + if (data == NULL) + return i; + + } + + return -1; +} + +static +int LocateSample(LPIT8 it8, const char* cSample) +{ + int i; + const char *fld; + + for (i=0; i < it8->nSamples; i++) { + + fld = GetDataFormat(it8, i); + if (stricmp(fld, cSample) == 0) + return i; + } + + return -1; +} + + +BOOL cmsxIT8GetDataSetByPos(LCMSHANDLE hIT8, int col, int row, char* Val, int ValBufferLen) +{ + LPIT8 it8 = (LPIT8) hIT8; + const char *data = GetData(it8, row, col); + + if (!data) + { + *Val = '\0'; + return false; + } + + strncpy(Val, data, ValBufferLen-1); + return true; +} + + + +BOOL cmsxIT8GetDataSet(LCMSHANDLE hIT8, const char* cPatch, + const char* cSample, + char* Val, int ValBuffLen) +{ + LPIT8 it8 = (LPIT8) hIT8; + int iField, iSet; + + + iField = LocateSample(it8, cSample); + if (iField < 0) { + /* cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't find data field %s\n", cSample); */ + return false; + } + + + iSet = LocatePatch(it8, cPatch); + if (iSet < 0) { + + /* cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't find patch '%s'\n", cPatch); */ + return false; + } + + strncpy(Val, GetData(it8, iSet, iField), ValBuffLen-1); + return true; +} + + +BOOL cmsxIT8GetDataSetDbl(LCMSHANDLE it8, const char* cPatch, const char* cSample, double* v) +{ + char Buffer[20]; + + if (cmsxIT8GetDataSet(it8, cPatch, cSample, Buffer, 20)) { + + *v = atof(Buffer); + return true; + } else + return false; +} + + + +BOOL cmsxIT8SetDataSet(LCMSHANDLE hIT8, const char* cPatch, + const char* cSample, + char *Val) +{ + LPIT8 it8 = (LPIT8) hIT8; + int iField, iSet; + + + iField = LocateSample(it8, cSample); + + if (iField < 0) { + + cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't find data field %s\n", cSample); + return false; + } + + + if (it8-> nPatches == 0) { + + AllocateDataFormat(it8); + AllocateDataSet(it8); + CookPointers(it8); + } + + + if (stricmp(cSample, "SAMPLE_ID") == 0) + { + + iSet = LocateEmptyPatch(it8, cPatch); + if (iSet < 0) { + cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't add more patches '%s'\n", cPatch); + return false; + } + iField = it8 -> SampleID; + } + else { + iSet = LocatePatch(it8, cPatch); + if (iSet < 0) { + + cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't find patch '%s'\n", cPatch); + return false; + } + } + + return SetData(it8, iSet, iField, Val); +} + + +BOOL cmsxIT8SetDataSetDbl(LCMSHANDLE hIT8, const char* cPatch, + const char* cSample, + double Val) +{ + char Buff[256]; + + sprintf(Buff, "%g", Val); + return cmsxIT8SetDataSet(hIT8, cPatch, cSample, Buff); + +} + + +const char* cmsxIT8GetPatchName(LCMSHANDLE hIT8, int nPatch, char* buffer) +{ + LPIT8 it8 = (LPIT8) hIT8; + char* Data = GetData(it8, nPatch, it8->SampleID); + if (!Data) return NULL; + + strcpy(buffer, Data); + return buffer; +} + + +const char* cmsxIT8GenericPatchName(int nPatch, char* buffer) +{ + int row, col; + + if (nPatch >= cmsxIT8_NORMAL_PATCHES) + return "$CUSTOM"; + + if (nPatch >= (cmsxIT8_ROWS * cmsxIT8_COLS)) { + + nPatch -= cmsxIT8_ROWS * cmsxIT8_COLS; + if (nPatch == 0) + return "DMIN"; + else + if (nPatch == cmsxIT8_GRAYCOLS - 1) + return "DMAX"; + else + sprintf(buffer, "GS%d", nPatch); + return buffer; + } + + + row = nPatch / cmsxIT8_COLS; + col = nPatch % cmsxIT8_COLS; + + sprintf (buffer, "%c%d", 'A'+row, col+1); + return buffer; +} + + + + +int cmsxIT8GenericPatchNum(const char *name) +{ + int i; + char Buff[256]; + + + for (i=0; i < cmsxIT8_TOTAL_PATCHES; i++) + if (stricmp(cmsxIT8GenericPatchName(i, Buff), name) == 0) + return i; + + return -1; +} + + + + + diff --git a/src/libs/lprof/lcmsprf.h b/src/libs/lprof/lcmsprf.h new file mode 100644 index 00000000..00c7ac40 --- /dev/null +++ b/src/libs/lprof/lcmsprf.h @@ -0,0 +1,485 @@ +/* +Little cms - profiler construction set +Copyright (C) 1998-2001 Marti Maria + +THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, +EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY +WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, +INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF +LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +OF THIS SOFTWARE. + +This file is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +As a special exception to the GNU General Public License, if you +distribute this file as part of a program that contains a +configuration script generated by Autoconf, you may include it under +the same distribution terms that you use for the rest of that program. +*/ + +/* Version 1.09a */ + +#ifndef __cmsprf_H + +#include +#include LCMS_HEADER + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef NON_WINDOWS +# ifndef stricmp +# define stricmp strcasecmp +# endif +#endif + +#ifndef max +#define max(a,b) ((a) > (b)?(a):(b)) +#endif + + +/* Matrix operations - arbitrary size ----------------------------------------------------- */ + +typedef struct { + + int Cols, Rows; + double** Values; + + } MATN,FAR* LPMATN; + +// See B.K.O #148930: compile with lcms v.1.17 +#if (LCMS_VERSION > 116) +typedef LCMSBOOL BOOL; +#endif + +LPMATN cdecl MATNalloc(int Rows, int Cols); +void cdecl MATNfree (LPMATN mat); +LPMATN cdecl MATNmult(LPMATN a1, LPMATN a2); +double cdecl MATNcross(LPMATN a); +void cdecl MATNscalar (LPMATN a, double scl, LPMATN b); +LPMATN cdecl MATNtranspose (LPMATN a); +BOOL cdecl MATNsolve(LPMATN a, LPMATN b); + + +/* IT8.7 / CGATS.17-200x handling -------------------------------------------------------- */ + +#define cmsxIT8_ROWS 12 +#define cmsxIT8_COLS 22 +#define cmsxIT8_GRAYCOLS 24 +#define cmsxIT8_NORMAL_PATCHES (cmsxIT8_ROWS*cmsxIT8_COLS + cmsxIT8_GRAYCOLS) +#define cmsxIT8_CUSTOM_PATCHES 10 +#define cmsxIT8_TOTAL_PATCHES (cmsxIT8_NORMAL_PATCHES + cmsxIT8_CUSTOM_PATCHES) + + +LCMSHANDLE cdecl cmsxIT8Alloc(void); +void cdecl cmsxIT8Free(LCMSHANDLE cmsxIT8); +LCMSHANDLE cdecl cmsxIT8LoadFromFile(const char* cFileName); +LCMSHANDLE cdecl cmsxIT8LoadFromMem(void *Ptr, size_t len); +BOOL cdecl cmsxIT8SaveToFile(LCMSHANDLE cmsxIT8, const char* cFileName); +const char* cdecl cmsxIT8GetSheetType(LCMSHANDLE hIT8); +BOOL cdecl cmsxIT8SetSheetType(LCMSHANDLE hIT8, const char* Type); +const char* cdecl cmsxIT8GetPatchName(LCMSHANDLE hIT8, int nPatch, char* buffer); +BOOL cdecl cmsxIT8SetProperty(LCMSHANDLE hcmsxIT8, const char* cProp, const char *Str); +BOOL cdecl cmsxIT8SetPropertyDbl(LCMSHANDLE hcmsxIT8, const char* cProp, double Val); +const char* cdecl cmsxIT8GetProperty(LCMSHANDLE hcmsxIT8, const char* cProp); +double cdecl cmsxIT8GetPropertyDbl(LCMSHANDLE hcmsxIT8, const char* cProp); +int cdecl cmsxIT8EnumProperties(LCMSHANDLE cmsxIT8, char ***PropertyNames); +int cdecl cmsxIT8EnumDataFormat(LCMSHANDLE cmsxIT8, char ***SampleNames); +BOOL cdecl cmsxIT8SetDataFormat(LCMSHANDLE cmsxIT8, int n, const char *Sample); + +BOOL cdecl cmsxIT8GetDataSetByPos(LCMSHANDLE IT8, int col, int row, char* Val, int ValBufferLen); + +BOOL cdecl cmsxIT8GetDataSet(LCMSHANDLE cmsxIT8, const char* cPatch, + const char* cSample, + char* Val, int ValBuffLen); + +BOOL cdecl cmsxIT8GetDataSetDbl(LCMSHANDLE cmsxIT8, const char* cPatch, const char* cSample, double* v); + +BOOL cdecl cmsxIT8SetDataSet(LCMSHANDLE cmsxIT8, const char* cPatch, + const char* cSample, + char *Val); + +BOOL cdecl cmsxIT8SetDataSetDbl(LCMSHANDLE cmsxIT8, const char* cPatch, const char* cSample, double Val); + +const char *cdecl cmsxIT8GenericPatchName(int nPatch, char* buffer); + + + +/* Patch collections (measurement lists) -------------------------------------------------- */ + +#define PATCH_HAS_Lab 0x00000001 +#define PATCH_HAS_XYZ 0x00000002 +#define PATCH_HAS_RGB 0x00000004 +#define PATCH_HAS_CMY 0x00000008 +#define PATCH_HAS_CMYK 0x00000010 +#define PATCH_HAS_HEXACRM 0x00000020 +#define PATCH_HAS_STD_Lab 0x00010000 +#define PATCH_HAS_STD_XYZ 0x00020000 +#define PATCH_HAS_XYZ_PROOF 0x00100000 +#define PATCH_HAS_MEAN_DE 0x01000000 +#define PATCH_HAS_STD_DE 0x02000000 +#define PATCH_HAS_CHISQ 0x04000000 + + +#define MAXPATCHNAMELEN 20 +/* A patch in memory */ + +typedef struct { + + DWORD dwFlags; /* Is quite possible to have colorant in only */ + /* some patches of sheet, so mark each entry with */ + /* the values it has. */ + + char Name[MAXPATCHNAMELEN]; + + cmsCIELab Lab; /* The tristimulus values of target */ + cmsCIEXYZ XYZ; + + cmsCIEXYZ XYZProof; /* The absolute XYZ value returned by profile */ + /* (gamut constrained to device) */ + + union { /* The possible colorants. Only one space is */ + /* allowed...obviously only one set of */ + /* device-dependent values per patch does make sense. */ + double RGB[3]; + double CMY[3]; + double CMYK[4]; + double Hexa[MAXCHANNELS]; + + } Colorant; + + double dEStd; /* Standard deviation */ + double ChiSq; /* Chi-square parameter (mean of STD of colorants) */ + double dEMean; /* Mean dE */ + + } PATCH, FAR* LPPATCH; + + + +/* A set of patches is simply an array of bools, TRUE if the patch */ +/* belong to the set, false otherwise. */ + +typedef BOOL* SETOFPATCHES; + +/* This struct holds whole Patches collection */ + +typedef struct _measurement { + + int nPatches; + LPPATCH Patches; + SETOFPATCHES Allowed; + + } MEASUREMENT,FAR *LPMEASUREMENT; + + +void cdecl cmsxPCollFreeMeasurements(LPMEASUREMENT m); +SETOFPATCHES cdecl cmsxPCollBuildSet(LPMEASUREMENT m, BOOL lDefault); + +BOOL cdecl cmsxPCollBuildMeasurement(LPMEASUREMENT m, + const char *ReferenceSheet, + const char *MeasurementSheet, + DWORD dwNeededSamplesType); + +int cdecl cmsxPCollCountSet(LPMEASUREMENT m, SETOFPATCHES Set); +BOOL cdecl cmsxPCollValidatePatches(LPMEASUREMENT m, DWORD dwFlags); + +BOOL cdecl cmsxPCollLoadFromSheet(LPMEASUREMENT m, LCMSHANDLE hSheet); +BOOL cdecl cmsxPCollSaveToSheet(LPMEASUREMENT m, LCMSHANDLE it8); + +LPPATCH cdecl cmsxPCollGetPatch(LPMEASUREMENT m, int n); +LPPATCH cdecl cmsxPCollGetPatchByName(LPMEASUREMENT m, const char* Name, int* lpPos); +LPPATCH cdecl cmsxPCollGetPatchByPos(LPMEASUREMENT m, int row, int col); +LPPATCH cdecl cmsxPCollAddPatchRGB(LPMEASUREMENT m, const char *Name, + double r, double g, double b, + LPcmsCIEXYZ XYZ, LPcmsCIELab Lab); + +void cdecl cmsxPCollLinearizePatches(LPMEASUREMENT m, SETOFPATCHES Valids, + LPGAMMATABLE Gamma[3]); + +/* Extraction utilities */ + +/* Collect "need" patches of the specific kind, return the number of collected (that */ +/* could be less if set of patches is exhausted) */ + +void cdecl cmsxPCollPatchesGS(LPMEASUREMENT m, SETOFPATCHES Result); + +int cdecl cmsxPCollPatchesNearRGB(LPMEASUREMENT m, SETOFPATCHES Valids, + double r, double g, double b, int need, SETOFPATCHES Result); + +int cdecl cmsxPCollPatchesNearNeutral(LPMEASUREMENT m, SETOFPATCHES Valids, + int need, SETOFPATCHES Result); + +int cdecl cmsxPCollPatchesNearPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, + int nChannel, int need, SETOFPATCHES Result); + +int cdecl cmsxPCollPatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids, + double Lmin, double LMax, double a, double b, SETOFPATCHES Result); + +int cdecl cmsxPCollPatchesInGamutLUT(LPMEASUREMENT m, SETOFPATCHES Valids, + LPLUT Gamut, SETOFPATCHES Result); + +/* Find important values */ + +LPPATCH cdecl cmsxPCollFindWhite(LPMEASUREMENT m, SETOFPATCHES Valids, double* Distance); +LPPATCH cdecl cmsxPCollFindBlack(LPMEASUREMENT m, SETOFPATCHES Valids, double* Distance); +LPPATCH cdecl cmsxPCollFindPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, int Channel, double* Distance); + +/* Multiple linear regression stuff ---------------------------------------- */ + + +/* A measurement of error */ + +typedef struct { + + double SSE; /* The error sum of squares */ + double MSE; /* The error mean sum of squares */ + double SSR; /* The regression sum of squares */ + double MSR; /* The regression mean sum of squares */ + double SSTO; /* Total sum of squares */ + double F; /* The Fisher-F value (MSR / MSE) */ + double R2; /* Proportion of variability explained by the regression */ + /* (root is Pearson correlation coefficient) */ + + double R2adj; /* The adjusted coefficient of multiple determination. */ + /* R2-adjusted or R2adj. This is calculated as */ + /* R2adj = 1 - (1-R2)(N-n-1)/(N-1) */ + /* and used as multiple correlation coefficient */ + /* (really, it should be square root) */ + + } MLRSTATISTICS, FAR* LPMLRSTATISTICS; + + +int cdecl cmsxRegressionCreateMatrix(LPMEASUREMENT m, SETOFPATCHES Allowed, int nterms, + int ColorSpace, + LPMATN* lpMat, LPMLRSTATISTICS Stat); + +BOOL cdecl cmsxRegressionRGB2Lab(double r, double g, double b, + LPMATN tfm, LPcmsCIELab Lab); + +BOOL cdecl cmsxRegressionRGB2XYZ(double r, double g, double b, + LPMATN tfm, LPcmsCIEXYZ XYZ); + +BOOL cdecl cmsxRegressionInterpolatorRGB(LPMEASUREMENT m, + int ColorSpace, + int RegressionTerms, + BOOL lUseLocalPatches, + int MinPatchesToCollect, + double r, double g, double b, + void* Res); + + + +/* Levenberg-Marquardt ---------------------------------------------------------------------- */ + +LCMSHANDLE cdecl cmsxLevenbergMarquardtInit(LPSAMPLEDCURVE x, LPSAMPLEDCURVE y, double sig, + double a[], + int ma, + void (*funcs)(double, double[], double*, double[], int) + ); + +double cdecl cmsxLevenbergMarquardtAlamda(LCMSHANDLE hMRQ); +double cdecl cmsxLevenbergMarquardtChiSq(LCMSHANDLE hMRQ); +BOOL cdecl cmsxLevenbergMarquardtIterate(LCMSHANDLE hMRQ); +BOOL cdecl cmsxLevenbergMarquardtFree(LCMSHANDLE hMRQ); + + +/* Convex hull geometric routines ------------------------------------------------------------ */ + +LCMSHANDLE cdecl cmsxHullInit(void); +void cdecl cmsxHullDone(LCMSHANDLE hHull); +BOOL cdecl cmsxHullAddPoint(LCMSHANDLE hHull, int x, int y, int z); +BOOL cdecl cmsxHullComputeHull(LCMSHANDLE hHull); +char cdecl cmsxHullCheckpoint(LCMSHANDLE hHull, int x, int y, int z); +BOOL cdecl cmsxHullDumpVRML(LCMSHANDLE hHull, const char* fname); + + +/* Linearization ---------------------------------------------------------------------------- */ + + +#define MEDIUM_REFLECTIVE_D50 0 /* Used for scanner targets */ +#define MEDIUM_TRANSMISSIVE 1 /* Used for monitors & projectors */ + +void cdecl cmsxComputeLinearizationTables(LPMEASUREMENT m, + int ColorSpace, + LPGAMMATABLE Lin[3], + int nResultingPoints, + int Medium); + + +void cdecl cmsxCompleteLabOfPatches(LPMEASUREMENT m, SETOFPATCHES Valids, int Medium); +LPGAMMATABLE cdecl cmsxEstimateGamma(LPSAMPLEDCURVE X, LPSAMPLEDCURVE Y, int nResultingPoints); + +void cdecl cmsxApplyLinearizationTable(double In[3], LPGAMMATABLE Gamma[3], double Out[3]); +void cdecl cmsxApplyLinearizationGamma(WORD In[3], LPGAMMATABLE Gamma[3], WORD Out[3]); + +/* Support routines ---------------------------------------------------------------------- */ + +double cdecl _cmsxSaturate65535To255(double d); +double cdecl _cmsxSaturate255To65535(double d); +void cdecl _cmsxClampXYZ100(LPcmsCIEXYZ xyz); + +/* Matrix shaper profiler API ------------------------------------------------------------- */ + + +BOOL cdecl cmsxComputeMatrixShaper(const char* ReferenceSheet, + const char* MeasurementSheet, + int Medium, + LPGAMMATABLE TransferCurves[3], + LPcmsCIEXYZ WhitePoint, + LPcmsCIEXYZ BlackPoint, + LPcmsCIExyYTRIPLE Primaries); + + +/* Common to all profilers ------------------------------------------------------------------- */ + +#define MAX_STR 256 + +typedef int (* cmsxGAUGER)(const char *Label, int nMin, int nMax, int Pos); +typedef int (* cmsxPRINTF)(const char *Frm, ...); + +typedef struct { + + /* Files */ + char ReferenceSheet[MAX_PATH]; + char MeasurementSheet[MAX_PATH]; + char OutputProfileFile[MAX_PATH]; + + /* Some infos */ + char Description[MAX_STR]; + char Manufacturer[MAX_STR]; + char Model[MAX_STR]; + char Copyright[MAX_STR]; + + /* Callbacks */ + cmsxGAUGER Gauger; + cmsxPRINTF printf; + + /* EndPoints */ + cmsCIEXYZ WhitePoint; /* Black point in 0.xxx notation */ + cmsCIEXYZ BlackPoint; /* Black point in 0.xxx notation */ + cmsCIExyYTRIPLE Primaries; /* The primaries */ + LPGAMMATABLE Gamma[3]; /* Gamma curves */ + + /* Profile */ + cmsHPROFILE hProfile; /* handle to profile */ + + icProfileClassSignature DeviceClass; + icColorSpaceSignature ColorSpace; + + int PCSType; /* PT_XYZ or PT_Lab */ + int CLUTPoints; /* Final CLUT resolution */ + int ProfileVerbosityLevel; /* 0=minimum, 1=additional, 2=Verbose, 3=Any suitable */ + + + /* Measurement */ + MEASUREMENT m; /* Contains list of available patches */ + int Medium; + + + /* RGB Gamut hull */ + LCMSHANDLE hRGBHull; /* Contains bobbin of valid RGB values */ + + /* CIECAM97s */ + BOOL lUseCIECAM97s; /* Use CIECAM97s for chromatic adaptation? */ + + cmsViewingConditions device; /* Viewing condition of source */ + cmsViewingConditions PCS; /* Viewing condition of PCS */ + + LCMSHANDLE hDevice; /* CIECAM97s models used for adaptation */ + LCMSHANDLE hPCS; /* and viewing conditions */ + + + } PROFILERCOMMONDATA,FAR* LPPROFILERCOMMONDATA; + + +/* Shared routines */ + +BOOL cdecl cmsxEmbedCharTarget(LPPROFILERCOMMONDATA hdr); +BOOL cdecl cmsxEmbedMatrixShaper(LPPROFILERCOMMONDATA hdr); +BOOL cdecl cmsxEmbedTextualInfo(LPPROFILERCOMMONDATA hdr); + +int cdecl cmsxFindOptimumNumOfTerms(LPPROFILERCOMMONDATA hdr, int nMaxTerms, BOOL* lAllOk); +void cdecl cmsxChromaticAdaptationAndNormalization(LPPROFILERCOMMONDATA hdr, LPcmsCIEXYZ xyz, BOOL lReverse); +void cdecl cmsxInitPCSViewingConditions(LPPROFILERCOMMONDATA hdr); +void cdecl cmsxComputeGamutHull(LPPROFILERCOMMONDATA hdr); +BOOL cdecl cmsxChoosePCS(LPPROFILERCOMMONDATA hdr); + +/* Monitor profiler API ------------------------------------------------------------------- */ + +typedef struct { + + PROFILERCOMMONDATA hdr; + + + LPGAMMATABLE Prelinearization[3]; /* Canonic gamma */ + LPGAMMATABLE ReverseTables[3]; /* Reverse (direct) gamma */ + LPGAMMATABLE PreLab[3]; + LPGAMMATABLE PreLabRev[3]; + + + MAT3 PrimariesMatrix; + MAT3 PrimariesMatrixRev; + + + } MONITORPROFILERDATA,FAR* LPMONITORPROFILERDATA; + + + +BOOL cdecl cmsxMonitorProfilerInit(LPMONITORPROFILERDATA sys); +BOOL cdecl cmsxMonitorProfilerDo(LPMONITORPROFILERDATA sys); + + +/* Scanner profiler API ------------------------------------------------------------------- */ + + +typedef struct { + + PROFILERCOMMONDATA hdr; + + LPGAMMATABLE Prelinearization[3]; + + LPMATN HiTerms; /* Regression matrix of many terms */ + LPMATN LoTerms; /* Low order regression matrix used for extrapolation */ + + BOOL lLocalConvergenceExtrapolation; + + + } SCANNERPROFILERDATA,FAR* LPSCANNERPROFILERDATA; + + + + +BOOL cdecl cmsxScannerProfilerInit(LPSCANNERPROFILERDATA sys); +BOOL cdecl cmsxScannerProfilerDo(LPSCANNERPROFILERDATA sys); + +/* ----------------------------------------------------------- end of profilers */ + + +#ifdef __cplusplus +} +#endif + +#define __cmsprf_H +#endif diff --git a/src/libs/sqlite2/Makefile.am b/src/libs/sqlite2/Makefile.am new file mode 100644 index 00000000..dca86fe0 --- /dev/null +++ b/src/libs/sqlite2/Makefile.am @@ -0,0 +1,43 @@ +#stolen Makefile.am from amarok + +noinst_LTLIBRARIES = libsqlite2.la + +INCLUDES = $(all_includes) + +libsqlite2_la_CFLAGS = -w + +libsqlite2_la_LDFLAGS = $(LIBPTHREAD) + +libsqlite2_la_SOURCES = \ + attach.c \ + auth.c \ + btree.c \ + btree_rb.c \ + build.c \ + copy.c \ + date.c \ + delete.c \ + encode.c \ + expr.c \ + func.c \ + hash.c \ + insert.c \ + main.c \ + opcodes.c \ + os.c \ + pager.c \ + parse.c \ + pragma.c \ + printf.c \ + random.c \ + select.c \ + shell.c \ + table.c \ + tokenize.c \ + trigger.c \ + update.c \ + util.c \ + vacuum.c \ + vdbe.c \ + vdbeaux.c \ + where.c diff --git a/src/libs/sqlite2/README b/src/libs/sqlite2/README new file mode 100644 index 00000000..eefbd49e --- /dev/null +++ b/src/libs/sqlite2/README @@ -0,0 +1,2 @@ +This folder contents sqlite version 2 source code used to backport old +digiKam database < 0.8.0 to new database based on sqlite version 3 \ No newline at end of file diff --git a/src/libs/sqlite2/attach.c b/src/libs/sqlite2/attach.c new file mode 100644 index 00000000..316d0d2a --- /dev/null +++ b/src/libs/sqlite2/attach.c @@ -0,0 +1,311 @@ +/* +** 2003 April 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to implement the ATTACH and DETACH commands. +** +** $Id: attach.c 326789 2004-07-07 21:25:56Z pahlibar $ +*/ +#include "sqliteInt.h" + +/* +** This routine is called by the parser to process an ATTACH statement: +** +** ATTACH DATABASE filename AS dbname +** +** The pFilename and pDbname arguments are the tokens that define the +** filename and dbname in the ATTACH statement. +*/ +void sqliteAttach(Parse *pParse, Token *pFilename, Token *pDbname, Token *pKey){ + Db *aNew; + int rc, i; + char *zFile, *zName; + sqlite *db; + Vdbe *v; + + v = sqliteGetVdbe(pParse); + sqliteVdbeAddOp(v, OP_Halt, 0, 0); + if( pParse->explain ) return; + db = pParse->db; + if( db->file_format<4 ){ + sqliteErrorMsg(pParse, "cannot attach auxiliary databases to an " + "older format master database", 0); + pParse->rc = SQLITE_ERROR; + return; + } + if( db->nDb>=MAX_ATTACHED+2 ){ + sqliteErrorMsg(pParse, "too many attached databases - max %d", + MAX_ATTACHED); + pParse->rc = SQLITE_ERROR; + return; + } + + zFile = 0; + sqliteSetNString(&zFile, pFilename->z, pFilename->n, 0); + if( zFile==0 ) return; + sqliteDequote(zFile); +#ifndef SQLITE_OMIT_AUTHORIZATION + if( sqliteAuthCheck(pParse, SQLITE_ATTACH, zFile, 0, 0)!=SQLITE_OK ){ + sqliteFree(zFile); + return; + } +#endif /* SQLITE_OMIT_AUTHORIZATION */ + + zName = 0; + sqliteSetNString(&zName, pDbname->z, pDbname->n, 0); + if( zName==0 ) return; + sqliteDequote(zName); + for(i=0; inDb; i++){ + if( db->aDb[i].zName && sqliteStrICmp(db->aDb[i].zName, zName)==0 ){ + sqliteErrorMsg(pParse, "database %z is already in use", zName); + pParse->rc = SQLITE_ERROR; + sqliteFree(zFile); + return; + } + } + + if( db->aDb==db->aDbStatic ){ + aNew = sqliteMalloc( sizeof(db->aDb[0])*3 ); + if( aNew==0 ) return; + memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2); + }else{ + aNew = sqliteRealloc(db->aDb, sizeof(db->aDb[0])*(db->nDb+1) ); + if( aNew==0 ) return; + } + db->aDb = aNew; + aNew = &db->aDb[db->nDb++]; + memset(aNew, 0, sizeof(*aNew)); + sqliteHashInit(&aNew->tblHash, SQLITE_HASH_STRING, 0); + sqliteHashInit(&aNew->idxHash, SQLITE_HASH_STRING, 0); + sqliteHashInit(&aNew->trigHash, SQLITE_HASH_STRING, 0); + sqliteHashInit(&aNew->aFKey, SQLITE_HASH_STRING, 1); + aNew->zName = zName; + rc = sqliteBtreeFactory(db, zFile, 0, MAX_PAGES, &aNew->pBt); + if( rc ){ + sqliteErrorMsg(pParse, "unable to open database: %s", zFile); + } +#if SQLITE_HAS_CODEC + { + extern int sqliteCodecAttach(sqlite*, int, void*, int); + char *zKey = 0; + int nKey; + if( pKey && pKey->z && pKey->n ){ + sqliteSetNString(&zKey, pKey->z, pKey->n, 0); + sqliteDequote(zKey); + nKey = strlen(zKey); + }else{ + zKey = 0; + nKey = 0; + } + sqliteCodecAttach(db, db->nDb-1, zKey, nKey); + } +#endif + sqliteFree(zFile); + db->flags &= ~SQLITE_Initialized; + if( pParse->nErr ) return; + if( rc==SQLITE_OK ){ + rc = sqliteInit(pParse->db, &pParse->zErrMsg); + } + if( rc ){ + int i = db->nDb - 1; + assert( i>=2 ); + if( db->aDb[i].pBt ){ + sqliteBtreeClose(db->aDb[i].pBt); + db->aDb[i].pBt = 0; + } + sqliteResetInternalSchema(db, 0); + pParse->nErr++; + pParse->rc = SQLITE_ERROR; + } +} + +/* +** This routine is called by the parser to process a DETACH statement: +** +** DETACH DATABASE dbname +** +** The pDbname argument is the name of the database in the DETACH statement. +*/ +void sqliteDetach(Parse *pParse, Token *pDbname){ + int i; + sqlite *db; + Vdbe *v; + Db *pDb; + + v = sqliteGetVdbe(pParse); + sqliteVdbeAddOp(v, OP_Halt, 0, 0); + if( pParse->explain ) return; + db = pParse->db; + for(i=0; inDb; i++){ + pDb = &db->aDb[i]; + if( pDb->pBt==0 || pDb->zName==0 ) continue; + if( strlen(pDb->zName)!=pDbname->n ) continue; + if( sqliteStrNICmp(pDb->zName, pDbname->z, pDbname->n)==0 ) break; + } + if( i>=db->nDb ){ + sqliteErrorMsg(pParse, "no such database: %T", pDbname); + return; + } + if( i<2 ){ + sqliteErrorMsg(pParse, "cannot detach database %T", pDbname); + return; + } +#ifndef SQLITE_OMIT_AUTHORIZATION + if( sqliteAuthCheck(pParse,SQLITE_DETACH,db->aDb[i].zName,0,0)!=SQLITE_OK ){ + return; + } +#endif /* SQLITE_OMIT_AUTHORIZATION */ + sqliteBtreeClose(pDb->pBt); + pDb->pBt = 0; + sqliteFree(pDb->zName); + sqliteResetInternalSchema(db, i); + if( pDb->pAux && pDb->xFreeAux ) pDb->xFreeAux(pDb->pAux); + db->nDb--; + if( inDb ){ + db->aDb[i] = db->aDb[db->nDb]; + memset(&db->aDb[db->nDb], 0, sizeof(db->aDb[0])); + sqliteResetInternalSchema(db, i); + } +} + +/* +** Initialize a DbFixer structure. This routine must be called prior +** to passing the structure to one of the sqliteFixAAAA() routines below. +** +** The return value indicates whether or not fixation is required. TRUE +** means we do need to fix the database references, FALSE means we do not. +*/ +int sqliteFixInit( + DbFixer *pFix, /* The fixer to be initialized */ + Parse *pParse, /* Error messages will be written here */ + int iDb, /* This is the database that must must be used */ + const char *zType, /* "view", "trigger", or "index" */ + const Token *pName /* Name of the view, trigger, or index */ +){ + sqlite *db; + + if( iDb<0 || iDb==1 ) return 0; + db = pParse->db; + assert( db->nDb>iDb ); + pFix->pParse = pParse; + pFix->zDb = db->aDb[iDb].zName; + pFix->zType = zType; + pFix->pName = pName; + return 1; +} + +/* +** The following set of routines walk through the parse tree and assign +** a specific database to all table references where the database name +** was left unspecified in the original SQL statement. The pFix structure +** must have been initialized by a prior call to sqliteFixInit(). +** +** These routines are used to make sure that an index, trigger, or +** view in one database does not refer to objects in a different database. +** (Exception: indices, triggers, and views in the TEMP database are +** allowed to refer to anything.) If a reference is explicitly made +** to an object in a different database, an error message is added to +** pParse->zErrMsg and these routines return non-zero. If everything +** checks out, these routines return 0. +*/ +int sqliteFixSrcList( + DbFixer *pFix, /* Context of the fixation */ + SrcList *pList /* The Source list to check and modify */ +){ + int i; + const char *zDb; + + if( pList==0 ) return 0; + zDb = pFix->zDb; + for(i=0; inSrc; i++){ + if( pList->a[i].zDatabase==0 ){ + pList->a[i].zDatabase = sqliteStrDup(zDb); + }else if( sqliteStrICmp(pList->a[i].zDatabase,zDb)!=0 ){ + sqliteErrorMsg(pFix->pParse, + "%s %z cannot reference objects in database %s", + pFix->zType, sqliteStrNDup(pFix->pName->z, pFix->pName->n), + pList->a[i].zDatabase); + return 1; + } + if( sqliteFixSelect(pFix, pList->a[i].pSelect) ) return 1; + if( sqliteFixExpr(pFix, pList->a[i].pOn) ) return 1; + } + return 0; +} +int sqliteFixSelect( + DbFixer *pFix, /* Context of the fixation */ + Select *pSelect /* The SELECT statement to be fixed to one database */ +){ + while( pSelect ){ + if( sqliteFixExprList(pFix, pSelect->pEList) ){ + return 1; + } + if( sqliteFixSrcList(pFix, pSelect->pSrc) ){ + return 1; + } + if( sqliteFixExpr(pFix, pSelect->pWhere) ){ + return 1; + } + if( sqliteFixExpr(pFix, pSelect->pHaving) ){ + return 1; + } + pSelect = pSelect->pPrior; + } + return 0; +} +int sqliteFixExpr( + DbFixer *pFix, /* Context of the fixation */ + Expr *pExpr /* The expression to be fixed to one database */ +){ + while( pExpr ){ + if( sqliteFixSelect(pFix, pExpr->pSelect) ){ + return 1; + } + if( sqliteFixExprList(pFix, pExpr->pList) ){ + return 1; + } + if( sqliteFixExpr(pFix, pExpr->pRight) ){ + return 1; + } + pExpr = pExpr->pLeft; + } + return 0; +} +int sqliteFixExprList( + DbFixer *pFix, /* Context of the fixation */ + ExprList *pList /* The expression to be fixed to one database */ +){ + int i; + if( pList==0 ) return 0; + for(i=0; inExpr; i++){ + if( sqliteFixExpr(pFix, pList->a[i].pExpr) ){ + return 1; + } + } + return 0; +} +int sqliteFixTriggerStep( + DbFixer *pFix, /* Context of the fixation */ + TriggerStep *pStep /* The trigger step be fixed to one database */ +){ + while( pStep ){ + if( sqliteFixSelect(pFix, pStep->pSelect) ){ + return 1; + } + if( sqliteFixExpr(pFix, pStep->pWhere) ){ + return 1; + } + if( sqliteFixExprList(pFix, pStep->pExprList) ){ + return 1; + } + pStep = pStep->pNext; + } + return 0; +} diff --git a/src/libs/sqlite2/auth.c b/src/libs/sqlite2/auth.c new file mode 100644 index 00000000..9147f148 --- /dev/null +++ b/src/libs/sqlite2/auth.c @@ -0,0 +1,219 @@ +/* +** 2003 January 11 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to implement the sqlite_set_authorizer() +** API. This facility is an optional feature of the library. Embedded +** systems that do not need this facility may omit it by recompiling +** the library with -DSQLITE_OMIT_AUTHORIZATION=1 +** +** $Id: auth.c 875429 2008-10-24 12:20:41Z cgilles $ +*/ +#include "sqliteInt.h" + +/* +** All of the code in this file may be omitted by defining a single +** macro. +*/ +#ifndef SQLITE_OMIT_AUTHORIZATION + +/* +** Set or clear the access authorization function. +** +** The access authorization function is be called during the compilation +** phase to verify that the user has read and/or write access permission on +** various fields of the database. The first argument to the auth function +** is a copy of the 3rd argument to this routine. The second argument +** to the auth function is one of these constants: +** +** SQLITE_COPY +** SQLITE_CREATE_INDEX +** SQLITE_CREATE_TABLE +** SQLITE_CREATE_TEMP_INDEX +** SQLITE_CREATE_TEMP_TABLE +** SQLITE_CREATE_TEMP_TRIGGER +** SQLITE_CREATE_TEMP_VIEW +** SQLITE_CREATE_TRIGGER +** SQLITE_CREATE_VIEW +** SQLITE_DELETE +** SQLITE_DROP_INDEX +** SQLITE_DROP_TABLE +** SQLITE_DROP_TEMP_INDEX +** SQLITE_DROP_TEMP_TABLE +** SQLITE_DROP_TEMP_TRIGGER +** SQLITE_DROP_TEMP_VIEW +** SQLITE_DROP_TRIGGER +** SQLITE_DROP_VIEW +** SQLITE_INSERT +** SQLITE_PRAGMA +** SQLITE_READ +** SQLITE_SELECT +** SQLITE_TRANSACTION +** SQLITE_UPDATE +** +** The third and fourth arguments to the auth function are the name of +** the table and the column that are being accessed. The auth function +** should return either SQLITE_OK, SQLITE_DENY, or SQLITE_IGNORE. If +** SQLITE_OK is returned, it means that access is allowed. SQLITE_DENY +** means that the SQL statement will never-run - the sqlite_exec() call +** will return with an error. SQLITE_IGNORE means that the SQL statement +** should run but attempts to read the specified column will return NULL +** and attempts to write the column will be ignored. +** +** Setting the auth function to NULL disables this hook. The default +** setting of the auth function is NULL. +*/ +int sqlite_set_authorizer( + sqlite *db, + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), + void *pArg +){ + db->xAuth = xAuth; + db->pAuthArg = pArg; + return SQLITE_OK; +} + +/* +** Write an error message into pParse->zErrMsg that explains that the +** user-supplied authorization function returned an illegal value. +*/ +static void sqliteAuthBadReturnCode(Parse *pParse, int rc){ + sqliteErrorMsg(pParse, "illegal return value (%d) from the " + "authorization function - should be SQLITE_OK, SQLITE_IGNORE, " + "or SQLITE_DENY", rc); + pParse->rc = SQLITE_MISUSE; +} + +/* +** The pExpr should be a TK_COLUMN expression. The table referred to +** is in pTabList or else it is the NEW or OLD table of a trigger. +** Check to see if it is OK to read this particular column. +** +** If the auth function returns SQLITE_IGNORE, change the TK_COLUMN +** instruction into a TK_NULL. If the auth function returns SQLITE_DENY, +** then generate an error. +*/ +void sqliteAuthRead( + Parse *pParse, /* The parser context */ + Expr *pExpr, /* The expression to check authorization on */ + SrcList *pTabList /* All table that pExpr might refer to */ +){ + sqlite *db = pParse->db; + int rc; + Table *pTab; /* The table being read */ + const char *zCol; /* Name of the column of the table */ + int iSrc; /* Index in pTabList->a[] of table being read */ + const char *zDBase; /* Name of database being accessed */ + TriggerStack *pStack; /* The stack of current triggers */ + + if( db->xAuth==0 ) return; + assert( pExpr->op==TK_COLUMN ); + for(iSrc=0; iSrcnSrc; iSrc++){ + if( pExpr->iTable==pTabList->a[iSrc].iCursor ) break; + } + if( iSrc>=0 && iSrcnSrc ){ + pTab = pTabList->a[iSrc].pTab; + }else if( (pStack = pParse->trigStack)!=0 ){ + /* This must be an attempt to read the NEW or OLD pseudo-tables + ** of a trigger. + */ + assert( pExpr->iTable==pStack->newIdx || pExpr->iTable==pStack->oldIdx ); + pTab = pStack->pTab; + }else{ + return; + } + if( pTab==0 ) return; + if( pExpr->iColumn>=0 ){ + assert( pExpr->iColumnnCol ); + zCol = pTab->aCol[pExpr->iColumn].zName; + }else if( pTab->iPKey>=0 ){ + assert( pTab->iPKeynCol ); + zCol = pTab->aCol[pTab->iPKey].zName; + }else{ + zCol = "ROWID"; + } + assert( pExpr->iDbnDb ); + zDBase = db->aDb[pExpr->iDb].zName; + rc = db->xAuth(db->pAuthArg, SQLITE_READ, pTab->zName, zCol, zDBase, + pParse->zAuthContext); + if( rc==SQLITE_IGNORE ){ + pExpr->op = TK_NULL; + }else if( rc==SQLITE_DENY ){ + if( db->nDb>2 || pExpr->iDb!=0 ){ + sqliteErrorMsg(pParse, "access to %s.%s.%s is prohibited", + zDBase, pTab->zName, zCol); + }else{ + sqliteErrorMsg(pParse, "access to %s.%s is prohibited", pTab->zName,zCol); + } + pParse->rc = SQLITE_AUTH; + }else if( rc!=SQLITE_OK ){ + sqliteAuthBadReturnCode(pParse, rc); + } +} + +/* +** Do an authorization check using the code and arguments given. Return +** either SQLITE_OK (zero) or SQLITE_IGNORE or SQLITE_DENY. If SQLITE_DENY +** is returned, then the error count and error message in pParse are +** modified appropriately. +*/ +int sqliteAuthCheck( + Parse *pParse, + int code, + const char *zArg1, + const char *zArg2, + const char *zArg3 +){ + sqlite *db = pParse->db; + int rc; + + if( db->init.busy || db->xAuth==0 ){ + return SQLITE_OK; + } + rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext); + if( rc==SQLITE_DENY ){ + sqliteErrorMsg(pParse, "not authorized"); + pParse->rc = SQLITE_AUTH; + }else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){ + rc = SQLITE_DENY; + sqliteAuthBadReturnCode(pParse, rc); + } + return rc; +} + +/* +** Push an authorization context. After this routine is called, the +** zArg3 argument to authorization callbacks will be zContext until +** popped. Or if pParse==0, this routine is a no-op. +*/ +void sqliteAuthContextPush( + Parse *pParse, + AuthContext *pContext, + const char *zContext +){ + pContext->pParse = pParse; + if( pParse ){ + pContext->zAuthContext = pParse->zAuthContext; + pParse->zAuthContext = zContext; + } +} + +/* +** Pop an authorization context that was previously pushed +** by sqliteAuthContextPush +*/ +void sqliteAuthContextPop(AuthContext *pContext){ + if( pContext->pParse ){ + pContext->pParse->zAuthContext = pContext->zAuthContext; + pContext->pParse = 0; + } +} + +#endif /* SQLITE_OMIT_AUTHORIZATION */ diff --git a/src/libs/sqlite2/btree.c b/src/libs/sqlite2/btree.c new file mode 100644 index 00000000..745bdda2 --- /dev/null +++ b/src/libs/sqlite2/btree.c @@ -0,0 +1,3584 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** $Id: btree.c 875429 2008-10-24 12:20:41Z cgilles $ +** +** This file implements a external (disk-based) database using BTrees. +** For a detailed discussion of BTrees, refer to +** +** Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3: +** "Sorting And Searching", pages 473-480. Addison-Wesley +** Publishing Company, Reading, Massachusetts. +** +** The basic idea is that each page of the file contains N database +** entries and N+1 pointers to subpages. +** +** ---------------------------------------------------------------- +** | Ptr(0) | Key(0) | Ptr(1) | Key(1) | ... | Key(N) | Ptr(N+1) | +** ---------------------------------------------------------------- +** +** All of the keys on the page that Ptr(0) points to have values less +** than Key(0). All of the keys on page Ptr(1) and its subpages have +** values greater than Key(0) and less than Key(1). All of the keys +** on Ptr(N+1) and its subpages have values greater than Key(N). And +** so forth. +** +** Finding a particular key requires reading O(log(M)) pages from the +** disk where M is the number of entries in the tree. +** +** In this implementation, a single file can hold one or more separate +** BTrees. Each BTree is identified by the index of its root page. The +** key and data for any entry are combined to form the "payload". Up to +** MX_LOCAL_PAYLOAD bytes of payload can be carried directly on the +** database page. If the payload is larger than MX_LOCAL_PAYLOAD bytes +** then surplus bytes are stored on overflow pages. The payload for an +** entry and the preceding pointer are combined to form a "Cell". Each +** page has a small header which contains the Ptr(N+1) pointer. +** +** The first page of the file contains a magic string used to verify that +** the file really is a valid BTree database, a pointer to a list of unused +** pages in the file, and some meta information. The root of the first +** BTree begins on page 2 of the file. (Pages are numbered beginning with +** 1, not 0.) Thus a minimum database contains 2 pages. +*/ +#include "sqliteInt.h" +#include "pager.h" +#include "btree.h" +#include + +/* Forward declarations */ +static BtOps sqliteBtreeOps; +static BtCursorOps sqliteBtreeCursorOps; + +/* +** Macros used for byteswapping. B is a pointer to the Btree +** structure. This is needed to access the Btree.needSwab boolean +** in order to tell if byte swapping is needed or not. +** X is an unsigned integer. SWAB16 byte swaps a 16-bit integer. +** SWAB32 byteswaps a 32-bit integer. +*/ +#define SWAB16(B,X) ((B)->needSwab? swab16((u16)X) : ((u16)X)) +#define SWAB32(B,X) ((B)->needSwab? swab32(X) : (X)) +#define SWAB_ADD(B,X,A) \ + if((B)->needSwab){ X=swab32(swab32(X)+A); }else{ X += (A); } + +/* +** The following global variable - available only if SQLITE_TEST is +** defined - is used to determine whether new databases are created in +** native byte order or in non-native byte order. Non-native byte order +** databases are created for testing purposes only. Under normal operation, +** only native byte-order databases should be created, but we should be +** able to read or write existing databases regardless of the byteorder. +*/ +#ifdef SQLITE_TEST +int btree_native_byte_order = 1; +#else +# define btree_native_byte_order 1 +#endif + +/* +** Forward declarations of structures used only in this file. +*/ +typedef struct PageOne PageOne; +typedef struct MemPage MemPage; +typedef struct PageHdr PageHdr; +typedef struct Cell Cell; +typedef struct CellHdr CellHdr; +typedef struct FreeBlk FreeBlk; +typedef struct OverflowPage OverflowPage; +typedef struct FreelistInfo FreelistInfo; + +/* +** All structures on a database page are aligned to 4-byte boundries. +** This routine rounds up a number of bytes to the next multiple of 4. +** +** This might need to change for computer architectures that require +** and 8-byte alignment boundry for structures. +*/ +#define ROUNDUP(X) ((X+3) & ~3) + +/* +** This is a magic string that appears at the beginning of every +** SQLite database in order to identify the file as a real database. +*/ +static const char zMagicHeader[] = + "** This file contains an SQLite 2.1 database **"; +#define MAGIC_SIZE (sizeof(zMagicHeader)) + +/* +** This is a magic integer also used to test the integrity of the database +** file. This integer is used in addition to the string above so that +** if the file is written on a little-endian architecture and read +** on a big-endian architectures (or vice versa) we can detect the +** problem. +** +** The number used was obtained at random and has no special +** significance other than the fact that it represents a different +** integer on little-endian and big-endian machines. +*/ +#define MAGIC 0xdae37528 + +/* +** The first page of the database file contains a magic header string +** to identify the file as an SQLite database file. It also contains +** a pointer to the first free page of the file. Page 2 contains the +** root of the principle BTree. The file might contain other BTrees +** rooted on pages above 2. +** +** The first page also contains SQLITE_N_BTREE_META integers that +** can be used by higher-level routines. +** +** Remember that pages are numbered beginning with 1. (See pager.c +** for additional information.) Page 0 does not exist and a page +** number of 0 is used to mean "no such page". +*/ +struct PageOne { + char zMagic[MAGIC_SIZE]; /* String that identifies the file as a database */ + int iMagic; /* Integer to verify correct byte order */ + Pgno freeList; /* First free page in a list of all free pages */ + int nFree; /* Number of pages on the free list */ + int aMeta[SQLITE_N_BTREE_META-1]; /* User defined integers */ +}; + +/* +** Each database page has a header that is an instance of this +** structure. +** +** PageHdr.firstFree is 0 if there is no free space on this page. +** Otherwise, PageHdr.firstFree is the index in MemPage.u.aDisk[] of a +** FreeBlk structure that describes the first block of free space. +** All free space is defined by a linked list of FreeBlk structures. +** +** Data is stored in a linked list of Cell structures. PageHdr.firstCell +** is the index into MemPage.u.aDisk[] of the first cell on the page. The +** Cells are kept in sorted order. +** +** A Cell contains all information about a database entry and a pointer +** to a child page that contains other entries less than itself. In +** other words, the i-th Cell contains both Ptr(i) and Key(i). The +** right-most pointer of the page is contained in PageHdr.rightChild. +*/ +struct PageHdr { + Pgno rightChild; /* Child page that comes after all cells on this page */ + u16 firstCell; /* Index in MemPage.u.aDisk[] of the first cell */ + u16 firstFree; /* Index in MemPage.u.aDisk[] of the first free block */ +}; + +/* +** Entries on a page of the database are called "Cells". Each Cell +** has a header and data. This structure defines the header. The +** key and data (collectively the "payload") follow this header on +** the database page. +** +** A definition of the complete Cell structure is given below. The +** header for the cell must be defined first in order to do some +** of the sizing #defines that follow. +*/ +struct CellHdr { + Pgno leftChild; /* Child page that comes before this cell */ + u16 nKey; /* Number of bytes in the key */ + u16 iNext; /* Index in MemPage.u.aDisk[] of next cell in sorted order */ + u8 nKeyHi; /* Upper 8 bits of key size for keys larger than 64K bytes */ + u8 nDataHi; /* Upper 8 bits of data size when the size is more than 64K */ + u16 nData; /* Number of bytes of data */ +}; + +/* +** The key and data size are split into a lower 16-bit segment and an +** upper 8-bit segment in order to pack them together into a smaller +** space. The following macros reassembly a key or data size back +** into an integer. +*/ +#define NKEY(b,h) (SWAB16(b,h.nKey) + h.nKeyHi*65536) +#define NDATA(b,h) (SWAB16(b,h.nData) + h.nDataHi*65536) + +/* +** The minimum size of a complete Cell. The Cell must contain a header +** and at least 4 bytes of payload. +*/ +#define MIN_CELL_SIZE (sizeof(CellHdr)+4) + +/* +** The maximum number of database entries that can be held in a single +** page of the database. +*/ +#define MX_CELL ((SQLITE_USABLE_SIZE-sizeof(PageHdr))/MIN_CELL_SIZE) + +/* +** The amount of usable space on a single page of the BTree. This is the +** page size minus the overhead of the page header. +*/ +#define USABLE_SPACE (SQLITE_USABLE_SIZE - sizeof(PageHdr)) + +/* +** The maximum amount of payload (in bytes) that can be stored locally for +** a database entry. If the entry contains more data than this, the +** extra goes onto overflow pages. +** +** This number is chosen so that at least 4 cells will fit on every page. +*/ +#define MX_LOCAL_PAYLOAD ((USABLE_SPACE/4-(sizeof(CellHdr)+sizeof(Pgno)))&~3) + +/* +** Data on a database page is stored as a linked list of Cell structures. +** Both the key and the data are stored in aPayload[]. The key always comes +** first. The aPayload[] field grows as necessary to hold the key and data, +** up to a maximum of MX_LOCAL_PAYLOAD bytes. If the size of the key and +** data combined exceeds MX_LOCAL_PAYLOAD bytes, then Cell.ovfl is the +** page number of the first overflow page. +** +** Though this structure is fixed in size, the Cell on the database +** page varies in size. Every cell has a CellHdr and at least 4 bytes +** of payload space. Additional payload bytes (up to the maximum of +** MX_LOCAL_PAYLOAD) and the Cell.ovfl value are allocated only as +** needed. +*/ +struct Cell { + CellHdr h; /* The cell header */ + char aPayload[MX_LOCAL_PAYLOAD]; /* Key and data */ + Pgno ovfl; /* The first overflow page */ +}; + +/* +** Free space on a page is remembered using a linked list of the FreeBlk +** structures. Space on a database page is allocated in increments of +** at least 4 bytes and is always aligned to a 4-byte boundry. The +** linked list of FreeBlks is always kept in order by address. +*/ +struct FreeBlk { + u16 iSize; /* Number of bytes in this block of free space */ + u16 iNext; /* Index in MemPage.u.aDisk[] of the next free block */ +}; + +/* +** The number of bytes of payload that will fit on a single overflow page. +*/ +#define OVERFLOW_SIZE (SQLITE_USABLE_SIZE-sizeof(Pgno)) + +/* +** When the key and data for a single entry in the BTree will not fit in +** the MX_LOCAL_PAYLOAD bytes of space available on the database page, +** then all extra bytes are written to a linked list of overflow pages. +** Each overflow page is an instance of the following structure. +** +** Unused pages in the database are also represented by instances of +** the OverflowPage structure. The PageOne.freeList field is the +** page number of the first page in a linked list of unused database +** pages. +*/ +struct OverflowPage { + Pgno iNext; + char aPayload[OVERFLOW_SIZE]; +}; + +/* +** The PageOne.freeList field points to a linked list of overflow pages +** hold information about free pages. The aPayload section of each +** overflow page contains an instance of the following structure. The +** aFree[] array holds the page number of nFree unused pages in the disk +** file. +*/ +struct FreelistInfo { + int nFree; + Pgno aFree[(OVERFLOW_SIZE-sizeof(int))/sizeof(Pgno)]; +}; + +/* +** For every page in the database file, an instance of the following structure +** is stored in memory. The u.aDisk[] array contains the raw bits read from +** the disk. The rest is auxiliary information held in memory only. The +** auxiliary info is only valid for regular database pages - it is not +** used for overflow pages and pages on the freelist. +** +** Of particular interest in the auxiliary info is the apCell[] entry. Each +** apCell[] entry is a pointer to a Cell structure in u.aDisk[]. The cells are +** put in this array so that they can be accessed in constant time, rather +** than in linear time which would be needed if we had to walk the linked +** list on every access. +** +** Note that apCell[] contains enough space to hold up to two more Cells +** than can possibly fit on one page. In the steady state, every apCell[] +** points to memory inside u.aDisk[]. But in the middle of an insert +** operation, some apCell[] entries may temporarily point to data space +** outside of u.aDisk[]. This is a transient situation that is quickly +** resolved. But while it is happening, it is possible for a database +** page to hold as many as two more cells than it might otherwise hold. +** The extra two entries in apCell[] are an allowance for this situation. +** +** The pParent field points back to the parent page. This allows us to +** walk up the BTree from any leaf to the root. Care must be taken to +** unref() the parent page pointer when this page is no longer referenced. +** The pageDestructor() routine handles that chore. +*/ +struct MemPage { + union u_page_data { + char aDisk[SQLITE_PAGE_SIZE]; /* Page data stored on disk */ + PageHdr hdr; /* Overlay page header */ + } u; + u8 isInit; /* True if auxiliary data is initialized */ + u8 idxShift; /* True if apCell[] indices have changed */ + u8 isOverfull; /* Some apCell[] points outside u.aDisk[] */ + MemPage *pParent; /* The parent of this page. NULL for root */ + int idxParent; /* Index in pParent->apCell[] of this node */ + int nFree; /* Number of free bytes in u.aDisk[] */ + int nCell; /* Number of entries on this page */ + Cell *apCell[MX_CELL+2]; /* All data entires in sorted order */ +}; + +/* +** The in-memory image of a disk page has the auxiliary information appended +** to the end. EXTRA_SIZE is the number of bytes of space needed to hold +** that extra information. +*/ +#define EXTRA_SIZE (sizeof(MemPage)-sizeof(union u_page_data)) + +/* +** Everything we need to know about an open database +*/ +struct Btree { + BtOps *pOps; /* Function table */ + Pager *pPager; /* The page cache */ + BtCursor *pCursor; /* A list of all open cursors */ + PageOne *page1; /* First page of the database */ + u8 inTrans; /* True if a transaction is in progress */ + u8 inCkpt; /* True if there is a checkpoint on the transaction */ + u8 readOnly; /* True if the underlying file is readonly */ + u8 needSwab; /* Need to byte-swapping */ +}; +typedef Btree Bt; + +/* +** A cursor is a pointer to a particular entry in the BTree. +** The entry is identified by its MemPage and the index in +** MemPage.apCell[] of the entry. +*/ +struct BtCursor { + BtCursorOps *pOps; /* Function table */ + Btree *pBt; /* The Btree to which this cursor belongs */ + BtCursor *pNext, *pPrev; /* Forms a linked list of all cursors */ + BtCursor *pShared; /* Loop of cursors with the same root page */ + Pgno pgnoRoot; /* The root page of this tree */ + MemPage *pPage; /* Page that contains the entry */ + int idx; /* Index of the entry in pPage->apCell[] */ + u8 wrFlag; /* True if writable */ + u8 eSkip; /* Determines if next step operation is a no-op */ + u8 iMatch; /* compare result from last sqliteBtreeMoveto() */ +}; + +/* +** Legal values for BtCursor.eSkip. +*/ +#define SKIP_NONE 0 /* Always step the cursor */ +#define SKIP_NEXT 1 /* The next sqliteBtreeNext() is a no-op */ +#define SKIP_PREV 2 /* The next sqliteBtreePrevious() is a no-op */ +#define SKIP_INVALID 3 /* Calls to Next() and Previous() are invalid */ + +/* Forward declarations */ +static int fileBtreeCloseCursor(BtCursor *pCur); + +/* +** Routines for byte swapping. +*/ +u16 swab16(u16 x){ + return ((x & 0xff)<<8) | ((x>>8)&0xff); +} +u32 swab32(u32 x){ + return ((x & 0xff)<<24) | ((x & 0xff00)<<8) | + ((x>>8) & 0xff00) | ((x>>24)&0xff); +} + +/* +** Compute the total number of bytes that a Cell needs on the main +** database page. The number returned includes the Cell header, +** local payload storage, and the pointer to overflow pages (if +** applicable). Additional space allocated on overflow pages +** is NOT included in the value returned from this routine. +*/ +static int cellSize(Btree *pBt, Cell *pCell){ + int n = NKEY(pBt, pCell->h) + NDATA(pBt, pCell->h); + if( n>MX_LOCAL_PAYLOAD ){ + n = MX_LOCAL_PAYLOAD + sizeof(Pgno); + }else{ + n = ROUNDUP(n); + } + n += sizeof(CellHdr); + return n; +} + +/* +** Defragment the page given. All Cells are moved to the +** beginning of the page and all free space is collected +** into one big FreeBlk at the end of the page. +*/ +static void defragmentPage(Btree *pBt, MemPage *pPage){ + int pc, i, n; + FreeBlk *pFBlk; + char newPage[SQLITE_USABLE_SIZE]; + + assert( sqlitepager_iswriteable(pPage) ); + assert( pPage->isInit ); + pc = sizeof(PageHdr); + pPage->u.hdr.firstCell = SWAB16(pBt, pc); + memcpy(newPage, pPage->u.aDisk, pc); + for(i=0; inCell; i++){ + Cell *pCell = pPage->apCell[i]; + + /* This routine should never be called on an overfull page. The + ** following asserts verify that constraint. */ + assert( Addr(pCell) > Addr(pPage) ); + assert( Addr(pCell) < Addr(pPage) + SQLITE_USABLE_SIZE ); + + n = cellSize(pBt, pCell); + pCell->h.iNext = SWAB16(pBt, pc + n); + memcpy(&newPage[pc], pCell, n); + pPage->apCell[i] = (Cell*)&pPage->u.aDisk[pc]; + pc += n; + } + assert( pPage->nFree==SQLITE_USABLE_SIZE-pc ); + memcpy(pPage->u.aDisk, newPage, pc); + if( pPage->nCell>0 ){ + pPage->apCell[pPage->nCell-1]->h.iNext = 0; + } + pFBlk = (FreeBlk*)&pPage->u.aDisk[pc]; + pFBlk->iSize = SWAB16(pBt, SQLITE_USABLE_SIZE - pc); + pFBlk->iNext = 0; + pPage->u.hdr.firstFree = SWAB16(pBt, pc); + memset(&pFBlk[1], 0, SQLITE_USABLE_SIZE - pc - sizeof(FreeBlk)); +} + +/* +** Allocate nByte bytes of space on a page. nByte must be a +** multiple of 4. +** +** Return the index into pPage->u.aDisk[] of the first byte of +** the new allocation. Or return 0 if there is not enough free +** space on the page to satisfy the allocation request. +** +** If the page contains nBytes of free space but does not contain +** nBytes of contiguous free space, then this routine automatically +** calls defragementPage() to consolidate all free space before +** allocating the new chunk. +*/ +static int allocateSpace(Btree *pBt, MemPage *pPage, int nByte){ + FreeBlk *p; + u16 *pIdx; + int start; + int iSize; +#ifndef NDEBUG + int cnt = 0; +#endif + + assert( sqlitepager_iswriteable(pPage) ); + assert( nByte==ROUNDUP(nByte) ); + assert( pPage->isInit ); + if( pPage->nFreeisOverfull ) return 0; + pIdx = &pPage->u.hdr.firstFree; + p = (FreeBlk*)&pPage->u.aDisk[SWAB16(pBt, *pIdx)]; + while( (iSize = SWAB16(pBt, p->iSize))iNext==0 ){ + defragmentPage(pBt, pPage); + pIdx = &pPage->u.hdr.firstFree; + }else{ + pIdx = &p->iNext; + } + p = (FreeBlk*)&pPage->u.aDisk[SWAB16(pBt, *pIdx)]; + } + if( iSize==nByte ){ + start = SWAB16(pBt, *pIdx); + *pIdx = p->iNext; + }else{ + FreeBlk *pNew; + start = SWAB16(pBt, *pIdx); + pNew = (FreeBlk*)&pPage->u.aDisk[start + nByte]; + pNew->iNext = p->iNext; + pNew->iSize = SWAB16(pBt, iSize - nByte); + *pIdx = SWAB16(pBt, start + nByte); + } + pPage->nFree -= nByte; + return start; +} + +/* +** Return a section of the MemPage.u.aDisk[] to the freelist. +** The first byte of the new free block is pPage->u.aDisk[start] +** and the size of the block is "size" bytes. Size must be +** a multiple of 4. +** +** Most of the effort here is involved in coalesing adjacent +** free blocks into a single big free block. +*/ +static void freeSpace(Btree *pBt, MemPage *pPage, int start, int size){ + int end = start + size; + u16 *pIdx, idx; + FreeBlk *pFBlk; + FreeBlk *pNew; + FreeBlk *pNext; + int iSize; + + assert( sqlitepager_iswriteable(pPage) ); + assert( size == ROUNDUP(size) ); + assert( start == ROUNDUP(start) ); + assert( pPage->isInit ); + pIdx = &pPage->u.hdr.firstFree; + idx = SWAB16(pBt, *pIdx); + while( idx!=0 && idxu.aDisk[idx]; + iSize = SWAB16(pBt, pFBlk->iSize); + if( idx + iSize == start ){ + pFBlk->iSize = SWAB16(pBt, iSize + size); + if( idx + iSize + size == SWAB16(pBt, pFBlk->iNext) ){ + pNext = (FreeBlk*)&pPage->u.aDisk[idx + iSize + size]; + if( pBt->needSwab ){ + pFBlk->iSize = swab16((u16)swab16(pNext->iSize)+iSize+size); + }else{ + pFBlk->iSize += pNext->iSize; + } + pFBlk->iNext = pNext->iNext; + } + pPage->nFree += size; + return; + } + pIdx = &pFBlk->iNext; + idx = SWAB16(pBt, *pIdx); + } + pNew = (FreeBlk*)&pPage->u.aDisk[start]; + if( idx != end ){ + pNew->iSize = SWAB16(pBt, size); + pNew->iNext = SWAB16(pBt, idx); + }else{ + pNext = (FreeBlk*)&pPage->u.aDisk[idx]; + pNew->iSize = SWAB16(pBt, size + SWAB16(pBt, pNext->iSize)); + pNew->iNext = pNext->iNext; + } + *pIdx = SWAB16(pBt, start); + pPage->nFree += size; +} + +/* +** Initialize the auxiliary information for a disk block. +** +** The pParent parameter must be a pointer to the MemPage which +** is the parent of the page being initialized. The root of the +** BTree (usually page 2) has no parent and so for that page, +** pParent==NULL. +** +** Return SQLITE_OK on success. If we see that the page does +** not contain a well-formed database page, then return +** SQLITE_CORRUPT. Note that a return of SQLITE_OK does not +** guarantee that the page is well-formed. It only shows that +** we failed to detect any corruption. +*/ +static int initPage(Bt *pBt, MemPage *pPage, Pgno pgnoThis, MemPage *pParent){ + int idx; /* An index into pPage->u.aDisk[] */ + Cell *pCell; /* A pointer to a Cell in pPage->u.aDisk[] */ + FreeBlk *pFBlk; /* A pointer to a free block in pPage->u.aDisk[] */ + int sz; /* The size of a Cell in bytes */ + int freeSpace; /* Amount of free space on the page */ + + if( pPage->pParent ){ + assert( pPage->pParent==pParent ); + return SQLITE_OK; + } + if( pParent ){ + pPage->pParent = pParent; + sqlitepager_ref(pParent); + } + if( pPage->isInit ) return SQLITE_OK; + pPage->isInit = 1; + pPage->nCell = 0; + freeSpace = USABLE_SPACE; + idx = SWAB16(pBt, pPage->u.hdr.firstCell); + while( idx!=0 ){ + if( idx>SQLITE_USABLE_SIZE-MIN_CELL_SIZE ) goto page_format_error; + if( idxu.aDisk[idx]; + sz = cellSize(pBt, pCell); + if( idx+sz > SQLITE_USABLE_SIZE ) goto page_format_error; + freeSpace -= sz; + pPage->apCell[pPage->nCell++] = pCell; + idx = SWAB16(pBt, pCell->h.iNext); + } + pPage->nFree = 0; + idx = SWAB16(pBt, pPage->u.hdr.firstFree); + while( idx!=0 ){ + int iNext; + if( idx>SQLITE_USABLE_SIZE-sizeof(FreeBlk) ) goto page_format_error; + if( idxu.aDisk[idx]; + pPage->nFree += SWAB16(pBt, pFBlk->iSize); + iNext = SWAB16(pBt, pFBlk->iNext); + if( iNext>0 && iNext <= idx ) goto page_format_error; + idx = iNext; + } + if( pPage->nCell==0 && pPage->nFree==0 ){ + /* As a special case, an uninitialized root page appears to be + ** an empty database */ + return SQLITE_OK; + } + if( pPage->nFree!=freeSpace ) goto page_format_error; + return SQLITE_OK; + +page_format_error: + return SQLITE_CORRUPT; +} + +/* +** Set up a raw page so that it looks like a database page holding +** no entries. +*/ +static void zeroPage(Btree *pBt, MemPage *pPage){ + PageHdr *pHdr; + FreeBlk *pFBlk; + assert( sqlitepager_iswriteable(pPage) ); + memset(pPage, 0, SQLITE_USABLE_SIZE); + pHdr = &pPage->u.hdr; + pHdr->firstCell = 0; + pHdr->firstFree = SWAB16(pBt, sizeof(*pHdr)); + pFBlk = (FreeBlk*)&pHdr[1]; + pFBlk->iNext = 0; + pPage->nFree = SQLITE_USABLE_SIZE - sizeof(*pHdr); + pFBlk->iSize = SWAB16(pBt, pPage->nFree); + pPage->nCell = 0; + pPage->isOverfull = 0; +} + +/* +** This routine is called when the reference count for a page +** reaches zero. We need to unref the pParent pointer when that +** happens. +*/ +static void pageDestructor(void *pData){ + MemPage *pPage = (MemPage*)pData; + if( pPage->pParent ){ + MemPage *pParent = pPage->pParent; + pPage->pParent = 0; + sqlitepager_unref(pParent); + } +} + +/* +** Open a new database. +** +** Actually, this routine just sets up the internal data structures +** for accessing the database. We do not open the database file +** until the first page is loaded. +** +** zFilename is the name of the database file. If zFilename is NULL +** a new database with a random name is created. This randomly named +** database file will be deleted when sqliteBtreeClose() is called. +*/ +int sqliteBtreeOpen( + const char *zFilename, /* Name of the file containing the BTree database */ + int omitJournal, /* if TRUE then do not journal this file */ + int nCache, /* How many pages in the page cache */ + Btree **ppBtree /* Pointer to new Btree object written here */ +){ + Btree *pBt; + int rc; + + /* + ** The following asserts make sure that structures used by the btree are + ** the right size. This is to guard against size changes that result + ** when compiling on a different architecture. + */ + assert( sizeof(u32)==4 ); + assert( sizeof(u16)==2 ); + assert( sizeof(Pgno)==4 ); + assert( sizeof(PageHdr)==8 ); + assert( sizeof(CellHdr)==12 ); + assert( sizeof(FreeBlk)==4 ); + assert( sizeof(OverflowPage)==SQLITE_USABLE_SIZE ); + assert( sizeof(FreelistInfo)==OVERFLOW_SIZE ); + assert( sizeof(ptr)==sizeof(char*) ); + assert( sizeof(uptr)==sizeof(ptr) ); + + pBt = sqliteMalloc( sizeof(*pBt) ); + if( pBt==0 ){ + *ppBtree = 0; + return SQLITE_NOMEM; + } + if( nCache<10 ) nCache = 10; + rc = sqlitepager_open(&pBt->pPager, zFilename, nCache, EXTRA_SIZE, + !omitJournal); + if( rc!=SQLITE_OK ){ + if( pBt->pPager ) sqlitepager_close(pBt->pPager); + sqliteFree(pBt); + *ppBtree = 0; + return rc; + } + sqlitepager_set_destructor(pBt->pPager, pageDestructor); + pBt->pCursor = 0; + pBt->page1 = 0; + pBt->readOnly = sqlitepager_isreadonly(pBt->pPager); + pBt->pOps = &sqliteBtreeOps; + *ppBtree = pBt; + return SQLITE_OK; +} + +/* +** Close an open database and invalidate all cursors. +*/ +static int fileBtreeClose(Btree *pBt){ + while( pBt->pCursor ){ + fileBtreeCloseCursor(pBt->pCursor); + } + sqlitepager_close(pBt->pPager); + sqliteFree(pBt); + return SQLITE_OK; +} + +/* +** Change the limit on the number of pages allowed in the cache. +** +** The maximum number of cache pages is set to the absolute +** value of mxPage. If mxPage is negative, the pager will +** operate asynchronously - it will not stop to do fsync()s +** to insure data is written to the disk surface before +** continuing. Transactions still work if synchronous is off, +** and the database cannot be corrupted if this program +** crashes. But if the operating system crashes or there is +** an abrupt power failure when synchronous is off, the database +** could be left in an inconsistent and unrecoverable state. +** Synchronous is on by default so database corruption is not +** normally a worry. +*/ +static int fileBtreeSetCacheSize(Btree *pBt, int mxPage){ + sqlitepager_set_cachesize(pBt->pPager, mxPage); + return SQLITE_OK; +} + +/* +** Change the way data is synced to disk in order to increase or decrease +** how well the database resists damage due to OS crashes and power +** failures. Level 1 is the same as asynchronous (no syncs() occur and +** there is a high probability of damage) Level 2 is the default. There +** is a very low but non-zero probability of damage. Level 3 reduces the +** probability of damage to near zero but with a write performance reduction. +*/ +static int fileBtreeSetSafetyLevel(Btree *pBt, int level){ + sqlitepager_set_safety_level(pBt->pPager, level); + return SQLITE_OK; +} + +/* +** Get a reference to page1 of the database file. This will +** also acquire a readlock on that file. +** +** SQLITE_OK is returned on success. If the file is not a +** well-formed database file, then SQLITE_CORRUPT is returned. +** SQLITE_BUSY is returned if the database is locked. SQLITE_NOMEM +** is returned if we run out of memory. SQLITE_PROTOCOL is returned +** if there is a locking protocol violation. +*/ +static int lockBtree(Btree *pBt){ + int rc; + if( pBt->page1 ) return SQLITE_OK; + rc = sqlitepager_get(pBt->pPager, 1, (void**)&pBt->page1); + if( rc!=SQLITE_OK ) return rc; + + /* Do some checking to help insure the file we opened really is + ** a valid database file. + */ + if( sqlitepager_pagecount(pBt->pPager)>0 ){ + PageOne *pP1 = pBt->page1; + if( strcmp(pP1->zMagic,zMagicHeader)!=0 || + (pP1->iMagic!=MAGIC && swab32(pP1->iMagic)!=MAGIC) ){ + rc = SQLITE_NOTADB; + goto page1_init_failed; + } + pBt->needSwab = pP1->iMagic!=MAGIC; + } + return rc; + +page1_init_failed: + sqlitepager_unref(pBt->page1); + pBt->page1 = 0; + return rc; +} + +/* +** If there are no outstanding cursors and we are not in the middle +** of a transaction but there is a read lock on the database, then +** this routine unrefs the first page of the database file which +** has the effect of releasing the read lock. +** +** If there are any outstanding cursors, this routine is a no-op. +** +** If there is a transaction in progress, this routine is a no-op. +*/ +static void unlockBtreeIfUnused(Btree *pBt){ + if( pBt->inTrans==0 && pBt->pCursor==0 && pBt->page1!=0 ){ + sqlitepager_unref(pBt->page1); + pBt->page1 = 0; + pBt->inTrans = 0; + pBt->inCkpt = 0; + } +} + +/* +** Create a new database by initializing the first two pages of the +** file. +*/ +static int newDatabase(Btree *pBt){ + MemPage *pRoot; + PageOne *pP1; + int rc; + if( sqlitepager_pagecount(pBt->pPager)>1 ) return SQLITE_OK; + pP1 = pBt->page1; + rc = sqlitepager_write(pBt->page1); + if( rc ) return rc; + rc = sqlitepager_get(pBt->pPager, 2, (void**)&pRoot); + if( rc ) return rc; + rc = sqlitepager_write(pRoot); + if( rc ){ + sqlitepager_unref(pRoot); + return rc; + } + strcpy(pP1->zMagic, zMagicHeader); + if( btree_native_byte_order ){ + pP1->iMagic = MAGIC; + pBt->needSwab = 0; + }else{ + pP1->iMagic = swab32(MAGIC); + pBt->needSwab = 1; + } + zeroPage(pBt, pRoot); + sqlitepager_unref(pRoot); + return SQLITE_OK; +} + +/* +** Attempt to start a new transaction. +** +** A transaction must be started before attempting any changes +** to the database. None of the following routines will work +** unless a transaction is started first: +** +** sqliteBtreeCreateTable() +** sqliteBtreeCreateIndex() +** sqliteBtreeClearTable() +** sqliteBtreeDropTable() +** sqliteBtreeInsert() +** sqliteBtreeDelete() +** sqliteBtreeUpdateMeta() +*/ +static int fileBtreeBeginTrans(Btree *pBt){ + int rc; + if( pBt->inTrans ) return SQLITE_ERROR; + if( pBt->readOnly ) return SQLITE_READONLY; + if( pBt->page1==0 ){ + rc = lockBtree(pBt); + if( rc!=SQLITE_OK ){ + return rc; + } + } + rc = sqlitepager_begin(pBt->page1); + if( rc==SQLITE_OK ){ + rc = newDatabase(pBt); + } + if( rc==SQLITE_OK ){ + pBt->inTrans = 1; + pBt->inCkpt = 0; + }else{ + unlockBtreeIfUnused(pBt); + } + return rc; +} + +/* +** Commit the transaction currently in progress. +** +** This will release the write lock on the database file. If there +** are no active cursors, it also releases the read lock. +*/ +static int fileBtreeCommit(Btree *pBt){ + int rc; + rc = pBt->readOnly ? SQLITE_OK : sqlitepager_commit(pBt->pPager); + pBt->inTrans = 0; + pBt->inCkpt = 0; + unlockBtreeIfUnused(pBt); + return rc; +} + +/* +** Rollback the transaction in progress. All cursors will be +** invalided by this operation. Any attempt to use a cursor +** that was open at the beginning of this operation will result +** in an error. +** +** This will release the write lock on the database file. If there +** are no active cursors, it also releases the read lock. +*/ +static int fileBtreeRollback(Btree *pBt){ + int rc; + BtCursor *pCur; + if( pBt->inTrans==0 ) return SQLITE_OK; + pBt->inTrans = 0; + pBt->inCkpt = 0; + rc = pBt->readOnly ? SQLITE_OK : sqlitepager_rollback(pBt->pPager); + for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ + if( pCur->pPage && pCur->pPage->isInit==0 ){ + sqlitepager_unref(pCur->pPage); + pCur->pPage = 0; + } + } + unlockBtreeIfUnused(pBt); + return rc; +} + +/* +** Set the checkpoint for the current transaction. The checkpoint serves +** as a sub-transaction that can be rolled back independently of the +** main transaction. You must start a transaction before starting a +** checkpoint. The checkpoint is ended automatically if the transaction +** commits or rolls back. +** +** Only one checkpoint may be active at a time. It is an error to try +** to start a new checkpoint if another checkpoint is already active. +*/ +static int fileBtreeBeginCkpt(Btree *pBt){ + int rc; + if( !pBt->inTrans || pBt->inCkpt ){ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + rc = pBt->readOnly ? SQLITE_OK : sqlitepager_ckpt_begin(pBt->pPager); + pBt->inCkpt = 1; + return rc; +} + + +/* +** Commit a checkpoint to transaction currently in progress. If no +** checkpoint is active, this is a no-op. +*/ +static int fileBtreeCommitCkpt(Btree *pBt){ + int rc; + if( pBt->inCkpt && !pBt->readOnly ){ + rc = sqlitepager_ckpt_commit(pBt->pPager); + }else{ + rc = SQLITE_OK; + } + pBt->inCkpt = 0; + return rc; +} + +/* +** Rollback the checkpoint to the current transaction. If there +** is no active checkpoint or transaction, this routine is a no-op. +** +** All cursors will be invalided by this operation. Any attempt +** to use a cursor that was open at the beginning of this operation +** will result in an error. +*/ +static int fileBtreeRollbackCkpt(Btree *pBt){ + int rc; + BtCursor *pCur; + if( pBt->inCkpt==0 || pBt->readOnly ) return SQLITE_OK; + rc = sqlitepager_ckpt_rollback(pBt->pPager); + for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ + if( pCur->pPage && pCur->pPage->isInit==0 ){ + sqlitepager_unref(pCur->pPage); + pCur->pPage = 0; + } + } + pBt->inCkpt = 0; + return rc; +} + +/* +** Create a new cursor for the BTree whose root is on the page +** iTable. The act of acquiring a cursor gets a read lock on +** the database file. +** +** If wrFlag==0, then the cursor can only be used for reading. +** If wrFlag==1, then the cursor can be used for reading or for +** writing if other conditions for writing are also met. These +** are the conditions that must be met in order for writing to +** be allowed: +** +** 1: The cursor must have been opened with wrFlag==1 +** +** 2: No other cursors may be open with wrFlag==0 on the same table +** +** 3: The database must be writable (not on read-only media) +** +** 4: There must be an active transaction. +** +** Condition 2 warrants further discussion. If any cursor is opened +** on a table with wrFlag==0, that prevents all other cursors from +** writing to that table. This is a kind of "read-lock". When a cursor +** is opened with wrFlag==0 it is guaranteed that the table will not +** change as long as the cursor is open. This allows the cursor to +** do a sequential scan of the table without having to worry about +** entries being inserted or deleted during the scan. Cursors should +** be opened with wrFlag==0 only if this read-lock property is needed. +** That is to say, cursors should be opened with wrFlag==0 only if they +** intend to use the sqliteBtreeNext() system call. All other cursors +** should be opened with wrFlag==1 even if they never really intend +** to write. +** +** No checking is done to make sure that page iTable really is the +** root page of a b-tree. If it is not, then the cursor acquired +** will not work correctly. +*/ +static +int fileBtreeCursor(Btree *pBt, int iTable, int wrFlag, BtCursor **ppCur){ + int rc; + BtCursor *pCur, *pRing; + + if( pBt->readOnly && wrFlag ){ + *ppCur = 0; + return SQLITE_READONLY; + } + if( pBt->page1==0 ){ + rc = lockBtree(pBt); + if( rc!=SQLITE_OK ){ + *ppCur = 0; + return rc; + } + } + pCur = sqliteMalloc( sizeof(*pCur) ); + if( pCur==0 ){ + rc = SQLITE_NOMEM; + goto create_cursor_exception; + } + pCur->pgnoRoot = (Pgno)iTable; + rc = sqlitepager_get(pBt->pPager, pCur->pgnoRoot, (void**)&pCur->pPage); + if( rc!=SQLITE_OK ){ + goto create_cursor_exception; + } + rc = initPage(pBt, pCur->pPage, pCur->pgnoRoot, 0); + if( rc!=SQLITE_OK ){ + goto create_cursor_exception; + } + pCur->pOps = &sqliteBtreeCursorOps; + pCur->pBt = pBt; + pCur->wrFlag = wrFlag; + pCur->idx = 0; + pCur->eSkip = SKIP_INVALID; + pCur->pNext = pBt->pCursor; + if( pCur->pNext ){ + pCur->pNext->pPrev = pCur; + } + pCur->pPrev = 0; + pRing = pBt->pCursor; + while( pRing && pRing->pgnoRoot!=pCur->pgnoRoot ){ pRing = pRing->pNext; } + if( pRing ){ + pCur->pShared = pRing->pShared; + pRing->pShared = pCur; + }else{ + pCur->pShared = pCur; + } + pBt->pCursor = pCur; + *ppCur = pCur; + return SQLITE_OK; + +create_cursor_exception: + *ppCur = 0; + if( pCur ){ + if( pCur->pPage ) sqlitepager_unref(pCur->pPage); + sqliteFree(pCur); + } + unlockBtreeIfUnused(pBt); + return rc; +} + +/* +** Close a cursor. The read lock on the database file is released +** when the last cursor is closed. +*/ +static int fileBtreeCloseCursor(BtCursor *pCur){ + Btree *pBt = pCur->pBt; + if( pCur->pPrev ){ + pCur->pPrev->pNext = pCur->pNext; + }else{ + pBt->pCursor = pCur->pNext; + } + if( pCur->pNext ){ + pCur->pNext->pPrev = pCur->pPrev; + } + if( pCur->pPage ){ + sqlitepager_unref(pCur->pPage); + } + if( pCur->pShared!=pCur ){ + BtCursor *pRing = pCur->pShared; + while( pRing->pShared!=pCur ){ pRing = pRing->pShared; } + pRing->pShared = pCur->pShared; + } + unlockBtreeIfUnused(pBt); + sqliteFree(pCur); + return SQLITE_OK; +} + +/* +** Make a temporary cursor by filling in the fields of pTempCur. +** The temporary cursor is not on the cursor list for the Btree. +*/ +static void getTempCursor(BtCursor *pCur, BtCursor *pTempCur){ + memcpy(pTempCur, pCur, sizeof(*pCur)); + pTempCur->pNext = 0; + pTempCur->pPrev = 0; + if( pTempCur->pPage ){ + sqlitepager_ref(pTempCur->pPage); + } +} + +/* +** Delete a temporary cursor such as was made by the CreateTemporaryCursor() +** function above. +*/ +static void releaseTempCursor(BtCursor *pCur){ + if( pCur->pPage ){ + sqlitepager_unref(pCur->pPage); + } +} + +/* +** Set *pSize to the number of bytes of key in the entry the +** cursor currently points to. Always return SQLITE_OK. +** Failure is not possible. If the cursor is not currently +** pointing to an entry (which can happen, for example, if +** the database is empty) then *pSize is set to 0. +*/ +static int fileBtreeKeySize(BtCursor *pCur, int *pSize){ + Cell *pCell; + MemPage *pPage; + + pPage = pCur->pPage; + assert( pPage!=0 ); + if( pCur->idx >= pPage->nCell ){ + *pSize = 0; + }else{ + pCell = pPage->apCell[pCur->idx]; + *pSize = NKEY(pCur->pBt, pCell->h); + } + return SQLITE_OK; +} + +/* +** Read payload information from the entry that the pCur cursor is +** pointing to. Begin reading the payload at "offset" and read +** a total of "amt" bytes. Put the result in zBuf. +** +** This routine does not make a distinction between key and data. +** It just reads bytes from the payload area. +*/ +static int getPayload(BtCursor *pCur, int offset, int amt, char *zBuf){ + char *aPayload; + Pgno nextPage; + int rc; + Btree *pBt = pCur->pBt; + assert( pCur!=0 && pCur->pPage!=0 ); + assert( pCur->idx>=0 && pCur->idxpPage->nCell ); + aPayload = pCur->pPage->apCell[pCur->idx]->aPayload; + if( offsetMX_LOCAL_PAYLOAD ){ + a = MX_LOCAL_PAYLOAD - offset; + } + memcpy(zBuf, &aPayload[offset], a); + if( a==amt ){ + return SQLITE_OK; + } + offset = 0; + zBuf += a; + amt -= a; + }else{ + offset -= MX_LOCAL_PAYLOAD; + } + if( amt>0 ){ + nextPage = SWAB32(pBt, pCur->pPage->apCell[pCur->idx]->ovfl); + } + while( amt>0 && nextPage ){ + OverflowPage *pOvfl; + rc = sqlitepager_get(pBt->pPager, nextPage, (void**)&pOvfl); + if( rc!=0 ){ + return rc; + } + nextPage = SWAB32(pBt, pOvfl->iNext); + if( offset OVERFLOW_SIZE ){ + a = OVERFLOW_SIZE - offset; + } + memcpy(zBuf, &pOvfl->aPayload[offset], a); + offset = 0; + amt -= a; + zBuf += a; + }else{ + offset -= OVERFLOW_SIZE; + } + sqlitepager_unref(pOvfl); + } + if( amt>0 ){ + return SQLITE_CORRUPT; + } + return SQLITE_OK; +} + +/* +** Read part of the key associated with cursor pCur. A maximum +** of "amt" bytes will be transfered into zBuf[]. The transfer +** begins at "offset". The number of bytes actually read is +** returned. +** +** Change: It used to be that the amount returned will be smaller +** than the amount requested if there are not enough bytes in the key +** to satisfy the request. But now, it must be the case that there +** is enough data available to satisfy the request. If not, an exception +** is raised. The change was made in an effort to boost performance +** by eliminating unneeded tests. +*/ +static int fileBtreeKey(BtCursor *pCur, int offset, int amt, char *zBuf){ + MemPage *pPage; + + assert( amt>=0 ); + assert( offset>=0 ); + assert( pCur->pPage!=0 ); + pPage = pCur->pPage; + if( pCur->idx >= pPage->nCell ){ + return 0; + } + assert( amt+offset <= NKEY(pCur->pBt, pPage->apCell[pCur->idx]->h) ); + getPayload(pCur, offset, amt, zBuf); + return amt; +} + +/* +** Set *pSize to the number of bytes of data in the entry the +** cursor currently points to. Always return SQLITE_OK. +** Failure is not possible. If the cursor is not currently +** pointing to an entry (which can happen, for example, if +** the database is empty) then *pSize is set to 0. +*/ +static int fileBtreeDataSize(BtCursor *pCur, int *pSize){ + Cell *pCell; + MemPage *pPage; + + pPage = pCur->pPage; + assert( pPage!=0 ); + if( pCur->idx >= pPage->nCell ){ + *pSize = 0; + }else{ + pCell = pPage->apCell[pCur->idx]; + *pSize = NDATA(pCur->pBt, pCell->h); + } + return SQLITE_OK; +} + +/* +** Read part of the data associated with cursor pCur. A maximum +** of "amt" bytes will be transfered into zBuf[]. The transfer +** begins at "offset". The number of bytes actually read is +** returned. The amount returned will be smaller than the +** amount requested if there are not enough bytes in the data +** to satisfy the request. +*/ +static int fileBtreeData(BtCursor *pCur, int offset, int amt, char *zBuf){ + Cell *pCell; + MemPage *pPage; + + assert( amt>=0 ); + assert( offset>=0 ); + assert( pCur->pPage!=0 ); + pPage = pCur->pPage; + if( pCur->idx >= pPage->nCell ){ + return 0; + } + pCell = pPage->apCell[pCur->idx]; + assert( amt+offset <= NDATA(pCur->pBt, pCell->h) ); + getPayload(pCur, offset + NKEY(pCur->pBt, pCell->h), amt, zBuf); + return amt; +} + +/* +** Compare an external key against the key on the entry that pCur points to. +** +** The external key is pKey and is nKey bytes long. The last nIgnore bytes +** of the key associated with pCur are ignored, as if they do not exist. +** (The normal case is for nIgnore to be zero in which case the entire +** internal key is used in the comparison.) +** +** The comparison result is written to *pRes as follows: +** +** *pRes<0 This means pCur0 This means pCur>pKey +** +** When one key is an exact prefix of the other, the shorter key is +** considered less than the longer one. In order to be equal the +** keys must be exactly the same length. (The length of the pCur key +** is the actual key length minus nIgnore bytes.) +*/ +static int fileBtreeKeyCompare( + BtCursor *pCur, /* Pointer to entry to compare against */ + const void *pKey, /* Key to compare against entry that pCur points to */ + int nKey, /* Number of bytes in pKey */ + int nIgnore, /* Ignore this many bytes at the end of pCur */ + int *pResult /* Write the result here */ +){ + Pgno nextPage; + int n, c, rc, nLocal; + Cell *pCell; + Btree *pBt = pCur->pBt; + const char *zKey = (const char*)pKey; + + assert( pCur->pPage ); + assert( pCur->idx>=0 && pCur->idxpPage->nCell ); + pCell = pCur->pPage->apCell[pCur->idx]; + nLocal = NKEY(pBt, pCell->h) - nIgnore; + if( nLocal<0 ) nLocal = 0; + n = nKeyMX_LOCAL_PAYLOAD ){ + n = MX_LOCAL_PAYLOAD; + } + c = memcmp(pCell->aPayload, zKey, n); + if( c!=0 ){ + *pResult = c; + return SQLITE_OK; + } + zKey += n; + nKey -= n; + nLocal -= n; + nextPage = SWAB32(pBt, pCell->ovfl); + while( nKey>0 && nLocal>0 ){ + OverflowPage *pOvfl; + if( nextPage==0 ){ + return SQLITE_CORRUPT; + } + rc = sqlitepager_get(pBt->pPager, nextPage, (void**)&pOvfl); + if( rc ){ + return rc; + } + nextPage = SWAB32(pBt, pOvfl->iNext); + n = nKeyOVERFLOW_SIZE ){ + n = OVERFLOW_SIZE; + } + c = memcmp(pOvfl->aPayload, zKey, n); + sqlitepager_unref(pOvfl); + if( c!=0 ){ + *pResult = c; + return SQLITE_OK; + } + nKey -= n; + nLocal -= n; + zKey += n; + } + if( c==0 ){ + c = nLocal - nKey; + } + *pResult = c; + return SQLITE_OK; +} + +/* +** Move the cursor down to a new child page. The newPgno argument is the +** page number of the child page in the byte order of the disk image. +*/ +static int moveToChild(BtCursor *pCur, int newPgno){ + int rc; + MemPage *pNewPage; + Btree *pBt = pCur->pBt; + + newPgno = SWAB32(pBt, newPgno); + rc = sqlitepager_get(pBt->pPager, newPgno, (void**)&pNewPage); + if( rc ) return rc; + rc = initPage(pBt, pNewPage, newPgno, pCur->pPage); + if( rc ) return rc; + assert( pCur->idx>=pCur->pPage->nCell + || pCur->pPage->apCell[pCur->idx]->h.leftChild==SWAB32(pBt,newPgno) ); + assert( pCur->idxpPage->nCell + || pCur->pPage->u.hdr.rightChild==SWAB32(pBt,newPgno) ); + pNewPage->idxParent = pCur->idx; + pCur->pPage->idxShift = 0; + sqlitepager_unref(pCur->pPage); + pCur->pPage = pNewPage; + pCur->idx = 0; + if( pNewPage->nCell<1 ){ + return SQLITE_CORRUPT; + } + return SQLITE_OK; +} + +/* +** Move the cursor up to the parent page. +** +** pCur->idx is set to the cell index that contains the pointer +** to the page we are coming from. If we are coming from the +** right-most child page then pCur->idx is set to one more than +** the largest cell index. +*/ +static void moveToParent(BtCursor *pCur){ + Pgno oldPgno; + MemPage *pParent; + MemPage *pPage; + int idxParent; + pPage = pCur->pPage; + assert( pPage!=0 ); + pParent = pPage->pParent; + assert( pParent!=0 ); + idxParent = pPage->idxParent; + sqlitepager_ref(pParent); + sqlitepager_unref(pPage); + pCur->pPage = pParent; + assert( pParent->idxShift==0 ); + if( pParent->idxShift==0 ){ + pCur->idx = idxParent; +#ifndef NDEBUG + /* Verify that pCur->idx is the correct index to point back to the child + ** page we just came from + */ + oldPgno = SWAB32(pCur->pBt, sqlitepager_pagenumber(pPage)); + if( pCur->idxnCell ){ + assert( pParent->apCell[idxParent]->h.leftChild==oldPgno ); + }else{ + assert( pParent->u.hdr.rightChild==oldPgno ); + } +#endif + }else{ + /* The MemPage.idxShift flag indicates that cell indices might have + ** changed since idxParent was set and hence idxParent might be out + ** of date. So recompute the parent cell index by scanning all cells + ** and locating the one that points to the child we just came from. + */ + int i; + pCur->idx = pParent->nCell; + oldPgno = SWAB32(pCur->pBt, sqlitepager_pagenumber(pPage)); + for(i=0; inCell; i++){ + if( pParent->apCell[i]->h.leftChild==oldPgno ){ + pCur->idx = i; + break; + } + } + } +} + +/* +** Move the cursor to the root page +*/ +static int moveToRoot(BtCursor *pCur){ + MemPage *pNew; + int rc; + Btree *pBt = pCur->pBt; + + rc = sqlitepager_get(pBt->pPager, pCur->pgnoRoot, (void**)&pNew); + if( rc ) return rc; + rc = initPage(pBt, pNew, pCur->pgnoRoot, 0); + if( rc ) return rc; + sqlitepager_unref(pCur->pPage); + pCur->pPage = pNew; + pCur->idx = 0; + return SQLITE_OK; +} + +/* +** Move the cursor down to the left-most leaf entry beneath the +** entry to which it is currently pointing. +*/ +static int moveToLeftmost(BtCursor *pCur){ + Pgno pgno; + int rc; + + while( (pgno = pCur->pPage->apCell[pCur->idx]->h.leftChild)!=0 ){ + rc = moveToChild(pCur, pgno); + if( rc ) return rc; + } + return SQLITE_OK; +} + +/* +** Move the cursor down to the right-most leaf entry beneath the +** page to which it is currently pointing. Notice the difference +** between moveToLeftmost() and moveToRightmost(). moveToLeftmost() +** finds the left-most entry beneath the *entry* whereas moveToRightmost() +** finds the right-most entry beneath the *page*. +*/ +static int moveToRightmost(BtCursor *pCur){ + Pgno pgno; + int rc; + + while( (pgno = pCur->pPage->u.hdr.rightChild)!=0 ){ + pCur->idx = pCur->pPage->nCell; + rc = moveToChild(pCur, pgno); + if( rc ) return rc; + } + pCur->idx = pCur->pPage->nCell - 1; + return SQLITE_OK; +} + +/* Move the cursor to the first entry in the table. Return SQLITE_OK +** on success. Set *pRes to 0 if the cursor actually points to something +** or set *pRes to 1 if the table is empty. +*/ +static int fileBtreeFirst(BtCursor *pCur, int *pRes){ + int rc; + if( pCur->pPage==0 ) return SQLITE_ABORT; + rc = moveToRoot(pCur); + if( rc ) return rc; + if( pCur->pPage->nCell==0 ){ + *pRes = 1; + return SQLITE_OK; + } + *pRes = 0; + rc = moveToLeftmost(pCur); + pCur->eSkip = SKIP_NONE; + return rc; +} + +/* Move the cursor to the last entry in the table. Return SQLITE_OK +** on success. Set *pRes to 0 if the cursor actually points to something +** or set *pRes to 1 if the table is empty. +*/ +static int fileBtreeLast(BtCursor *pCur, int *pRes){ + int rc; + if( pCur->pPage==0 ) return SQLITE_ABORT; + rc = moveToRoot(pCur); + if( rc ) return rc; + assert( pCur->pPage->isInit ); + if( pCur->pPage->nCell==0 ){ + *pRes = 1; + return SQLITE_OK; + } + *pRes = 0; + rc = moveToRightmost(pCur); + pCur->eSkip = SKIP_NONE; + return rc; +} + +/* Move the cursor so that it points to an entry near pKey. +** Return a success code. +** +** If an exact match is not found, then the cursor is always +** left pointing at a leaf page which would hold the entry if it +** were present. The cursor might point to an entry that comes +** before or after the key. +** +** The result of comparing the key with the entry to which the +** cursor is left pointing is stored in pCur->iMatch. The same +** value is also written to *pRes if pRes!=NULL. The meaning of +** this value is as follows: +** +** *pRes<0 The cursor is left pointing at an entry that +** is smaller than pKey or if the table is empty +** and the cursor is therefore left point to nothing. +** +** *pRes==0 The cursor is left pointing at an entry that +** exactly matches pKey. +** +** *pRes>0 The cursor is left pointing at an entry that +** is larger than pKey. +*/ +static +int fileBtreeMoveto(BtCursor *pCur, const void *pKey, int nKey, int *pRes){ + int rc; + if( pCur->pPage==0 ) return SQLITE_ABORT; + pCur->eSkip = SKIP_NONE; + rc = moveToRoot(pCur); + if( rc ) return rc; + for(;;){ + int lwr, upr; + Pgno chldPg; + MemPage *pPage = pCur->pPage; + int c = -1; /* pRes return if table is empty must be -1 */ + lwr = 0; + upr = pPage->nCell-1; + while( lwr<=upr ){ + pCur->idx = (lwr+upr)/2; + rc = fileBtreeKeyCompare(pCur, pKey, nKey, 0, &c); + if( rc ) return rc; + if( c==0 ){ + pCur->iMatch = c; + if( pRes ) *pRes = 0; + return SQLITE_OK; + } + if( c<0 ){ + lwr = pCur->idx+1; + }else{ + upr = pCur->idx-1; + } + } + assert( lwr==upr+1 ); + assert( pPage->isInit ); + if( lwr>=pPage->nCell ){ + chldPg = pPage->u.hdr.rightChild; + }else{ + chldPg = pPage->apCell[lwr]->h.leftChild; + } + if( chldPg==0 ){ + pCur->iMatch = c; + if( pRes ) *pRes = c; + return SQLITE_OK; + } + pCur->idx = lwr; + rc = moveToChild(pCur, chldPg); + if( rc ) return rc; + } + /* NOT REACHED */ +} + +/* +** Advance the cursor to the next entry in the database. If +** successful then set *pRes=0. If the cursor +** was already pointing to the last entry in the database before +** this routine was called, then set *pRes=1. +*/ +static int fileBtreeNext(BtCursor *pCur, int *pRes){ + int rc; + MemPage *pPage = pCur->pPage; + assert( pRes!=0 ); + if( pPage==0 ){ + *pRes = 1; + return SQLITE_ABORT; + } + assert( pPage->isInit ); + assert( pCur->eSkip!=SKIP_INVALID ); + if( pPage->nCell==0 ){ + *pRes = 1; + return SQLITE_OK; + } + assert( pCur->idxnCell ); + if( pCur->eSkip==SKIP_NEXT ){ + pCur->eSkip = SKIP_NONE; + *pRes = 0; + return SQLITE_OK; + } + pCur->eSkip = SKIP_NONE; + pCur->idx++; + if( pCur->idx>=pPage->nCell ){ + if( pPage->u.hdr.rightChild ){ + rc = moveToChild(pCur, pPage->u.hdr.rightChild); + if( rc ) return rc; + rc = moveToLeftmost(pCur); + *pRes = 0; + return rc; + } + do{ + if( pPage->pParent==0 ){ + *pRes = 1; + return SQLITE_OK; + } + moveToParent(pCur); + pPage = pCur->pPage; + }while( pCur->idx>=pPage->nCell ); + *pRes = 0; + return SQLITE_OK; + } + *pRes = 0; + if( pPage->u.hdr.rightChild==0 ){ + return SQLITE_OK; + } + rc = moveToLeftmost(pCur); + return rc; +} + +/* +** Step the cursor to the back to the previous entry in the database. If +** successful then set *pRes=0. If the cursor +** was already pointing to the first entry in the database before +** this routine was called, then set *pRes=1. +*/ +static int fileBtreePrevious(BtCursor *pCur, int *pRes){ + int rc; + Pgno pgno; + MemPage *pPage; + pPage = pCur->pPage; + if( pPage==0 ){ + *pRes = 1; + return SQLITE_ABORT; + } + assert( pPage->isInit ); + assert( pCur->eSkip!=SKIP_INVALID ); + if( pPage->nCell==0 ){ + *pRes = 1; + return SQLITE_OK; + } + if( pCur->eSkip==SKIP_PREV ){ + pCur->eSkip = SKIP_NONE; + *pRes = 0; + return SQLITE_OK; + } + pCur->eSkip = SKIP_NONE; + assert( pCur->idx>=0 ); + if( (pgno = pPage->apCell[pCur->idx]->h.leftChild)!=0 ){ + rc = moveToChild(pCur, pgno); + if( rc ) return rc; + rc = moveToRightmost(pCur); + }else{ + while( pCur->idx==0 ){ + if( pPage->pParent==0 ){ + if( pRes ) *pRes = 1; + return SQLITE_OK; + } + moveToParent(pCur); + pPage = pCur->pPage; + } + pCur->idx--; + rc = SQLITE_OK; + } + *pRes = 0; + return rc; +} + +/* +** Allocate a new page from the database file. +** +** The new page is marked as dirty. (In other words, sqlitepager_write() +** has already been called on the new page.) The new page has also +** been referenced and the calling routine is responsible for calling +** sqlitepager_unref() on the new page when it is done. +** +** SQLITE_OK is returned on success. Any other return value indicates +** an error. *ppPage and *pPgno are undefined in the event of an error. +** Do not invoke sqlitepager_unref() on *ppPage if an error is returned. +** +** If the "nearby" parameter is not 0, then a (feeble) effort is made to +** locate a page close to the page number "nearby". This can be used in an +** attempt to keep related pages close to each other in the database file, +** which in turn can make database access faster. +*/ +static int allocatePage(Btree *pBt, MemPage **ppPage, Pgno *pPgno, Pgno nearby){ + PageOne *pPage1 = pBt->page1; + int rc; + if( pPage1->freeList ){ + OverflowPage *pOvfl; + FreelistInfo *pInfo; + + rc = sqlitepager_write(pPage1); + if( rc ) return rc; + SWAB_ADD(pBt, pPage1->nFree, -1); + rc = sqlitepager_get(pBt->pPager, SWAB32(pBt, pPage1->freeList), + (void**)&pOvfl); + if( rc ) return rc; + rc = sqlitepager_write(pOvfl); + if( rc ){ + sqlitepager_unref(pOvfl); + return rc; + } + pInfo = (FreelistInfo*)pOvfl->aPayload; + if( pInfo->nFree==0 ){ + *pPgno = SWAB32(pBt, pPage1->freeList); + pPage1->freeList = pOvfl->iNext; + *ppPage = (MemPage*)pOvfl; + }else{ + int closest, n; + n = SWAB32(pBt, pInfo->nFree); + if( n>1 && nearby>0 ){ + int i, dist; + closest = 0; + dist = SWAB32(pBt, pInfo->aFree[0]) - nearby; + if( dist<0 ) dist = -dist; + for(i=1; iaFree[i]) - nearby; + if( d2<0 ) d2 = -d2; + if( d2nFree, -1); + *pPgno = SWAB32(pBt, pInfo->aFree[closest]); + pInfo->aFree[closest] = pInfo->aFree[n-1]; + rc = sqlitepager_get(pBt->pPager, *pPgno, (void**)ppPage); + sqlitepager_unref(pOvfl); + if( rc==SQLITE_OK ){ + sqlitepager_dont_rollback(*ppPage); + rc = sqlitepager_write(*ppPage); + } + } + }else{ + *pPgno = sqlitepager_pagecount(pBt->pPager) + 1; + rc = sqlitepager_get(pBt->pPager, *pPgno, (void**)ppPage); + if( rc ) return rc; + rc = sqlitepager_write(*ppPage); + } + return rc; +} + +/* +** Add a page of the database file to the freelist. Either pgno or +** pPage but not both may be 0. +** +** sqlitepager_unref() is NOT called for pPage. +*/ +static int freePage(Btree *pBt, void *pPage, Pgno pgno){ + PageOne *pPage1 = pBt->page1; + OverflowPage *pOvfl = (OverflowPage*)pPage; + int rc; + int needUnref = 0; + MemPage *pMemPage; + + if( pgno==0 ){ + assert( pOvfl!=0 ); + pgno = sqlitepager_pagenumber(pOvfl); + } + assert( pgno>2 ); + assert( sqlitepager_pagenumber(pOvfl)==pgno ); + pMemPage = (MemPage*)pPage; + pMemPage->isInit = 0; + if( pMemPage->pParent ){ + sqlitepager_unref(pMemPage->pParent); + pMemPage->pParent = 0; + } + rc = sqlitepager_write(pPage1); + if( rc ){ + return rc; + } + SWAB_ADD(pBt, pPage1->nFree, 1); + if( pPage1->nFree!=0 && pPage1->freeList!=0 ){ + OverflowPage *pFreeIdx; + rc = sqlitepager_get(pBt->pPager, SWAB32(pBt, pPage1->freeList), + (void**)&pFreeIdx); + if( rc==SQLITE_OK ){ + FreelistInfo *pInfo = (FreelistInfo*)pFreeIdx->aPayload; + int n = SWAB32(pBt, pInfo->nFree); + if( n<(sizeof(pInfo->aFree)/sizeof(pInfo->aFree[0])) ){ + rc = sqlitepager_write(pFreeIdx); + if( rc==SQLITE_OK ){ + pInfo->aFree[n] = SWAB32(pBt, pgno); + SWAB_ADD(pBt, pInfo->nFree, 1); + sqlitepager_unref(pFreeIdx); + sqlitepager_dont_write(pBt->pPager, pgno); + return rc; + } + } + sqlitepager_unref(pFreeIdx); + } + } + if( pOvfl==0 ){ + assert( pgno>0 ); + rc = sqlitepager_get(pBt->pPager, pgno, (void**)&pOvfl); + if( rc ) return rc; + needUnref = 1; + } + rc = sqlitepager_write(pOvfl); + if( rc ){ + if( needUnref ) sqlitepager_unref(pOvfl); + return rc; + } + pOvfl->iNext = pPage1->freeList; + pPage1->freeList = SWAB32(pBt, pgno); + memset(pOvfl->aPayload, 0, OVERFLOW_SIZE); + if( needUnref ) rc = sqlitepager_unref(pOvfl); + return rc; +} + +/* +** Erase all the data out of a cell. This involves returning overflow +** pages back the freelist. +*/ +static int clearCell(Btree *pBt, Cell *pCell){ + Pager *pPager = pBt->pPager; + OverflowPage *pOvfl; + Pgno ovfl, nextOvfl; + int rc; + + if( NKEY(pBt, pCell->h) + NDATA(pBt, pCell->h) <= MX_LOCAL_PAYLOAD ){ + return SQLITE_OK; + } + ovfl = SWAB32(pBt, pCell->ovfl); + pCell->ovfl = 0; + while( ovfl ){ + rc = sqlitepager_get(pPager, ovfl, (void**)&pOvfl); + if( rc ) return rc; + nextOvfl = SWAB32(pBt, pOvfl->iNext); + rc = freePage(pBt, pOvfl, ovfl); + if( rc ) return rc; + sqlitepager_unref(pOvfl); + ovfl = nextOvfl; + } + return SQLITE_OK; +} + +/* +** Create a new cell from key and data. Overflow pages are allocated as +** necessary and linked to this cell. +*/ +static int fillInCell( + Btree *pBt, /* The whole Btree. Needed to allocate pages */ + Cell *pCell, /* Populate this Cell structure */ + const void *pKey, int nKey, /* The key */ + const void *pData,int nData /* The data */ +){ + OverflowPage *pOvfl, *pPrior; + Pgno *pNext; + int spaceLeft; + int n, rc; + int nPayload; + const char *pPayload; + char *pSpace; + Pgno nearby = 0; + + pCell->h.leftChild = 0; + pCell->h.nKey = SWAB16(pBt, nKey & 0xffff); + pCell->h.nKeyHi = nKey >> 16; + pCell->h.nData = SWAB16(pBt, nData & 0xffff); + pCell->h.nDataHi = nData >> 16; + pCell->h.iNext = 0; + + pNext = &pCell->ovfl; + pSpace = pCell->aPayload; + spaceLeft = MX_LOCAL_PAYLOAD; + pPayload = pKey; + pKey = 0; + nPayload = nKey; + pPrior = 0; + while( nPayload>0 ){ + if( spaceLeft==0 ){ + rc = allocatePage(pBt, (MemPage**)&pOvfl, pNext, nearby); + if( rc ){ + *pNext = 0; + }else{ + nearby = *pNext; + } + if( pPrior ) sqlitepager_unref(pPrior); + if( rc ){ + clearCell(pBt, pCell); + return rc; + } + if( pBt->needSwab ) *pNext = swab32(*pNext); + pPrior = pOvfl; + spaceLeft = OVERFLOW_SIZE; + pSpace = pOvfl->aPayload; + pNext = &pOvfl->iNext; + } + n = nPayload; + if( n>spaceLeft ) n = spaceLeft; + memcpy(pSpace, pPayload, n); + nPayload -= n; + if( nPayload==0 && pData ){ + pPayload = pData; + nPayload = nData; + pData = 0; + }else{ + pPayload += n; + } + spaceLeft -= n; + pSpace += n; + } + *pNext = 0; + if( pPrior ){ + sqlitepager_unref(pPrior); + } + return SQLITE_OK; +} + +/* +** Change the MemPage.pParent pointer on the page whose number is +** given in the second argument so that MemPage.pParent holds the +** pointer in the third argument. +*/ +static void reparentPage(Pager *pPager, Pgno pgno, MemPage *pNewParent,int idx){ + MemPage *pThis; + + if( pgno==0 ) return; + assert( pPager!=0 ); + pThis = sqlitepager_lookup(pPager, pgno); + if( pThis && pThis->isInit ){ + if( pThis->pParent!=pNewParent ){ + if( pThis->pParent ) sqlitepager_unref(pThis->pParent); + pThis->pParent = pNewParent; + if( pNewParent ) sqlitepager_ref(pNewParent); + } + pThis->idxParent = idx; + sqlitepager_unref(pThis); + } +} + +/* +** Reparent all children of the given page to be the given page. +** In other words, for every child of pPage, invoke reparentPage() +** to make sure that each child knows that pPage is its parent. +** +** This routine gets called after you memcpy() one page into +** another. +*/ +static void reparentChildPages(Btree *pBt, MemPage *pPage){ + int i; + Pager *pPager = pBt->pPager; + for(i=0; inCell; i++){ + reparentPage(pPager, SWAB32(pBt, pPage->apCell[i]->h.leftChild), pPage, i); + } + reparentPage(pPager, SWAB32(pBt, pPage->u.hdr.rightChild), pPage, i); + pPage->idxShift = 0; +} + +/* +** Remove the i-th cell from pPage. This routine effects pPage only. +** The cell content is not freed or deallocated. It is assumed that +** the cell content has been copied someplace else. This routine just +** removes the reference to the cell from pPage. +** +** "sz" must be the number of bytes in the cell. +** +** Do not bother maintaining the integrity of the linked list of Cells. +** Only the pPage->apCell[] array is important. The relinkCellList() +** routine will be called soon after this routine in order to rebuild +** the linked list. +*/ +static void dropCell(Btree *pBt, MemPage *pPage, int idx, int sz){ + int j; + assert( idx>=0 && idxnCell ); + assert( sz==cellSize(pBt, pPage->apCell[idx]) ); + assert( sqlitepager_iswriteable(pPage) ); + freeSpace(pBt, pPage, Addr(pPage->apCell[idx]) - Addr(pPage), sz); + for(j=idx; jnCell-1; j++){ + pPage->apCell[j] = pPage->apCell[j+1]; + } + pPage->nCell--; + pPage->idxShift = 1; +} + +/* +** Insert a new cell on pPage at cell index "i". pCell points to the +** content of the cell. +** +** If the cell content will fit on the page, then put it there. If it +** will not fit, then just make pPage->apCell[i] point to the content +** and set pPage->isOverfull. +** +** Do not bother maintaining the integrity of the linked list of Cells. +** Only the pPage->apCell[] array is important. The relinkCellList() +** routine will be called soon after this routine in order to rebuild +** the linked list. +*/ +static void insertCell(Btree *pBt, MemPage *pPage, int i, Cell *pCell, int sz){ + int idx, j; + assert( i>=0 && i<=pPage->nCell ); + assert( sz==cellSize(pBt, pCell) ); + assert( sqlitepager_iswriteable(pPage) ); + idx = allocateSpace(pBt, pPage, sz); + for(j=pPage->nCell; j>i; j--){ + pPage->apCell[j] = pPage->apCell[j-1]; + } + pPage->nCell++; + if( idx<=0 ){ + pPage->isOverfull = 1; + pPage->apCell[i] = pCell; + }else{ + memcpy(&pPage->u.aDisk[idx], pCell, sz); + pPage->apCell[i] = (Cell*)&pPage->u.aDisk[idx]; + } + pPage->idxShift = 1; +} + +/* +** Rebuild the linked list of cells on a page so that the cells +** occur in the order specified by the pPage->apCell[] array. +** Invoke this routine once to repair damage after one or more +** invocations of either insertCell() or dropCell(). +*/ +static void relinkCellList(Btree *pBt, MemPage *pPage){ + int i; + u16 *pIdx; + assert( sqlitepager_iswriteable(pPage) ); + pIdx = &pPage->u.hdr.firstCell; + for(i=0; inCell; i++){ + int idx = Addr(pPage->apCell[i]) - Addr(pPage); + assert( idx>0 && idxapCell[i]->h.iNext; + } + *pIdx = 0; +} + +/* +** Make a copy of the contents of pFrom into pTo. The pFrom->apCell[] +** pointers that point into pFrom->u.aDisk[] must be adjusted to point +** into pTo->u.aDisk[] instead. But some pFrom->apCell[] entries might +** not point to pFrom->u.aDisk[]. Those are unchanged. +*/ +static void copyPage(MemPage *pTo, MemPage *pFrom){ + uptr from, to; + int i; + memcpy(pTo->u.aDisk, pFrom->u.aDisk, SQLITE_USABLE_SIZE); + pTo->pParent = 0; + pTo->isInit = 1; + pTo->nCell = pFrom->nCell; + pTo->nFree = pFrom->nFree; + pTo->isOverfull = pFrom->isOverfull; + to = Addr(pTo); + from = Addr(pFrom); + for(i=0; inCell; i++){ + uptr x = Addr(pFrom->apCell[i]); + if( x>from && xapCell[i]) = x + to - from; + }else{ + pTo->apCell[i] = pFrom->apCell[i]; + } + } +} + +/* +** The following parameters determine how many adjacent pages get involved +** in a balancing operation. NN is the number of neighbors on either side +** of the page that participate in the balancing operation. NB is the +** total number of pages that participate, including the target page and +** NN neighbors on either side. +** +** The minimum value of NN is 1 (of course). Increasing NN above 1 +** (to 2 or 3) gives a modest improvement in SELECT and DELETE performance +** in exchange for a larger degradation in INSERT and UPDATE performance. +** The value of NN appears to give the best results overall. +*/ +#define NN 1 /* Number of neighbors on either side of pPage */ +#define NB (NN*2+1) /* Total pages involved in the balance */ + +/* +** This routine redistributes Cells on pPage and up to two siblings +** of pPage so that all pages have about the same amount of free space. +** Usually one sibling on either side of pPage is used in the balancing, +** though both siblings might come from one side if pPage is the first +** or last child of its parent. If pPage has fewer than two siblings +** (something which can only happen if pPage is the root page or a +** child of root) then all available siblings participate in the balancing. +** +** The number of siblings of pPage might be increased or decreased by +** one in an effort to keep pages between 66% and 100% full. The root page +** is special and is allowed to be less than 66% full. If pPage is +** the root page, then the depth of the tree might be increased +** or decreased by one, as necessary, to keep the root page from being +** overfull or empty. +** +** This routine calls relinkCellList() on its input page regardless of +** whether or not it does any real balancing. Client routines will typically +** invoke insertCell() or dropCell() before calling this routine, so we +** need to call relinkCellList() to clean up the mess that those other +** routines left behind. +** +** pCur is left pointing to the same cell as when this routine was called +** even if that cell gets moved to a different page. pCur may be NULL. +** Set the pCur parameter to NULL if you do not care about keeping track +** of a cell as that will save this routine the work of keeping track of it. +** +** Note that when this routine is called, some of the Cells on pPage +** might not actually be stored in pPage->u.aDisk[]. This can happen +** if the page is overfull. Part of the job of this routine is to +** make sure all Cells for pPage once again fit in pPage->u.aDisk[]. +** +** In the course of balancing the siblings of pPage, the parent of pPage +** might become overfull or underfull. If that happens, then this routine +** is called recursively on the parent. +** +** If this routine fails for any reason, it might leave the database +** in a corrupted state. So if this routine fails, the database should +** be rolled back. +*/ +static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){ + MemPage *pParent; /* The parent of pPage */ + int nCell; /* Number of cells in apCell[] */ + int nOld; /* Number of pages in apOld[] */ + int nNew; /* Number of pages in apNew[] */ + int nDiv; /* Number of cells in apDiv[] */ + int i, j, k; /* Loop counters */ + int idx; /* Index of pPage in pParent->apCell[] */ + int nxDiv; /* Next divider slot in pParent->apCell[] */ + int rc; /* The return code */ + int iCur; /* apCell[iCur] is the cell of the cursor */ + MemPage *pOldCurPage; /* The cursor originally points to this page */ + int subtotal; /* Subtotal of bytes in cells on one page */ + MemPage *extraUnref = 0; /* A page that needs to be unref-ed */ + MemPage *apOld[NB]; /* pPage and up to two siblings */ + Pgno pgnoOld[NB]; /* Page numbers for each page in apOld[] */ + MemPage *apNew[NB+1]; /* pPage and up to NB siblings after balancing */ + Pgno pgnoNew[NB+1]; /* Page numbers for each page in apNew[] */ + int idxDiv[NB]; /* Indices of divider cells in pParent */ + Cell *apDiv[NB]; /* Divider cells in pParent */ + Cell aTemp[NB]; /* Temporary holding area for apDiv[] */ + int cntNew[NB+1]; /* Index in apCell[] of cell after i-th page */ + int szNew[NB+1]; /* Combined size of cells place on i-th page */ + MemPage aOld[NB]; /* Temporary copies of pPage and its siblings */ + Cell *apCell[(MX_CELL+2)*NB]; /* All cells from pages being balanced */ + int szCell[(MX_CELL+2)*NB]; /* Local size of all cells */ + + /* + ** Return without doing any work if pPage is neither overfull nor + ** underfull. + */ + assert( sqlitepager_iswriteable(pPage) ); + if( !pPage->isOverfull && pPage->nFreenCell>=2){ + relinkCellList(pBt, pPage); + return SQLITE_OK; + } + + /* + ** Find the parent of the page to be balanceed. + ** If there is no parent, it means this page is the root page and + ** special rules apply. + */ + pParent = pPage->pParent; + if( pParent==0 ){ + Pgno pgnoChild; + MemPage *pChild; + assert( pPage->isInit ); + if( pPage->nCell==0 ){ + if( pPage->u.hdr.rightChild ){ + /* + ** The root page is empty. Copy the one child page + ** into the root page and return. This reduces the depth + ** of the BTree by one. + */ + pgnoChild = SWAB32(pBt, pPage->u.hdr.rightChild); + rc = sqlitepager_get(pBt->pPager, pgnoChild, (void**)&pChild); + if( rc ) return rc; + memcpy(pPage, pChild, SQLITE_USABLE_SIZE); + pPage->isInit = 0; + rc = initPage(pBt, pPage, sqlitepager_pagenumber(pPage), 0); + assert( rc==SQLITE_OK ); + reparentChildPages(pBt, pPage); + if( pCur && pCur->pPage==pChild ){ + sqlitepager_unref(pChild); + pCur->pPage = pPage; + sqlitepager_ref(pPage); + } + freePage(pBt, pChild, pgnoChild); + sqlitepager_unref(pChild); + }else{ + relinkCellList(pBt, pPage); + } + return SQLITE_OK; + } + if( !pPage->isOverfull ){ + /* It is OK for the root page to be less than half full. + */ + relinkCellList(pBt, pPage); + return SQLITE_OK; + } + /* + ** If we get to here, it means the root page is overfull. + ** When this happens, Create a new child page and copy the + ** contents of the root into the child. Then make the root + ** page an empty page with rightChild pointing to the new + ** child. Then fall thru to the code below which will cause + ** the overfull child page to be split. + */ + rc = sqlitepager_write(pPage); + if( rc ) return rc; + rc = allocatePage(pBt, &pChild, &pgnoChild, sqlitepager_pagenumber(pPage)); + if( rc ) return rc; + assert( sqlitepager_iswriteable(pChild) ); + copyPage(pChild, pPage); + pChild->pParent = pPage; + pChild->idxParent = 0; + sqlitepager_ref(pPage); + pChild->isOverfull = 1; + if( pCur && pCur->pPage==pPage ){ + sqlitepager_unref(pPage); + pCur->pPage = pChild; + }else{ + extraUnref = pChild; + } + zeroPage(pBt, pPage); + pPage->u.hdr.rightChild = SWAB32(pBt, pgnoChild); + pParent = pPage; + pPage = pChild; + } + rc = sqlitepager_write(pParent); + if( rc ) return rc; + assert( pParent->isInit ); + + /* + ** Find the Cell in the parent page whose h.leftChild points back + ** to pPage. The "idx" variable is the index of that cell. If pPage + ** is the rightmost child of pParent then set idx to pParent->nCell + */ + if( pParent->idxShift ){ + Pgno pgno, swabPgno; + pgno = sqlitepager_pagenumber(pPage); + swabPgno = SWAB32(pBt, pgno); + for(idx=0; idxnCell; idx++){ + if( pParent->apCell[idx]->h.leftChild==swabPgno ){ + break; + } + } + assert( idxnCell || pParent->u.hdr.rightChild==swabPgno ); + }else{ + idx = pPage->idxParent; + } + + /* + ** Initialize variables so that it will be safe to jump + ** directly to balance_cleanup at any moment. + */ + nOld = nNew = 0; + sqlitepager_ref(pParent); + + /* + ** Find sibling pages to pPage and the Cells in pParent that divide + ** the siblings. An attempt is made to find NN siblings on either + ** side of pPage. More siblings are taken from one side, however, if + ** pPage there are fewer than NN siblings on the other side. If pParent + ** has NB or fewer children then all children of pParent are taken. + */ + nxDiv = idx - NN; + if( nxDiv + NB > pParent->nCell ){ + nxDiv = pParent->nCell - NB + 1; + } + if( nxDiv<0 ){ + nxDiv = 0; + } + nDiv = 0; + for(i=0, k=nxDiv; inCell ){ + idxDiv[i] = k; + apDiv[i] = pParent->apCell[k]; + nDiv++; + pgnoOld[i] = SWAB32(pBt, apDiv[i]->h.leftChild); + }else if( k==pParent->nCell ){ + pgnoOld[i] = SWAB32(pBt, pParent->u.hdr.rightChild); + }else{ + break; + } + rc = sqlitepager_get(pBt->pPager, pgnoOld[i], (void**)&apOld[i]); + if( rc ) goto balance_cleanup; + rc = initPage(pBt, apOld[i], pgnoOld[i], pParent); + if( rc ) goto balance_cleanup; + apOld[i]->idxParent = k; + nOld++; + } + + /* + ** Set iCur to be the index in apCell[] of the cell that the cursor + ** is pointing to. We will need this later on in order to keep the + ** cursor pointing at the same cell. If pCur points to a page that + ** has no involvement with this rebalancing, then set iCur to a large + ** number so that the iCur==j tests always fail in the main cell + ** distribution loop below. + */ + if( pCur ){ + iCur = 0; + for(i=0; ipPage==apOld[i] ){ + iCur += pCur->idx; + break; + } + iCur += apOld[i]->nCell; + if( ipPage==pParent && pCur->idx==idxDiv[i] ){ + break; + } + iCur++; + } + pOldCurPage = pCur->pPage; + } + + /* + ** Make copies of the content of pPage and its siblings into aOld[]. + ** The rest of this function will use data from the copies rather + ** that the original pages since the original pages will be in the + ** process of being overwritten. + */ + for(i=0; inCell; j++){ + apCell[nCell] = pOld->apCell[j]; + szCell[nCell] = cellSize(pBt, apCell[nCell]); + nCell++; + } + if( ih.leftChild)==pgnoOld[i] ); + apCell[nCell]->h.leftChild = pOld->u.hdr.rightChild; + nCell++; + } + } + + /* + ** Figure out the number of pages needed to hold all nCell cells. + ** Store this number in "k". Also compute szNew[] which is the total + ** size of all cells on the i-th page and cntNew[] which is the index + ** in apCell[] of the cell that divides path i from path i+1. + ** cntNew[k] should equal nCell. + ** + ** This little patch of code is critical for keeping the tree + ** balanced. + */ + for(subtotal=k=i=0; i USABLE_SPACE ){ + szNew[k] = subtotal - szCell[i]; + cntNew[k] = i; + subtotal = 0; + k++; + } + } + szNew[k] = subtotal; + cntNew[k] = nCell; + k++; + for(i=k-1; i>0; i--){ + while( szNew[i]0 ); + szNew[i] += szCell[cntNew[i-1]]; + szNew[i-1] -= szCell[cntNew[i-1]-1]; + } + } + assert( cntNew[0]>0 ); + + /* + ** Allocate k new pages. Reuse old pages where possible. + */ + for(i=0; iisInit = 1; + } + + /* Free any old pages that were not reused as new pages. + */ + while( ii ){ + int t; + MemPage *pT; + t = pgnoNew[i]; + pT = apNew[i]; + pgnoNew[i] = pgnoNew[minI]; + apNew[i] = apNew[minI]; + pgnoNew[minI] = t; + apNew[minI] = pT; + } + } + + /* + ** Evenly distribute the data in apCell[] across the new pages. + ** Insert divider cells into pParent as necessary. + */ + j = 0; + for(i=0; inFree>=szCell[j] ); + if( pCur && iCur==j ){ pCur->pPage = pNew; pCur->idx = pNew->nCell; } + insertCell(pBt, pNew, pNew->nCell, apCell[j], szCell[j]); + j++; + } + assert( pNew->nCell>0 ); + assert( !pNew->isOverfull ); + relinkCellList(pBt, pNew); + if( iu.hdr.rightChild = apCell[j]->h.leftChild; + apCell[j]->h.leftChild = SWAB32(pBt, pgnoNew[i]); + if( pCur && iCur==j ){ pCur->pPage = pParent; pCur->idx = nxDiv; } + insertCell(pBt, pParent, nxDiv, apCell[j], szCell[j]); + j++; + nxDiv++; + } + } + assert( j==nCell ); + apNew[nNew-1]->u.hdr.rightChild = aOld[nOld-1].u.hdr.rightChild; + if( nxDiv==pParent->nCell ){ + pParent->u.hdr.rightChild = SWAB32(pBt, pgnoNew[nNew-1]); + }else{ + pParent->apCell[nxDiv]->h.leftChild = SWAB32(pBt, pgnoNew[nNew-1]); + } + if( pCur ){ + if( j<=iCur && pCur->pPage==pParent && pCur->idx>idxDiv[nOld-1] ){ + assert( pCur->pPage==pOldCurPage ); + pCur->idx += nNew - nOld; + }else{ + assert( pOldCurPage!=0 ); + sqlitepager_ref(pCur->pPage); + sqlitepager_unref(pOldCurPage); + } + } + + /* + ** Reparent children of all cells. + */ + for(i=0; ipPage==0 ){ + pCur->pPage = pParent; + pCur->idx = 0; + }else{ + sqlitepager_unref(pParent); + } + return rc; +} + +/* +** This routine checks all cursors that point to the same table +** as pCur points to. If any of those cursors were opened with +** wrFlag==0 then this routine returns SQLITE_LOCKED. If all +** cursors point to the same table were opened with wrFlag==1 +** then this routine returns SQLITE_OK. +** +** In addition to checking for read-locks (where a read-lock +** means a cursor opened with wrFlag==0) this routine also moves +** all cursors other than pCur so that they are pointing to the +** first Cell on root page. This is necessary because an insert +** or delete might change the number of cells on a page or delete +** a page entirely and we do not want to leave any cursors +** pointing to non-existant pages or cells. +*/ +static int checkReadLocks(BtCursor *pCur){ + BtCursor *p; + assert( pCur->wrFlag ); + for(p=pCur->pShared; p!=pCur; p=p->pShared){ + assert( p ); + assert( p->pgnoRoot==pCur->pgnoRoot ); + if( p->wrFlag==0 ) return SQLITE_LOCKED; + if( sqlitepager_pagenumber(p->pPage)!=p->pgnoRoot ){ + moveToRoot(p); + } + } + return SQLITE_OK; +} + +/* +** Insert a new record into the BTree. The key is given by (pKey,nKey) +** and the data is given by (pData,nData). The cursor is used only to +** define what database the record should be inserted into. The cursor +** is left pointing at the new record. +*/ +static int fileBtreeInsert( + BtCursor *pCur, /* Insert data into the table of this cursor */ + const void *pKey, int nKey, /* The key of the new record */ + const void *pData, int nData /* The data of the new record */ +){ + Cell newCell; + int rc; + int loc; + int szNew; + MemPage *pPage; + Btree *pBt = pCur->pBt; + + if( pCur->pPage==0 ){ + return SQLITE_ABORT; /* A rollback destroyed this cursor */ + } + if( !pBt->inTrans || nKey+nData==0 ){ + /* Must start a transaction before doing an insert */ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + assert( !pBt->readOnly ); + if( !pCur->wrFlag ){ + return SQLITE_PERM; /* Cursor not open for writing */ + } + if( checkReadLocks(pCur) ){ + return SQLITE_LOCKED; /* The table pCur points to has a read lock */ + } + rc = fileBtreeMoveto(pCur, pKey, nKey, &loc); + if( rc ) return rc; + pPage = pCur->pPage; + assert( pPage->isInit ); + rc = sqlitepager_write(pPage); + if( rc ) return rc; + rc = fillInCell(pBt, &newCell, pKey, nKey, pData, nData); + if( rc ) return rc; + szNew = cellSize(pBt, &newCell); + if( loc==0 ){ + newCell.h.leftChild = pPage->apCell[pCur->idx]->h.leftChild; + rc = clearCell(pBt, pPage->apCell[pCur->idx]); + if( rc ) return rc; + dropCell(pBt, pPage, pCur->idx, cellSize(pBt, pPage->apCell[pCur->idx])); + }else if( loc<0 && pPage->nCell>0 ){ + assert( pPage->u.hdr.rightChild==0 ); /* Must be a leaf page */ + pCur->idx++; + }else{ + assert( pPage->u.hdr.rightChild==0 ); /* Must be a leaf page */ + } + insertCell(pBt, pPage, pCur->idx, &newCell, szNew); + rc = balance(pCur->pBt, pPage, pCur); + /* sqliteBtreePageDump(pCur->pBt, pCur->pgnoRoot, 1); */ + /* fflush(stdout); */ + pCur->eSkip = SKIP_INVALID; + return rc; +} + +/* +** Delete the entry that the cursor is pointing to. +** +** The cursor is left pointing at either the next or the previous +** entry. If the cursor is left pointing to the next entry, then +** the pCur->eSkip flag is set to SKIP_NEXT which forces the next call to +** sqliteBtreeNext() to be a no-op. That way, you can always call +** sqliteBtreeNext() after a delete and the cursor will be left +** pointing to the first entry after the deleted entry. Similarly, +** pCur->eSkip is set to SKIP_PREV is the cursor is left pointing to +** the entry prior to the deleted entry so that a subsequent call to +** sqliteBtreePrevious() will always leave the cursor pointing at the +** entry immediately before the one that was deleted. +*/ +static int fileBtreeDelete(BtCursor *pCur){ + MemPage *pPage = pCur->pPage; + Cell *pCell; + int rc; + Pgno pgnoChild; + Btree *pBt = pCur->pBt; + + assert( pPage->isInit ); + if( pCur->pPage==0 ){ + return SQLITE_ABORT; /* A rollback destroyed this cursor */ + } + if( !pBt->inTrans ){ + /* Must start a transaction before doing a delete */ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + assert( !pBt->readOnly ); + if( pCur->idx >= pPage->nCell ){ + return SQLITE_ERROR; /* The cursor is not pointing to anything */ + } + if( !pCur->wrFlag ){ + return SQLITE_PERM; /* Did not open this cursor for writing */ + } + if( checkReadLocks(pCur) ){ + return SQLITE_LOCKED; /* The table pCur points to has a read lock */ + } + rc = sqlitepager_write(pPage); + if( rc ) return rc; + pCell = pPage->apCell[pCur->idx]; + pgnoChild = SWAB32(pBt, pCell->h.leftChild); + clearCell(pBt, pCell); + if( pgnoChild ){ + /* + ** The entry we are about to delete is not a leaf so if we do not + ** do something we will leave a hole on an internal page. + ** We have to fill the hole by moving in a cell from a leaf. The + ** next Cell after the one to be deleted is guaranteed to exist and + ** to be a leaf so we can use it. + */ + BtCursor leafCur; + Cell *pNext; + int szNext; + int notUsed; + getTempCursor(pCur, &leafCur); + rc = fileBtreeNext(&leafCur, ¬Used); + if( rc!=SQLITE_OK ){ + if( rc!=SQLITE_NOMEM ) rc = SQLITE_CORRUPT; + return rc; + } + rc = sqlitepager_write(leafCur.pPage); + if( rc ) return rc; + dropCell(pBt, pPage, pCur->idx, cellSize(pBt, pCell)); + pNext = leafCur.pPage->apCell[leafCur.idx]; + szNext = cellSize(pBt, pNext); + pNext->h.leftChild = SWAB32(pBt, pgnoChild); + insertCell(pBt, pPage, pCur->idx, pNext, szNext); + rc = balance(pBt, pPage, pCur); + if( rc ) return rc; + pCur->eSkip = SKIP_NEXT; + dropCell(pBt, leafCur.pPage, leafCur.idx, szNext); + rc = balance(pBt, leafCur.pPage, pCur); + releaseTempCursor(&leafCur); + }else{ + dropCell(pBt, pPage, pCur->idx, cellSize(pBt, pCell)); + if( pCur->idx>=pPage->nCell ){ + pCur->idx = pPage->nCell-1; + if( pCur->idx<0 ){ + pCur->idx = 0; + pCur->eSkip = SKIP_NEXT; + }else{ + pCur->eSkip = SKIP_PREV; + } + }else{ + pCur->eSkip = SKIP_NEXT; + } + rc = balance(pBt, pPage, pCur); + } + return rc; +} + +/* +** Create a new BTree table. Write into *piTable the page +** number for the root page of the new table. +** +** In the current implementation, BTree tables and BTree indices are the +** the same. In the future, we may change this so that BTree tables +** are restricted to having a 4-byte integer key and arbitrary data and +** BTree indices are restricted to having an arbitrary key and no data. +** But for now, this routine also serves to create indices. +*/ +static int fileBtreeCreateTable(Btree *pBt, int *piTable){ + MemPage *pRoot; + Pgno pgnoRoot; + int rc; + if( !pBt->inTrans ){ + /* Must start a transaction first */ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + if( pBt->readOnly ){ + return SQLITE_READONLY; + } + rc = allocatePage(pBt, &pRoot, &pgnoRoot, 0); + if( rc ) return rc; + assert( sqlitepager_iswriteable(pRoot) ); + zeroPage(pBt, pRoot); + sqlitepager_unref(pRoot); + *piTable = (int)pgnoRoot; + return SQLITE_OK; +} + +/* +** Erase the given database page and all its children. Return +** the page to the freelist. +*/ +static int clearDatabasePage(Btree *pBt, Pgno pgno, int freePageFlag){ + MemPage *pPage; + int rc; + Cell *pCell; + int idx; + + rc = sqlitepager_get(pBt->pPager, pgno, (void**)&pPage); + if( rc ) return rc; + rc = sqlitepager_write(pPage); + if( rc ) return rc; + rc = initPage(pBt, pPage, pgno, 0); + if( rc ) return rc; + idx = SWAB16(pBt, pPage->u.hdr.firstCell); + while( idx>0 ){ + pCell = (Cell*)&pPage->u.aDisk[idx]; + idx = SWAB16(pBt, pCell->h.iNext); + if( pCell->h.leftChild ){ + rc = clearDatabasePage(pBt, SWAB32(pBt, pCell->h.leftChild), 1); + if( rc ) return rc; + } + rc = clearCell(pBt, pCell); + if( rc ) return rc; + } + if( pPage->u.hdr.rightChild ){ + rc = clearDatabasePage(pBt, SWAB32(pBt, pPage->u.hdr.rightChild), 1); + if( rc ) return rc; + } + if( freePageFlag ){ + rc = freePage(pBt, pPage, pgno); + }else{ + zeroPage(pBt, pPage); + } + sqlitepager_unref(pPage); + return rc; +} + +/* +** Delete all information from a single table in the database. +*/ +static int fileBtreeClearTable(Btree *pBt, int iTable){ + int rc; + BtCursor *pCur; + if( !pBt->inTrans ){ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ + if( pCur->pgnoRoot==(Pgno)iTable ){ + if( pCur->wrFlag==0 ) return SQLITE_LOCKED; + moveToRoot(pCur); + } + } + rc = clearDatabasePage(pBt, (Pgno)iTable, 0); + if( rc ){ + fileBtreeRollback(pBt); + } + return rc; +} + +/* +** Erase all information in a table and add the root of the table to +** the freelist. Except, the root of the principle table (the one on +** page 2) is never added to the freelist. +*/ +static int fileBtreeDropTable(Btree *pBt, int iTable){ + int rc; + MemPage *pPage; + BtCursor *pCur; + if( !pBt->inTrans ){ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ + if( pCur->pgnoRoot==(Pgno)iTable ){ + return SQLITE_LOCKED; /* Cannot drop a table that has a cursor */ + } + } + rc = sqlitepager_get(pBt->pPager, (Pgno)iTable, (void**)&pPage); + if( rc ) return rc; + rc = fileBtreeClearTable(pBt, iTable); + if( rc ) return rc; + if( iTable>2 ){ + rc = freePage(pBt, pPage, iTable); + }else{ + zeroPage(pBt, pPage); + } + sqlitepager_unref(pPage); + return rc; +} + +#if 0 /* UNTESTED */ +/* +** Copy all cell data from one database file into another. +** pages back the freelist. +*/ +static int copyCell(Btree *pBtFrom, BTree *pBtTo, Cell *pCell){ + Pager *pFromPager = pBtFrom->pPager; + OverflowPage *pOvfl; + Pgno ovfl, nextOvfl; + Pgno *pPrev; + int rc = SQLITE_OK; + MemPage *pNew, *pPrevPg; + Pgno new; + + if( NKEY(pBtTo, pCell->h) + NDATA(pBtTo, pCell->h) <= MX_LOCAL_PAYLOAD ){ + return SQLITE_OK; + } + pPrev = &pCell->ovfl; + pPrevPg = 0; + ovfl = SWAB32(pBtTo, pCell->ovfl); + while( ovfl && rc==SQLITE_OK ){ + rc = sqlitepager_get(pFromPager, ovfl, (void**)&pOvfl); + if( rc ) return rc; + nextOvfl = SWAB32(pBtFrom, pOvfl->iNext); + rc = allocatePage(pBtTo, &pNew, &new, 0); + if( rc==SQLITE_OK ){ + rc = sqlitepager_write(pNew); + if( rc==SQLITE_OK ){ + memcpy(pNew, pOvfl, SQLITE_USABLE_SIZE); + *pPrev = SWAB32(pBtTo, new); + if( pPrevPg ){ + sqlitepager_unref(pPrevPg); + } + pPrev = &pOvfl->iNext; + pPrevPg = pNew; + } + } + sqlitepager_unref(pOvfl); + ovfl = nextOvfl; + } + if( pPrevPg ){ + sqlitepager_unref(pPrevPg); + } + return rc; +} +#endif + + +#if 0 /* UNTESTED */ +/* +** Copy a page of data from one database over to another. +*/ +static int copyDatabasePage( + Btree *pBtFrom, + Pgno pgnoFrom, + Btree *pBtTo, + Pgno *pTo +){ + MemPage *pPageFrom, *pPage; + Pgno to; + int rc; + Cell *pCell; + int idx; + + rc = sqlitepager_get(pBtFrom->pPager, pgno, (void**)&pPageFrom); + if( rc ) return rc; + rc = allocatePage(pBt, &pPage, pTo, 0); + if( rc==SQLITE_OK ){ + rc = sqlitepager_write(pPage); + } + if( rc==SQLITE_OK ){ + memcpy(pPage, pPageFrom, SQLITE_USABLE_SIZE); + idx = SWAB16(pBt, pPage->u.hdr.firstCell); + while( idx>0 ){ + pCell = (Cell*)&pPage->u.aDisk[idx]; + idx = SWAB16(pBt, pCell->h.iNext); + if( pCell->h.leftChild ){ + Pgno newChld; + rc = copyDatabasePage(pBtFrom, SWAB32(pBtFrom, pCell->h.leftChild), + pBtTo, &newChld); + if( rc ) return rc; + pCell->h.leftChild = SWAB32(pBtFrom, newChld); + } + rc = copyCell(pBtFrom, pBtTo, pCell); + if( rc ) return rc; + } + if( pPage->u.hdr.rightChild ){ + Pgno newChld; + rc = copyDatabasePage(pBtFrom, SWAB32(pBtFrom, pPage->u.hdr.rightChild), + pBtTo, &newChld); + if( rc ) return rc; + pPage->u.hdr.rightChild = SWAB32(pBtTo, newChild); + } + } + sqlitepager_unref(pPage); + return rc; +} +#endif + +/* +** Read the meta-information out of a database file. +*/ +static int fileBtreeGetMeta(Btree *pBt, int *aMeta){ + PageOne *pP1; + int rc; + int i; + + rc = sqlitepager_get(pBt->pPager, 1, (void**)&pP1); + if( rc ) return rc; + aMeta[0] = SWAB32(pBt, pP1->nFree); + for(i=0; iaMeta)/sizeof(pP1->aMeta[0]); i++){ + aMeta[i+1] = SWAB32(pBt, pP1->aMeta[i]); + } + sqlitepager_unref(pP1); + return SQLITE_OK; +} + +/* +** Write meta-information back into the database. +*/ +static int fileBtreeUpdateMeta(Btree *pBt, int *aMeta){ + PageOne *pP1; + int rc, i; + if( !pBt->inTrans ){ + return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR; + } + pP1 = pBt->page1; + rc = sqlitepager_write(pP1); + if( rc ) return rc; + for(i=0; iaMeta)/sizeof(pP1->aMeta[0]); i++){ + pP1->aMeta[i] = SWAB32(pBt, aMeta[i+1]); + } + return SQLITE_OK; +} + +/****************************************************************************** +** The complete implementation of the BTree subsystem is above this line. +** All the code the follows is for testing and troubleshooting the BTree +** subsystem. None of the code that follows is used during normal operation. +******************************************************************************/ + +/* +** Print a disassembly of the given page on standard output. This routine +** is used for debugging and testing only. +*/ +#ifdef SQLITE_TEST +static int fileBtreePageDump(Btree *pBt, int pgno, int recursive){ + int rc; + MemPage *pPage; + int i, j; + int nFree; + u16 idx; + char range[20]; + unsigned char payload[20]; + rc = sqlitepager_get(pBt->pPager, (Pgno)pgno, (void**)&pPage); + if( rc ){ + return rc; + } + if( recursive ) printf("PAGE %d:\n", pgno); + i = 0; + idx = SWAB16(pBt, pPage->u.hdr.firstCell); + while( idx>0 && idx<=SQLITE_USABLE_SIZE-MIN_CELL_SIZE ){ + Cell *pCell = (Cell*)&pPage->u.aDisk[idx]; + int sz = cellSize(pBt, pCell); + sprintf(range,"%d..%d", idx, idx+sz-1); + sz = NKEY(pBt, pCell->h) + NDATA(pBt, pCell->h); + if( sz>sizeof(payload)-1 ) sz = sizeof(payload)-1; + memcpy(payload, pCell->aPayload, sz); + for(j=0; j0x7f ) payload[j] = '.'; + } + payload[sz] = 0; + printf( + "cell %2d: i=%-10s chld=%-4d nk=%-4d nd=%-4d payload=%s\n", + i, range, (int)pCell->h.leftChild, + NKEY(pBt, pCell->h), NDATA(pBt, pCell->h), + payload + ); + if( pPage->isInit && pPage->apCell[i]!=pCell ){ + printf("**** apCell[%d] does not match on prior entry ****\n", i); + } + i++; + idx = SWAB16(pBt, pCell->h.iNext); + } + if( idx!=0 ){ + printf("ERROR: next cell index out of range: %d\n", idx); + } + printf("right_child: %d\n", SWAB32(pBt, pPage->u.hdr.rightChild)); + nFree = 0; + i = 0; + idx = SWAB16(pBt, pPage->u.hdr.firstFree); + while( idx>0 && idxu.aDisk[idx]; + sprintf(range,"%d..%d", idx, idx+p->iSize-1); + nFree += SWAB16(pBt, p->iSize); + printf("freeblock %2d: i=%-10s size=%-4d total=%d\n", + i, range, SWAB16(pBt, p->iSize), nFree); + idx = SWAB16(pBt, p->iNext); + i++; + } + if( idx!=0 ){ + printf("ERROR: next freeblock index out of range: %d\n", idx); + } + if( recursive && pPage->u.hdr.rightChild!=0 ){ + idx = SWAB16(pBt, pPage->u.hdr.firstCell); + while( idx>0 && idxu.aDisk[idx]; + fileBtreePageDump(pBt, SWAB32(pBt, pCell->h.leftChild), 1); + idx = SWAB16(pBt, pCell->h.iNext); + } + fileBtreePageDump(pBt, SWAB32(pBt, pPage->u.hdr.rightChild), 1); + } + sqlitepager_unref(pPage); + return SQLITE_OK; +} +#endif + +#ifdef SQLITE_TEST +/* +** Fill aResult[] with information about the entry and page that the +** cursor is pointing to. +** +** aResult[0] = The page number +** aResult[1] = The entry number +** aResult[2] = Total number of entries on this page +** aResult[3] = Size of this entry +** aResult[4] = Number of free bytes on this page +** aResult[5] = Number of free blocks on the page +** aResult[6] = Page number of the left child of this entry +** aResult[7] = Page number of the right child for the whole page +** +** This routine is used for testing and debugging only. +*/ +static int fileBtreeCursorDump(BtCursor *pCur, int *aResult){ + int cnt, idx; + MemPage *pPage = pCur->pPage; + Btree *pBt = pCur->pBt; + aResult[0] = sqlitepager_pagenumber(pPage); + aResult[1] = pCur->idx; + aResult[2] = pPage->nCell; + if( pCur->idx>=0 && pCur->idxnCell ){ + aResult[3] = cellSize(pBt, pPage->apCell[pCur->idx]); + aResult[6] = SWAB32(pBt, pPage->apCell[pCur->idx]->h.leftChild); + }else{ + aResult[3] = 0; + aResult[6] = 0; + } + aResult[4] = pPage->nFree; + cnt = 0; + idx = SWAB16(pBt, pPage->u.hdr.firstFree); + while( idx>0 && idxu.aDisk[idx])->iNext); + } + aResult[5] = cnt; + aResult[7] = SWAB32(pBt, pPage->u.hdr.rightChild); + return SQLITE_OK; +} +#endif + +/* +** Return the pager associated with a BTree. This routine is used for +** testing and debugging only. +*/ +static Pager *fileBtreePager(Btree *pBt){ + return pBt->pPager; +} + +/* +** This structure is passed around through all the sanity checking routines +** in order to keep track of some global state information. +*/ +typedef struct IntegrityCk IntegrityCk; +struct IntegrityCk { + Btree *pBt; /* The tree being checked out */ + Pager *pPager; /* The associated pager. Also accessible by pBt->pPager */ + int nPage; /* Number of pages in the database */ + int *anRef; /* Number of times each page is referenced */ + char *zErrMsg; /* An error message. NULL of no errors seen. */ +}; + +/* +** Append a message to the error message string. +*/ +static void checkAppendMsg(IntegrityCk *pCheck, char *zMsg1, char *zMsg2){ + if( pCheck->zErrMsg ){ + char *zOld = pCheck->zErrMsg; + pCheck->zErrMsg = 0; + sqliteSetString(&pCheck->zErrMsg, zOld, "\n", zMsg1, zMsg2, (char*)0); + sqliteFree(zOld); + }else{ + sqliteSetString(&pCheck->zErrMsg, zMsg1, zMsg2, (char*)0); + } +} + +/* +** Add 1 to the reference count for page iPage. If this is the second +** reference to the page, add an error message to pCheck->zErrMsg. +** Return 1 if there are 2 ore more references to the page and 0 if +** if this is the first reference to the page. +** +** Also check that the page number is in bounds. +*/ +static int checkRef(IntegrityCk *pCheck, int iPage, char *zContext){ + if( iPage==0 ) return 1; + if( iPage>pCheck->nPage || iPage<0 ){ + char zBuf[100]; + sprintf(zBuf, "invalid page number %d", iPage); + checkAppendMsg(pCheck, zContext, zBuf); + return 1; + } + if( pCheck->anRef[iPage]==1 ){ + char zBuf[100]; + sprintf(zBuf, "2nd reference to page %d", iPage); + checkAppendMsg(pCheck, zContext, zBuf); + return 1; + } + return (pCheck->anRef[iPage]++)>1; +} + +/* +** Check the integrity of the freelist or of an overflow page list. +** Verify that the number of pages on the list is N. +*/ +static void checkList( + IntegrityCk *pCheck, /* Integrity checking context */ + int isFreeList, /* True for a freelist. False for overflow page list */ + int iPage, /* Page number for first page in the list */ + int N, /* Expected number of pages in the list */ + char *zContext /* Context for error messages */ +){ + int i; + char zMsg[100]; + while( N-- > 0 ){ + OverflowPage *pOvfl; + if( iPage<1 ){ + sprintf(zMsg, "%d pages missing from overflow list", N+1); + checkAppendMsg(pCheck, zContext, zMsg); + break; + } + if( checkRef(pCheck, iPage, zContext) ) break; + if( sqlitepager_get(pCheck->pPager, (Pgno)iPage, (void**)&pOvfl) ){ + sprintf(zMsg, "failed to get page %d", iPage); + checkAppendMsg(pCheck, zContext, zMsg); + break; + } + if( isFreeList ){ + FreelistInfo *pInfo = (FreelistInfo*)pOvfl->aPayload; + int n = SWAB32(pCheck->pBt, pInfo->nFree); + for(i=0; ipBt, pInfo->aFree[i]), zContext); + } + N -= n; + } + iPage = SWAB32(pCheck->pBt, pOvfl->iNext); + sqlitepager_unref(pOvfl); + } +} + +/* +** Return negative if zKey1zKey2. +*/ +static int keyCompare( + const char *zKey1, int nKey1, + const char *zKey2, int nKey2 +){ + int min = nKey1>nKey2 ? nKey2 : nKey1; + int c = memcmp(zKey1, zKey2, min); + if( c==0 ){ + c = nKey1 - nKey2; + } + return c; +} + +/* +** Do various sanity checks on a single page of a tree. Return +** the tree depth. Root pages return 0. Parents of root pages +** return 1, and so forth. +** +** These checks are done: +** +** 1. Make sure that cells and freeblocks do not overlap +** but combine to completely cover the page. +** 2. Make sure cell keys are in order. +** 3. Make sure no key is less than or equal to zLowerBound. +** 4. Make sure no key is greater than or equal to zUpperBound. +** 5. Check the integrity of overflow pages. +** 6. Recursively call checkTreePage on all children. +** 7. Verify that the depth of all children is the same. +** 8. Make sure this page is at least 33% full or else it is +** the root of the tree. +*/ +static int checkTreePage( + IntegrityCk *pCheck, /* Context for the sanity check */ + int iPage, /* Page number of the page to check */ + MemPage *pParent, /* Parent page */ + char *zParentContext, /* Parent context */ + char *zLowerBound, /* All keys should be greater than this, if not NULL */ + int nLower, /* Number of characters in zLowerBound */ + char *zUpperBound, /* All keys should be less than this, if not NULL */ + int nUpper /* Number of characters in zUpperBound */ +){ + MemPage *pPage; + int i, rc, depth, d2, pgno; + char *zKey1, *zKey2; + int nKey1, nKey2; + BtCursor cur; + Btree *pBt; + char zMsg[100]; + char zContext[100]; + char hit[SQLITE_USABLE_SIZE]; + + /* Check that the page exists + */ + cur.pBt = pBt = pCheck->pBt; + if( iPage==0 ) return 0; + if( checkRef(pCheck, iPage, zParentContext) ) return 0; + sprintf(zContext, "On tree page %d: ", iPage); + if( (rc = sqlitepager_get(pCheck->pPager, (Pgno)iPage, (void**)&pPage))!=0 ){ + sprintf(zMsg, "unable to get the page. error code=%d", rc); + checkAppendMsg(pCheck, zContext, zMsg); + return 0; + } + if( (rc = initPage(pBt, pPage, (Pgno)iPage, pParent))!=0 ){ + sprintf(zMsg, "initPage() returns error code %d", rc); + checkAppendMsg(pCheck, zContext, zMsg); + sqlitepager_unref(pPage); + return 0; + } + + /* Check out all the cells. + */ + depth = 0; + if( zLowerBound ){ + zKey1 = sqliteMalloc( nLower+1 ); + memcpy(zKey1, zLowerBound, nLower); + zKey1[nLower] = 0; + }else{ + zKey1 = 0; + } + nKey1 = nLower; + cur.pPage = pPage; + for(i=0; inCell; i++){ + Cell *pCell = pPage->apCell[i]; + int sz; + + /* Check payload overflow pages + */ + nKey2 = NKEY(pBt, pCell->h); + sz = nKey2 + NDATA(pBt, pCell->h); + sprintf(zContext, "On page %d cell %d: ", iPage, i); + if( sz>MX_LOCAL_PAYLOAD ){ + int nPage = (sz - MX_LOCAL_PAYLOAD + OVERFLOW_SIZE - 1)/OVERFLOW_SIZE; + checkList(pCheck, 0, SWAB32(pBt, pCell->ovfl), nPage, zContext); + } + + /* Check that keys are in the right order + */ + cur.idx = i; + zKey2 = sqliteMallocRaw( nKey2+1 ); + getPayload(&cur, 0, nKey2, zKey2); + if( zKey1 && keyCompare(zKey1, nKey1, zKey2, nKey2)>=0 ){ + checkAppendMsg(pCheck, zContext, "Key is out of order"); + } + + /* Check sanity of left child page. + */ + pgno = SWAB32(pBt, pCell->h.leftChild); + d2 = checkTreePage(pCheck, pgno, pPage, zContext, zKey1,nKey1,zKey2,nKey2); + if( i>0 && d2!=depth ){ + checkAppendMsg(pCheck, zContext, "Child page depth differs"); + } + depth = d2; + sqliteFree(zKey1); + zKey1 = zKey2; + nKey1 = nKey2; + } + pgno = SWAB32(pBt, pPage->u.hdr.rightChild); + sprintf(zContext, "On page %d at right child: ", iPage); + checkTreePage(pCheck, pgno, pPage, zContext, zKey1,nKey1,zUpperBound,nUpper); + sqliteFree(zKey1); + + /* Check for complete coverage of the page + */ + memset(hit, 0, sizeof(hit)); + memset(hit, 1, sizeof(PageHdr)); + for(i=SWAB16(pBt, pPage->u.hdr.firstCell); i>0 && iu.aDisk[i]; + int j; + for(j=i+cellSize(pBt, pCell)-1; j>=i; j--) hit[j]++; + i = SWAB16(pBt, pCell->h.iNext); + } + for(i=SWAB16(pBt,pPage->u.hdr.firstFree); i>0 && iu.aDisk[i]; + int j; + for(j=i+SWAB16(pBt,pFBlk->iSize)-1; j>=i; j--) hit[j]++; + i = SWAB16(pBt,pFBlk->iNext); + } + for(i=0; i1 ){ + sprintf(zMsg, "Multiple uses for byte %d of page %d", i, iPage); + checkAppendMsg(pCheck, zMsg, 0); + break; + } + } + + /* Check that free space is kept to a minimum + */ +#if 0 + if( pParent && pParent->nCell>2 && pPage->nFree>3*SQLITE_USABLE_SIZE/4 ){ + sprintf(zMsg, "free space (%d) greater than max (%d)", pPage->nFree, + SQLITE_USABLE_SIZE/3); + checkAppendMsg(pCheck, zContext, zMsg); + } +#endif + + sqlitepager_unref(pPage); + return depth; +} + +/* +** This routine does a complete check of the given BTree file. aRoot[] is +** an array of pages numbers were each page number is the root page of +** a table. nRoot is the number of entries in aRoot. +** +** If everything checks out, this routine returns NULL. If something is +** amiss, an error message is written into memory obtained from malloc() +** and a pointer to that error message is returned. The calling function +** is responsible for freeing the error message when it is done. +*/ +char *fileBtreeIntegrityCheck(Btree *pBt, int *aRoot, int nRoot){ + int i; + int nRef; + IntegrityCk sCheck; + + nRef = *sqlitepager_stats(pBt->pPager); + if( lockBtree(pBt)!=SQLITE_OK ){ + return sqliteStrDup("Unable to acquire a read lock on the database"); + } + sCheck.pBt = pBt; + sCheck.pPager = pBt->pPager; + sCheck.nPage = sqlitepager_pagecount(sCheck.pPager); + if( sCheck.nPage==0 ){ + unlockBtreeIfUnused(pBt); + return 0; + } + sCheck.anRef = sqliteMallocRaw( (sCheck.nPage+1)*sizeof(sCheck.anRef[0]) ); + sCheck.anRef[1] = 1; + for(i=2; i<=sCheck.nPage; i++){ sCheck.anRef[i] = 0; } + sCheck.zErrMsg = 0; + + /* Check the integrity of the freelist + */ + checkList(&sCheck, 1, SWAB32(pBt, pBt->page1->freeList), + SWAB32(pBt, pBt->page1->nFree), "Main freelist: "); + + /* Check all the tables. + */ + for(i=0; ipPager) ){ + char zBuf[100]; + sprintf(zBuf, + "Outstanding page count goes from %d to %d during this analysis", + nRef, *sqlitepager_stats(pBt->pPager) + ); + checkAppendMsg(&sCheck, zBuf, 0); + } + + /* Clean up and report errors. + */ + sqliteFree(sCheck.anRef); + return sCheck.zErrMsg; +} + +/* +** Return the full pathname of the underlying database file. +*/ +static const char *fileBtreeGetFilename(Btree *pBt){ + assert( pBt->pPager!=0 ); + return sqlitepager_filename(pBt->pPager); +} + +/* +** Copy the complete content of pBtFrom into pBtTo. A transaction +** must be active for both files. +** +** The size of file pBtFrom may be reduced by this operation. +** If anything goes wrong, the transaction on pBtFrom is rolled back. +*/ +static int fileBtreeCopyFile(Btree *pBtTo, Btree *pBtFrom){ + int rc = SQLITE_OK; + Pgno i, nPage, nToPage; + + if( !pBtTo->inTrans || !pBtFrom->inTrans ) return SQLITE_ERROR; + if( pBtTo->needSwab!=pBtFrom->needSwab ) return SQLITE_ERROR; + if( pBtTo->pCursor ) return SQLITE_BUSY; + memcpy(pBtTo->page1, pBtFrom->page1, SQLITE_USABLE_SIZE); + rc = sqlitepager_overwrite(pBtTo->pPager, 1, pBtFrom->page1); + nToPage = sqlitepager_pagecount(pBtTo->pPager); + nPage = sqlitepager_pagecount(pBtFrom->pPager); + for(i=2; rc==SQLITE_OK && i<=nPage; i++){ + void *pPage; + rc = sqlitepager_get(pBtFrom->pPager, i, &pPage); + if( rc ) break; + rc = sqlitepager_overwrite(pBtTo->pPager, i, pPage); + if( rc ) break; + sqlitepager_unref(pPage); + } + for(i=nPage+1; rc==SQLITE_OK && i<=nToPage; i++){ + void *pPage; + rc = sqlitepager_get(pBtTo->pPager, i, &pPage); + if( rc ) break; + rc = sqlitepager_write(pPage); + sqlitepager_unref(pPage); + sqlitepager_dont_write(pBtTo->pPager, i); + } + if( !rc && nPagepPager, nPage); + } + if( rc ){ + fileBtreeRollback(pBtTo); + } + return rc; +} + +/* +** The following tables contain pointers to all of the interface +** routines for this implementation of the B*Tree backend. To +** substitute a different implemention of the backend, one has merely +** to provide pointers to alternative functions in similar tables. +*/ +static BtOps sqliteBtreeOps = { + fileBtreeClose, + fileBtreeSetCacheSize, + fileBtreeSetSafetyLevel, + fileBtreeBeginTrans, + fileBtreeCommit, + fileBtreeRollback, + fileBtreeBeginCkpt, + fileBtreeCommitCkpt, + fileBtreeRollbackCkpt, + fileBtreeCreateTable, + fileBtreeCreateTable, /* Really sqliteBtreeCreateIndex() */ + fileBtreeDropTable, + fileBtreeClearTable, + fileBtreeCursor, + fileBtreeGetMeta, + fileBtreeUpdateMeta, + fileBtreeIntegrityCheck, + fileBtreeGetFilename, + fileBtreeCopyFile, + fileBtreePager, +#ifdef SQLITE_TEST + fileBtreePageDump, +#endif +}; +static BtCursorOps sqliteBtreeCursorOps = { + fileBtreeMoveto, + fileBtreeDelete, + fileBtreeInsert, + fileBtreeFirst, + fileBtreeLast, + fileBtreeNext, + fileBtreePrevious, + fileBtreeKeySize, + fileBtreeKey, + fileBtreeKeyCompare, + fileBtreeDataSize, + fileBtreeData, + fileBtreeCloseCursor, +#ifdef SQLITE_TEST + fileBtreeCursorDump, +#endif +}; diff --git a/src/libs/sqlite2/btree.h b/src/libs/sqlite2/btree.h new file mode 100644 index 00000000..5a11b60e --- /dev/null +++ b/src/libs/sqlite2/btree.h @@ -0,0 +1,156 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite B-Tree file +** subsystem. See comments in the source code for a detailed description +** of what each interface routine does. +** +** @(#) $Id: btree.h 326789 2004-07-07 21:25:56Z pahlibar $ +*/ +#ifndef _BTREE_H_ +#define _BTREE_H_ + +/* +** Forward declarations of structure +*/ +typedef struct Btree Btree; +typedef struct BtCursor BtCursor; +typedef struct BtOps BtOps; +typedef struct BtCursorOps BtCursorOps; + + +/* +** An instance of the following structure contains pointers to all +** methods against an open BTree. Alternative BTree implementations +** (examples: file based versus in-memory) can be created by substituting +** different methods. Users of the BTree cannot tell the difference. +** +** In C++ we could do this by defining a virtual base class and then +** creating subclasses for each different implementation. But this is +** C not C++ so we have to be a little more explicit. +*/ +struct BtOps { + int (*Close)(Btree*); + int (*SetCacheSize)(Btree*, int); + int (*SetSafetyLevel)(Btree*, int); + int (*BeginTrans)(Btree*); + int (*Commit)(Btree*); + int (*Rollback)(Btree*); + int (*BeginCkpt)(Btree*); + int (*CommitCkpt)(Btree*); + int (*RollbackCkpt)(Btree*); + int (*CreateTable)(Btree*, int*); + int (*CreateIndex)(Btree*, int*); + int (*DropTable)(Btree*, int); + int (*ClearTable)(Btree*, int); + int (*Cursor)(Btree*, int iTable, int wrFlag, BtCursor **ppCur); + int (*GetMeta)(Btree*, int*); + int (*UpdateMeta)(Btree*, int*); + char *(*IntegrityCheck)(Btree*, int*, int); + const char *(*GetFilename)(Btree*); + int (*Copyfile)(Btree*,Btree*); + struct Pager *(*Pager)(Btree*); +#ifdef SQLITE_TEST + int (*PageDump)(Btree*, int, int); +#endif +}; + +/* +** An instance of this structure defines all of the methods that can +** be executed against a cursor. +*/ +struct BtCursorOps { + int (*Moveto)(BtCursor*, const void *pKey, int nKey, int *pRes); + int (*Delete)(BtCursor*); + int (*Insert)(BtCursor*, const void *pKey, int nKey, + const void *pData, int nData); + int (*First)(BtCursor*, int *pRes); + int (*Last)(BtCursor*, int *pRes); + int (*Next)(BtCursor*, int *pRes); + int (*Previous)(BtCursor*, int *pRes); + int (*KeySize)(BtCursor*, int *pSize); + int (*Key)(BtCursor*, int offset, int amt, char *zBuf); + int (*KeyCompare)(BtCursor*, const void *pKey, int nKey, + int nIgnore, int *pRes); + int (*DataSize)(BtCursor*, int *pSize); + int (*Data)(BtCursor*, int offset, int amt, char *zBuf); + int (*CloseCursor)(BtCursor*); +#ifdef SQLITE_TEST + int (*CursorDump)(BtCursor*, int*); +#endif +}; + +/* +** The number of 4-byte "meta" values contained on the first page of each +** database file. +*/ +#define SQLITE_N_BTREE_META 10 + +int sqliteBtreeOpen(const char *zFilename, int mode, int nPg, Btree **ppBtree); +int sqliteRbtreeOpen(const char *zFilename, int mode, int nPg, Btree **ppBtree); + +#define btOps(pBt) (*((BtOps **)(pBt))) +#define btCOps(pCur) (*((BtCursorOps **)(pCur))) + +#define sqliteBtreeClose(pBt) (btOps(pBt)->Close(pBt)) +#define sqliteBtreeSetCacheSize(pBt, sz) (btOps(pBt)->SetCacheSize(pBt, sz)) +#define sqliteBtreeSetSafetyLevel(pBt, sl) (btOps(pBt)->SetSafetyLevel(pBt, sl)) +#define sqliteBtreeBeginTrans(pBt) (btOps(pBt)->BeginTrans(pBt)) +#define sqliteBtreeCommit(pBt) (btOps(pBt)->Commit(pBt)) +#define sqliteBtreeRollback(pBt) (btOps(pBt)->Rollback(pBt)) +#define sqliteBtreeBeginCkpt(pBt) (btOps(pBt)->BeginCkpt(pBt)) +#define sqliteBtreeCommitCkpt(pBt) (btOps(pBt)->CommitCkpt(pBt)) +#define sqliteBtreeRollbackCkpt(pBt) (btOps(pBt)->RollbackCkpt(pBt)) +#define sqliteBtreeCreateTable(pBt,piTable)\ + (btOps(pBt)->CreateTable(pBt,piTable)) +#define sqliteBtreeCreateIndex(pBt, piIndex)\ + (btOps(pBt)->CreateIndex(pBt, piIndex)) +#define sqliteBtreeDropTable(pBt, iTable) (btOps(pBt)->DropTable(pBt, iTable)) +#define sqliteBtreeClearTable(pBt, iTable)\ + (btOps(pBt)->ClearTable(pBt, iTable)) +#define sqliteBtreeCursor(pBt, iTable, wrFlag, ppCur)\ + (btOps(pBt)->Cursor(pBt, iTable, wrFlag, ppCur)) +#define sqliteBtreeMoveto(pCur, pKey, nKey, pRes)\ + (btCOps(pCur)->Moveto(pCur, pKey, nKey, pRes)) +#define sqliteBtreeDelete(pCur) (btCOps(pCur)->Delete(pCur)) +#define sqliteBtreeInsert(pCur, pKey, nKey, pData, nData) \ + (btCOps(pCur)->Insert(pCur, pKey, nKey, pData, nData)) +#define sqliteBtreeFirst(pCur, pRes) (btCOps(pCur)->First(pCur, pRes)) +#define sqliteBtreeLast(pCur, pRes) (btCOps(pCur)->Last(pCur, pRes)) +#define sqliteBtreeNext(pCur, pRes) (btCOps(pCur)->Next(pCur, pRes)) +#define sqliteBtreePrevious(pCur, pRes) (btCOps(pCur)->Previous(pCur, pRes)) +#define sqliteBtreeKeySize(pCur, pSize) (btCOps(pCur)->KeySize(pCur, pSize) ) +#define sqliteBtreeKey(pCur, offset, amt, zBuf)\ + (btCOps(pCur)->Key(pCur, offset, amt, zBuf)) +#define sqliteBtreeKeyCompare(pCur, pKey, nKey, nIgnore, pRes)\ + (btCOps(pCur)->KeyCompare(pCur, pKey, nKey, nIgnore, pRes)) +#define sqliteBtreeDataSize(pCur, pSize) (btCOps(pCur)->DataSize(pCur, pSize)) +#define sqliteBtreeData(pCur, offset, amt, zBuf)\ + (btCOps(pCur)->Data(pCur, offset, amt, zBuf)) +#define sqliteBtreeCloseCursor(pCur) (btCOps(pCur)->CloseCursor(pCur)) +#define sqliteBtreeGetMeta(pBt, aMeta) (btOps(pBt)->GetMeta(pBt, aMeta)) +#define sqliteBtreeUpdateMeta(pBt, aMeta) (btOps(pBt)->UpdateMeta(pBt, aMeta)) +#define sqliteBtreeIntegrityCheck(pBt, aRoot, nRoot)\ + (btOps(pBt)->IntegrityCheck(pBt, aRoot, nRoot)) +#define sqliteBtreeGetFilename(pBt) (btOps(pBt)->GetFilename(pBt)) +#define sqliteBtreeCopyFile(pBt1, pBt2) (btOps(pBt1)->Copyfile(pBt1, pBt2)) +#define sqliteBtreePager(pBt) (btOps(pBt)->Pager(pBt)) + +#ifdef SQLITE_TEST +#define sqliteBtreePageDump(pBt, pgno, recursive)\ + (btOps(pBt)->PageDump(pBt, pgno, recursive)) +#define sqliteBtreeCursorDump(pCur, aResult)\ + (btCOps(pCur)->CursorDump(pCur, aResult)) +int btree_native_byte_order; +#endif /* SQLITE_TEST */ + + +#endif /* _BTREE_H_ */ diff --git a/src/libs/sqlite2/btree_rb.c b/src/libs/sqlite2/btree_rb.c new file mode 100644 index 00000000..18e49b81 --- /dev/null +++ b/src/libs/sqlite2/btree_rb.c @@ -0,0 +1,1488 @@ +/* +** 2003 Feb 4 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** $Id: btree_rb.c 875429 2008-10-24 12:20:41Z cgilles $ +** +** This file implements an in-core database using Red-Black balanced +** binary trees. +** +** It was contributed to SQLite by anonymous on 2003-Feb-04 23:24:49 UTC. +*/ +#include "btree.h" +#include "sqliteInt.h" +#include + +/* +** Omit this whole file if the SQLITE_OMIT_INMEMORYDB macro is +** defined. This allows a lot of code to be omitted for installations +** that do not need it. +*/ +#ifndef SQLITE_OMIT_INMEMORYDB + + +typedef struct BtRbTree BtRbTree; +typedef struct BtRbNode BtRbNode; +typedef struct BtRollbackOp BtRollbackOp; +typedef struct Rbtree Rbtree; +typedef struct RbtCursor RbtCursor; + +/* Forward declarations */ +static BtOps sqliteRbtreeOps; +static BtCursorOps sqliteRbtreeCursorOps; + +/* + * During each transaction (or checkpoint), a linked-list of + * "rollback-operations" is accumulated. If the transaction is rolled back, + * then the list of operations must be executed (to restore the database to + * it's state before the transaction started). If the transaction is to be + * committed, just delete the list. + * + * Each operation is represented as follows, depending on the value of eOp: + * + * ROLLBACK_INSERT -> Need to insert (pKey, pData) into table iTab. + * ROLLBACK_DELETE -> Need to delete the record (pKey) into table iTab. + * ROLLBACK_CREATE -> Need to create table iTab. + * ROLLBACK_DROP -> Need to drop table iTab. + */ +struct BtRollbackOp { + u8 eOp; + int iTab; + int nKey; + void *pKey; + int nData; + void *pData; + BtRollbackOp *pNext; +}; + +/* +** Legal values for BtRollbackOp.eOp: +*/ +#define ROLLBACK_INSERT 1 /* Insert a record */ +#define ROLLBACK_DELETE 2 /* Delete a record */ +#define ROLLBACK_CREATE 3 /* Create a table */ +#define ROLLBACK_DROP 4 /* Drop a table */ + +struct Rbtree { + BtOps *pOps; /* Function table */ + int aMetaData[SQLITE_N_BTREE_META]; + + int next_idx; /* next available table index */ + Hash tblHash; /* All created tables, by index */ + u8 isAnonymous; /* True if this Rbtree is to be deleted when closed */ + u8 eTransState; /* State of this Rbtree wrt transactions */ + + BtRollbackOp *pTransRollback; + BtRollbackOp *pCheckRollback; + BtRollbackOp *pCheckRollbackTail; +}; + +/* +** Legal values for Rbtree.eTransState. +*/ +#define TRANS_NONE 0 /* No transaction is in progress */ +#define TRANS_INTRANSACTION 1 /* A transaction is in progress */ +#define TRANS_INCHECKPOINT 2 /* A checkpoint is in progress */ +#define TRANS_ROLLBACK 3 /* We are currently rolling back a checkpoint or + * transaction. */ + +struct RbtCursor { + BtCursorOps *pOps; /* Function table */ + Rbtree *pRbtree; + BtRbTree *pTree; + int iTree; /* Index of pTree in pRbtree */ + BtRbNode *pNode; + RbtCursor *pShared; /* List of all cursors on the same Rbtree */ + u8 eSkip; /* Determines if next step operation is a no-op */ + u8 wrFlag; /* True if this cursor is open for writing */ +}; + +/* +** Legal values for RbtCursor.eSkip. +*/ +#define SKIP_NONE 0 /* Always step the cursor */ +#define SKIP_NEXT 1 /* The next sqliteRbtreeNext() is a no-op */ +#define SKIP_PREV 2 /* The next sqliteRbtreePrevious() is a no-op */ +#define SKIP_INVALID 3 /* Calls to Next() and Previous() are invalid */ + +struct BtRbTree { + RbtCursor *pCursors; /* All cursors pointing to this tree */ + BtRbNode *pHead; /* Head of the tree, or NULL */ +}; + +struct BtRbNode { + int nKey; + void *pKey; + int nData; + void *pData; + u8 isBlack; /* true for a black node, 0 for a red node */ + BtRbNode *pParent; /* Nodes parent node, NULL for the tree head */ + BtRbNode *pLeft; /* Nodes left child, or NULL */ + BtRbNode *pRight; /* Nodes right child, or NULL */ + + int nBlackHeight; /* Only used during the red-black integrity check */ +}; + +/* Forward declarations */ +static int memRbtreeMoveto( + RbtCursor* pCur, + const void *pKey, + int nKey, + int *pRes +); +static int memRbtreeClearTable(Rbtree* tree, int n); +static int memRbtreeNext(RbtCursor* pCur, int *pRes); +static int memRbtreeLast(RbtCursor* pCur, int *pRes); +static int memRbtreePrevious(RbtCursor* pCur, int *pRes); + + +/* +** This routine checks all cursors that point to the same table +** as pCur points to. If any of those cursors were opened with +** wrFlag==0 then this routine returns SQLITE_LOCKED. If all +** cursors point to the same table were opened with wrFlag==1 +** then this routine returns SQLITE_OK. +** +** In addition to checking for read-locks (where a read-lock +** means a cursor opened with wrFlag==0) this routine also NULLs +** out the pNode field of all other cursors. +** This is necessary because an insert +** or delete might change erase the node out from under +** another cursor. +*/ +static int checkReadLocks(RbtCursor *pCur){ + RbtCursor *p; + assert( pCur->wrFlag ); + for(p=pCur->pTree->pCursors; p; p=p->pShared){ + if( p!=pCur ){ + if( p->wrFlag==0 ) return SQLITE_LOCKED; + p->pNode = 0; + } + } + return SQLITE_OK; +} + +/* + * The key-compare function for the red-black trees. Returns as follows: + * + * (key1 < key2) -1 + * (key1 == key2) 0 + * (key1 > key2) 1 + * + * Keys are compared using memcmp(). If one key is an exact prefix of the + * other, then the shorter key is less than the longer key. + */ +static int key_compare(void const*pKey1, int nKey1, void const*pKey2, int nKey2) +{ + int mcmp = memcmp(pKey1, pKey2, (nKey1 <= nKey2)?nKey1:nKey2); + if( mcmp == 0){ + if( nKey1 == nKey2 ) return 0; + return ((nKey1 < nKey2)?-1:1); + } + return ((mcmp>0)?1:-1); +} + +/* + * Perform the LEFT-rotate transformation on node X of tree pTree. This + * transform is part of the red-black balancing code. + * + * | | + * X Y + * / \ / \ + * a Y X c + * / \ / \ + * b c a b + * + * BEFORE AFTER + */ +static void leftRotate(BtRbTree *pTree, BtRbNode *pX) +{ + BtRbNode *pY; + BtRbNode *pb; + pY = pX->pRight; + pb = pY->pLeft; + + pY->pParent = pX->pParent; + if( pX->pParent ){ + if( pX->pParent->pLeft == pX ) pX->pParent->pLeft = pY; + else pX->pParent->pRight = pY; + } + pY->pLeft = pX; + pX->pParent = pY; + pX->pRight = pb; + if( pb ) pb->pParent = pX; + if( pTree->pHead == pX ) pTree->pHead = pY; +} + +/* + * Perform the RIGHT-rotate transformation on node X of tree pTree. This + * transform is part of the red-black balancing code. + * + * | | + * X Y + * / \ / \ + * Y c a X + * / \ / \ + * a b b c + * + * BEFORE AFTER + */ +static void rightRotate(BtRbTree *pTree, BtRbNode *pX) +{ + BtRbNode *pY; + BtRbNode *pb; + pY = pX->pLeft; + pb = pY->pRight; + + pY->pParent = pX->pParent; + if( pX->pParent ){ + if( pX->pParent->pLeft == pX ) pX->pParent->pLeft = pY; + else pX->pParent->pRight = pY; + } + pY->pRight = pX; + pX->pParent = pY; + pX->pLeft = pb; + if( pb ) pb->pParent = pX; + if( pTree->pHead == pX ) pTree->pHead = pY; +} + +/* + * A string-manipulation helper function for check_redblack_tree(). If (orig == + * NULL) a copy of val is returned. If (orig != NULL) then a copy of the * + * concatenation of orig and val is returned. The original orig is deleted + * (using sqliteFree()). + */ +static char *append_val(char * orig, char const * val){ + char *z; + if( !orig ){ + z = sqliteStrDup( val ); + } else{ + z = 0; + sqliteSetString(&z, orig, val, (char*)0); + sqliteFree( orig ); + } + return z; +} + +/* + * Append a string representation of the entire node to orig and return it. + * This is used to produce debugging information if check_redblack_tree() finds + * a problem with a red-black binary tree. + */ +static char *append_node(char * orig, BtRbNode *pNode, int indent) +{ + char buf[128]; + int i; + + for( i=0; iisBlack ){ + orig = append_val(orig, " B \n"); + }else{ + orig = append_val(orig, " R \n"); + } + orig = append_node( orig, pNode->pLeft, indent ); + orig = append_node( orig, pNode->pRight, indent ); + }else{ + orig = append_val(orig, "\n"); + } + return orig; +} + +/* + * Print a representation of a node to stdout. This function is only included + * so you can call it from within a debugger if things get really bad. It + * is not called from anyplace in the code. + */ +static void print_node(BtRbNode *pNode) +{ + char * str = append_node(0, pNode, 0); + printf("%s", str); + + /* Suppress a warning message about print_node() being unused */ + (void)print_node; +} + +/* + * Check the following properties of the red-black tree: + * (1) - If a node is red, both of it's children are black + * (2) - Each path from a given node to a leaf (NULL) node passes thru the + * same number of black nodes + * + * If there is a problem, append a description (using append_val() ) to *msg. + */ +static void check_redblack_tree(BtRbTree * tree, char ** msg) +{ + BtRbNode *pNode; + + /* 0 -> came from parent + * 1 -> came from left + * 2 -> came from right */ + int prev_step = 0; + + pNode = tree->pHead; + while( pNode ){ + switch( prev_step ){ + case 0: + if( pNode->pLeft ){ + pNode = pNode->pLeft; + }else{ + prev_step = 1; + } + break; + case 1: + if( pNode->pRight ){ + pNode = pNode->pRight; + prev_step = 0; + }else{ + prev_step = 2; + } + break; + case 2: + /* Check red-black property (1) */ + if( !pNode->isBlack && + ( (pNode->pLeft && !pNode->pLeft->isBlack) || + (pNode->pRight && !pNode->pRight->isBlack) ) + ){ + char buf[128]; + sprintf(buf, "Red node with red child at %p\n", pNode); + *msg = append_val(*msg, buf); + *msg = append_node(*msg, tree->pHead, 0); + *msg = append_val(*msg, "\n"); + } + + /* Check red-black property (2) */ + { + int leftHeight = 0; + int rightHeight = 0; + if( pNode->pLeft ){ + leftHeight += pNode->pLeft->nBlackHeight; + leftHeight += (pNode->pLeft->isBlack?1:0); + } + if( pNode->pRight ){ + rightHeight += pNode->pRight->nBlackHeight; + rightHeight += (pNode->pRight->isBlack?1:0); + } + if( leftHeight != rightHeight ){ + char buf[128]; + sprintf(buf, "Different black-heights at %p\n", pNode); + *msg = append_val(*msg, buf); + *msg = append_node(*msg, tree->pHead, 0); + *msg = append_val(*msg, "\n"); + } + pNode->nBlackHeight = leftHeight; + } + + if( pNode->pParent ){ + if( pNode == pNode->pParent->pLeft ) prev_step = 1; + else prev_step = 2; + } + pNode = pNode->pParent; + break; + default: assert(0); + } + } +} + +/* + * Node pX has just been inserted into pTree (by code in sqliteRbtreeInsert()). + * It is possible that pX is a red node with a red parent, which is a violation + * of the red-black tree properties. This function performs rotations and + * color changes to rebalance the tree + */ +static void do_insert_balancing(BtRbTree *pTree, BtRbNode *pX) +{ + /* In the first iteration of this loop, pX points to the red node just + * inserted in the tree. If the parent of pX exists (pX is not the root + * node) and is red, then the properties of the red-black tree are + * violated. + * + * At the start of any subsequent iterations, pX points to a red node + * with a red parent. In all other respects the tree is a legal red-black + * binary tree. */ + while( pX != pTree->pHead && !pX->pParent->isBlack ){ + BtRbNode *pUncle; + BtRbNode *pGrandparent; + + /* Grandparent of pX must exist and must be black. */ + pGrandparent = pX->pParent->pParent; + assert( pGrandparent ); + assert( pGrandparent->isBlack ); + + /* Uncle of pX may or may not exist. */ + if( pX->pParent == pGrandparent->pLeft ) + pUncle = pGrandparent->pRight; + else + pUncle = pGrandparent->pLeft; + + /* If the uncle of pX exists and is red, we do the following: + * | | + * G(b) G(r) + * / \ / \ + * U(r) P(r) U(b) P(b) + * \ \ + * X(r) X(r) + * + * BEFORE AFTER + * pX is then set to G. If the parent of G is red, then the while loop + * will run again. */ + if( pUncle && !pUncle->isBlack ){ + pGrandparent->isBlack = 0; + pUncle->isBlack = 1; + pX->pParent->isBlack = 1; + pX = pGrandparent; + }else{ + + if( pX->pParent == pGrandparent->pLeft ){ + if( pX == pX->pParent->pRight ){ + /* If pX is a right-child, do the following transform, essentially + * to change pX into a left-child: + * | | + * G(b) G(b) + * / \ / \ + * P(r) U(b) X(r) U(b) + * \ / + * X(r) P(r) <-- new X + * + * BEFORE AFTER + */ + pX = pX->pParent; + leftRotate(pTree, pX); + } + + /* Do the following transform, which balances the tree :) + * | | + * G(b) P(b) + * / \ / \ + * P(r) U(b) X(r) G(r) + * / \ + * X(r) U(b) + * + * BEFORE AFTER + */ + assert( pGrandparent == pX->pParent->pParent ); + pGrandparent->isBlack = 0; + pX->pParent->isBlack = 1; + rightRotate( pTree, pGrandparent ); + + }else{ + /* This code is symetric to the illustrated case above. */ + if( pX == pX->pParent->pLeft ){ + pX = pX->pParent; + rightRotate(pTree, pX); + } + assert( pGrandparent == pX->pParent->pParent ); + pGrandparent->isBlack = 0; + pX->pParent->isBlack = 1; + leftRotate( pTree, pGrandparent ); + } + } + } + pTree->pHead->isBlack = 1; +} + +/* + * A child of pParent, which in turn had child pX, has just been removed from + * pTree (the figure below depicts the operation, Z is being removed). pParent + * or pX, or both may be NULL. + * | | + * P P + * / \ / \ + * Z X + * / \ + * X nil + * + * This function is only called if Z was black. In this case the red-black tree + * properties have been violated, and pX has an "extra black". This function + * performs rotations and color-changes to re-balance the tree. + */ +static +void do_delete_balancing(BtRbTree *pTree, BtRbNode *pX, BtRbNode *pParent) +{ + BtRbNode *pSib; + + /* TODO: Comment this code! */ + while( pX != pTree->pHead && (!pX || pX->isBlack) ){ + if( pX == pParent->pLeft ){ + pSib = pParent->pRight; + if( pSib && !(pSib->isBlack) ){ + pSib->isBlack = 1; + pParent->isBlack = 0; + leftRotate(pTree, pParent); + pSib = pParent->pRight; + } + if( !pSib ){ + pX = pParent; + }else if( + (!pSib->pLeft || pSib->pLeft->isBlack) && + (!pSib->pRight || pSib->pRight->isBlack) ) { + pSib->isBlack = 0; + pX = pParent; + }else{ + if( (!pSib->pRight || pSib->pRight->isBlack) ){ + if( pSib->pLeft ) pSib->pLeft->isBlack = 1; + pSib->isBlack = 0; + rightRotate( pTree, pSib ); + pSib = pParent->pRight; + } + pSib->isBlack = pParent->isBlack; + pParent->isBlack = 1; + if( pSib->pRight ) pSib->pRight->isBlack = 1; + leftRotate(pTree, pParent); + pX = pTree->pHead; + } + }else{ + pSib = pParent->pLeft; + if( pSib && !(pSib->isBlack) ){ + pSib->isBlack = 1; + pParent->isBlack = 0; + rightRotate(pTree, pParent); + pSib = pParent->pLeft; + } + if( !pSib ){ + pX = pParent; + }else if( + (!pSib->pLeft || pSib->pLeft->isBlack) && + (!pSib->pRight || pSib->pRight->isBlack) ){ + pSib->isBlack = 0; + pX = pParent; + }else{ + if( (!pSib->pLeft || pSib->pLeft->isBlack) ){ + if( pSib->pRight ) pSib->pRight->isBlack = 1; + pSib->isBlack = 0; + leftRotate( pTree, pSib ); + pSib = pParent->pLeft; + } + pSib->isBlack = pParent->isBlack; + pParent->isBlack = 1; + if( pSib->pLeft ) pSib->pLeft->isBlack = 1; + rightRotate(pTree, pParent); + pX = pTree->pHead; + } + } + pParent = pX->pParent; + } + if( pX ) pX->isBlack = 1; +} + +/* + * Create table n in tree pRbtree. Table n must not exist. + */ +static void btreeCreateTable(Rbtree* pRbtree, int n) +{ + BtRbTree *pNewTbl = sqliteMalloc(sizeof(BtRbTree)); + sqliteHashInsert(&pRbtree->tblHash, 0, n, pNewTbl); +} + +/* + * Log a single "rollback-op" for the given Rbtree. See comments for struct + * BtRollbackOp. + */ +static void btreeLogRollbackOp(Rbtree* pRbtree, BtRollbackOp *pRollbackOp) +{ + assert( pRbtree->eTransState == TRANS_INCHECKPOINT || + pRbtree->eTransState == TRANS_INTRANSACTION ); + if( pRbtree->eTransState == TRANS_INTRANSACTION ){ + pRollbackOp->pNext = pRbtree->pTransRollback; + pRbtree->pTransRollback = pRollbackOp; + } + if( pRbtree->eTransState == TRANS_INCHECKPOINT ){ + if( !pRbtree->pCheckRollback ){ + pRbtree->pCheckRollbackTail = pRollbackOp; + } + pRollbackOp->pNext = pRbtree->pCheckRollback; + pRbtree->pCheckRollback = pRollbackOp; + } +} + +int sqliteRbtreeOpen( + const char *zFilename, + int mode, + int nPg, + Btree **ppBtree +){ + Rbtree **ppRbtree = (Rbtree**)ppBtree; + *ppRbtree = (Rbtree *)sqliteMalloc(sizeof(Rbtree)); + if( sqlite_malloc_failed ) goto open_no_mem; + sqliteHashInit(&(*ppRbtree)->tblHash, SQLITE_HASH_INT, 0); + + /* Create a binary tree for the SQLITE_MASTER table at location 2 */ + btreeCreateTable(*ppRbtree, 2); + if( sqlite_malloc_failed ) goto open_no_mem; + (*ppRbtree)->next_idx = 3; + (*ppRbtree)->pOps = &sqliteRbtreeOps; + /* Set file type to 4; this is so that "attach ':memory:' as ...." does not + ** think that the database in uninitialised and refuse to attach + */ + (*ppRbtree)->aMetaData[2] = 4; + + return SQLITE_OK; + +open_no_mem: + *ppBtree = 0; + return SQLITE_NOMEM; +} + +/* + * Create a new table in the supplied Rbtree. Set *n to the new table number. + * Return SQLITE_OK if the operation is a success. + */ +static int memRbtreeCreateTable(Rbtree* tree, int* n) +{ + assert( tree->eTransState != TRANS_NONE ); + + *n = tree->next_idx++; + btreeCreateTable(tree, *n); + if( sqlite_malloc_failed ) return SQLITE_NOMEM; + + /* Set up the rollback structure (if we are not doing this as part of a + * rollback) */ + if( tree->eTransState != TRANS_ROLLBACK ){ + BtRollbackOp *pRollbackOp = sqliteMalloc(sizeof(BtRollbackOp)); + if( pRollbackOp==0 ) return SQLITE_NOMEM; + pRollbackOp->eOp = ROLLBACK_DROP; + pRollbackOp->iTab = *n; + btreeLogRollbackOp(tree, pRollbackOp); + } + + return SQLITE_OK; +} + +/* + * Delete table n from the supplied Rbtree. + */ +static int memRbtreeDropTable(Rbtree* tree, int n) +{ + BtRbTree *pTree; + assert( tree->eTransState != TRANS_NONE ); + + memRbtreeClearTable(tree, n); + pTree = sqliteHashInsert(&tree->tblHash, 0, n, 0); + assert(pTree); + assert( pTree->pCursors==0 ); + sqliteFree(pTree); + + if( tree->eTransState != TRANS_ROLLBACK ){ + BtRollbackOp *pRollbackOp = sqliteMalloc(sizeof(BtRollbackOp)); + if( pRollbackOp==0 ) return SQLITE_NOMEM; + pRollbackOp->eOp = ROLLBACK_CREATE; + pRollbackOp->iTab = n; + btreeLogRollbackOp(tree, pRollbackOp); + } + + return SQLITE_OK; +} + +static int memRbtreeKeyCompare(RbtCursor* pCur, const void *pKey, int nKey, + int nIgnore, int *pRes) +{ + assert(pCur); + + if( !pCur->pNode ) { + *pRes = -1; + } else { + if( (pCur->pNode->nKey - nIgnore) < 0 ){ + *pRes = -1; + }else{ + *pRes = key_compare(pCur->pNode->pKey, pCur->pNode->nKey-nIgnore, + pKey, nKey); + } + } + return SQLITE_OK; +} + +/* + * Get a new cursor for table iTable of the supplied Rbtree. The wrFlag + * parameter indicates that the cursor is open for writing. + * + * Note that RbtCursor.eSkip and RbtCursor.pNode both initialize to 0. + */ +static int memRbtreeCursor( + Rbtree* tree, + int iTable, + int wrFlag, + RbtCursor **ppCur +){ + RbtCursor *pCur; + assert(tree); + pCur = *ppCur = sqliteMalloc(sizeof(RbtCursor)); + if( sqlite_malloc_failed ) return SQLITE_NOMEM; + pCur->pTree = sqliteHashFind(&tree->tblHash, 0, iTable); + assert( pCur->pTree ); + pCur->pRbtree = tree; + pCur->iTree = iTable; + pCur->pOps = &sqliteRbtreeCursorOps; + pCur->wrFlag = wrFlag; + pCur->pShared = pCur->pTree->pCursors; + pCur->pTree->pCursors = pCur; + + assert( (*ppCur)->pTree ); + return SQLITE_OK; +} + +/* + * Insert a new record into the Rbtree. The key is given by (pKey,nKey) + * and the data is given by (pData,nData). The cursor is used only to + * define what database the record should be inserted into. The cursor + * is left pointing at the new record. + * + * If the key exists already in the tree, just replace the data. + */ +static int memRbtreeInsert( + RbtCursor* pCur, + const void *pKey, + int nKey, + const void *pDataInput, + int nData +){ + void * pData; + int match; + + /* It is illegal to call sqliteRbtreeInsert() if we are + ** not in a transaction */ + assert( pCur->pRbtree->eTransState != TRANS_NONE ); + + /* Make sure some other cursor isn't trying to read this same table */ + if( checkReadLocks(pCur) ){ + return SQLITE_LOCKED; /* The table pCur points to has a read lock */ + } + + /* Take a copy of the input data now, in case we need it for the + * replace case */ + pData = sqliteMallocRaw(nData); + if( sqlite_malloc_failed ) return SQLITE_NOMEM; + memcpy(pData, pDataInput, nData); + + /* Move the cursor to a node near the key to be inserted. If the key already + * exists in the table, then (match == 0). In this case we can just replace + * the data associated with the entry, we don't need to manipulate the tree. + * + * If there is no exact match, then the cursor points at what would be either + * the predecessor (match == -1) or successor (match == 1) of the + * searched-for key, were it to be inserted. The new node becomes a child of + * this node. + * + * The new node is initially red. + */ + memRbtreeMoveto( pCur, pKey, nKey, &match); + if( match ){ + BtRbNode *pNode = sqliteMalloc(sizeof(BtRbNode)); + if( pNode==0 ) return SQLITE_NOMEM; + pNode->nKey = nKey; + pNode->pKey = sqliteMallocRaw(nKey); + if( sqlite_malloc_failed ) return SQLITE_NOMEM; + memcpy(pNode->pKey, pKey, nKey); + pNode->nData = nData; + pNode->pData = pData; + if( pCur->pNode ){ + switch( match ){ + case -1: + assert( !pCur->pNode->pRight ); + pNode->pParent = pCur->pNode; + pCur->pNode->pRight = pNode; + break; + case 1: + assert( !pCur->pNode->pLeft ); + pNode->pParent = pCur->pNode; + pCur->pNode->pLeft = pNode; + break; + default: + assert(0); + } + }else{ + pCur->pTree->pHead = pNode; + } + + /* Point the cursor at the node just inserted, as per SQLite requirements */ + pCur->pNode = pNode; + + /* A new node has just been inserted, so run the balancing code */ + do_insert_balancing(pCur->pTree, pNode); + + /* Set up a rollback-op in case we have to roll this operation back */ + if( pCur->pRbtree->eTransState != TRANS_ROLLBACK ){ + BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) ); + if( pOp==0 ) return SQLITE_NOMEM; + pOp->eOp = ROLLBACK_DELETE; + pOp->iTab = pCur->iTree; + pOp->nKey = pNode->nKey; + pOp->pKey = sqliteMallocRaw( pOp->nKey ); + if( sqlite_malloc_failed ) return SQLITE_NOMEM; + memcpy( pOp->pKey, pNode->pKey, pOp->nKey ); + btreeLogRollbackOp(pCur->pRbtree, pOp); + } + + }else{ + /* No need to insert a new node in the tree, as the key already exists. + * Just clobber the current nodes data. */ + + /* Set up a rollback-op in case we have to roll this operation back */ + if( pCur->pRbtree->eTransState != TRANS_ROLLBACK ){ + BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) ); + if( pOp==0 ) return SQLITE_NOMEM; + pOp->iTab = pCur->iTree; + pOp->nKey = pCur->pNode->nKey; + pOp->pKey = sqliteMallocRaw( pOp->nKey ); + if( sqlite_malloc_failed ) return SQLITE_NOMEM; + memcpy( pOp->pKey, pCur->pNode->pKey, pOp->nKey ); + pOp->nData = pCur->pNode->nData; + pOp->pData = pCur->pNode->pData; + pOp->eOp = ROLLBACK_INSERT; + btreeLogRollbackOp(pCur->pRbtree, pOp); + }else{ + sqliteFree( pCur->pNode->pData ); + } + + /* Actually clobber the nodes data */ + pCur->pNode->pData = pData; + pCur->pNode->nData = nData; + } + + return SQLITE_OK; +} + +/* Move the cursor so that it points to an entry near pKey. +** Return a success code. +** +** *pRes<0 The cursor is left pointing at an entry that +** is smaller than pKey or if the table is empty +** and the cursor is therefore left point to nothing. +** +** *pRes==0 The cursor is left pointing at an entry that +** exactly matches pKey. +** +** *pRes>0 The cursor is left pointing at an entry that +** is larger than pKey. +*/ +static int memRbtreeMoveto( + RbtCursor* pCur, + const void *pKey, + int nKey, + int *pRes +){ + BtRbNode *pTmp = 0; + + pCur->pNode = pCur->pTree->pHead; + *pRes = -1; + while( pCur->pNode && *pRes ) { + *pRes = key_compare(pCur->pNode->pKey, pCur->pNode->nKey, pKey, nKey); + pTmp = pCur->pNode; + switch( *pRes ){ + case 1: /* cursor > key */ + pCur->pNode = pCur->pNode->pLeft; + break; + case -1: /* cursor < key */ + pCur->pNode = pCur->pNode->pRight; + break; + } + } + + /* If (pCur->pNode == NULL), then we have failed to find a match. Set + * pCur->pNode to pTmp, which is either NULL (if the tree is empty) or the + * last node traversed in the search. In either case the relation ship + * between pTmp and the searched for key is already stored in *pRes. pTmp is + * either the successor or predecessor of the key we tried to move to. */ + if( !pCur->pNode ) pCur->pNode = pTmp; + pCur->eSkip = SKIP_NONE; + + return SQLITE_OK; +} + + +/* +** Delete the entry that the cursor is pointing to. +** +** The cursor is left pointing at either the next or the previous +** entry. If the cursor is left pointing to the next entry, then +** the pCur->eSkip flag is set to SKIP_NEXT which forces the next call to +** sqliteRbtreeNext() to be a no-op. That way, you can always call +** sqliteRbtreeNext() after a delete and the cursor will be left +** pointing to the first entry after the deleted entry. Similarly, +** pCur->eSkip is set to SKIP_PREV is the cursor is left pointing to +** the entry prior to the deleted entry so that a subsequent call to +** sqliteRbtreePrevious() will always leave the cursor pointing at the +** entry immediately before the one that was deleted. +*/ +static int memRbtreeDelete(RbtCursor* pCur) +{ + BtRbNode *pZ; /* The one being deleted */ + BtRbNode *pChild; /* The child of the spliced out node */ + + /* It is illegal to call sqliteRbtreeDelete() if we are + ** not in a transaction */ + assert( pCur->pRbtree->eTransState != TRANS_NONE ); + + /* Make sure some other cursor isn't trying to read this same table */ + if( checkReadLocks(pCur) ){ + return SQLITE_LOCKED; /* The table pCur points to has a read lock */ + } + + pZ = pCur->pNode; + if( !pZ ){ + return SQLITE_OK; + } + + /* If we are not currently doing a rollback, set up a rollback op for this + * deletion */ + if( pCur->pRbtree->eTransState != TRANS_ROLLBACK ){ + BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) ); + if( pOp==0 ) return SQLITE_NOMEM; + pOp->iTab = pCur->iTree; + pOp->nKey = pZ->nKey; + pOp->pKey = pZ->pKey; + pOp->nData = pZ->nData; + pOp->pData = pZ->pData; + pOp->eOp = ROLLBACK_INSERT; + btreeLogRollbackOp(pCur->pRbtree, pOp); + } + + /* First do a standard binary-tree delete (node pZ is to be deleted). How + * to do this depends on how many children pZ has: + * + * If pZ has no children or one child, then splice out pZ. If pZ has two + * children, splice out the successor of pZ and replace the key and data of + * pZ with the key and data of the spliced out successor. */ + if( pZ->pLeft && pZ->pRight ){ + BtRbNode *pTmp; + int dummy; + pCur->eSkip = SKIP_NONE; + memRbtreeNext(pCur, &dummy); + assert( dummy == 0 ); + if( pCur->pRbtree->eTransState == TRANS_ROLLBACK ){ + sqliteFree(pZ->pKey); + sqliteFree(pZ->pData); + } + pZ->pData = pCur->pNode->pData; + pZ->nData = pCur->pNode->nData; + pZ->pKey = pCur->pNode->pKey; + pZ->nKey = pCur->pNode->nKey; + pTmp = pZ; + pZ = pCur->pNode; + pCur->pNode = pTmp; + pCur->eSkip = SKIP_NEXT; + }else{ + int res; + pCur->eSkip = SKIP_NONE; + memRbtreeNext(pCur, &res); + pCur->eSkip = SKIP_NEXT; + if( res ){ + memRbtreeLast(pCur, &res); + memRbtreePrevious(pCur, &res); + pCur->eSkip = SKIP_PREV; + } + if( pCur->pRbtree->eTransState == TRANS_ROLLBACK ){ + sqliteFree(pZ->pKey); + sqliteFree(pZ->pData); + } + } + + /* pZ now points at the node to be spliced out. This block does the + * splicing. */ + { + BtRbNode **ppParentSlot = 0; + assert( !pZ->pLeft || !pZ->pRight ); /* pZ has at most one child */ + pChild = ((pZ->pLeft)?pZ->pLeft:pZ->pRight); + if( pZ->pParent ){ + assert( pZ == pZ->pParent->pLeft || pZ == pZ->pParent->pRight ); + ppParentSlot = ((pZ == pZ->pParent->pLeft) + ?&pZ->pParent->pLeft:&pZ->pParent->pRight); + *ppParentSlot = pChild; + }else{ + pCur->pTree->pHead = pChild; + } + if( pChild ) pChild->pParent = pZ->pParent; + } + + /* pZ now points at the spliced out node. pChild is the only child of pZ, or + * NULL if pZ has no children. If pZ is black, and not the tree root, then we + * will have violated the "same number of black nodes in every path to a + * leaf" property of the red-black tree. The code in do_delete_balancing() + * repairs this. */ + if( pZ->isBlack ){ + do_delete_balancing(pCur->pTree, pChild, pZ->pParent); + } + + sqliteFree(pZ); + return SQLITE_OK; +} + +/* + * Empty table n of the Rbtree. + */ +static int memRbtreeClearTable(Rbtree* tree, int n) +{ + BtRbTree *pTree; + BtRbNode *pNode; + + pTree = sqliteHashFind(&tree->tblHash, 0, n); + assert(pTree); + + pNode = pTree->pHead; + while( pNode ){ + if( pNode->pLeft ){ + pNode = pNode->pLeft; + } + else if( pNode->pRight ){ + pNode = pNode->pRight; + } + else { + BtRbNode *pTmp = pNode->pParent; + if( tree->eTransState == TRANS_ROLLBACK ){ + sqliteFree( pNode->pKey ); + sqliteFree( pNode->pData ); + }else{ + BtRollbackOp *pRollbackOp = sqliteMallocRaw(sizeof(BtRollbackOp)); + if( pRollbackOp==0 ) return SQLITE_NOMEM; + pRollbackOp->eOp = ROLLBACK_INSERT; + pRollbackOp->iTab = n; + pRollbackOp->nKey = pNode->nKey; + pRollbackOp->pKey = pNode->pKey; + pRollbackOp->nData = pNode->nData; + pRollbackOp->pData = pNode->pData; + btreeLogRollbackOp(tree, pRollbackOp); + } + sqliteFree( pNode ); + if( pTmp ){ + if( pTmp->pLeft == pNode ) pTmp->pLeft = 0; + else if( pTmp->pRight == pNode ) pTmp->pRight = 0; + } + pNode = pTmp; + } + } + + pTree->pHead = 0; + return SQLITE_OK; +} + +static int memRbtreeFirst(RbtCursor* pCur, int *pRes) +{ + if( pCur->pTree->pHead ){ + pCur->pNode = pCur->pTree->pHead; + while( pCur->pNode->pLeft ){ + pCur->pNode = pCur->pNode->pLeft; + } + } + if( pCur->pNode ){ + *pRes = 0; + }else{ + *pRes = 1; + } + pCur->eSkip = SKIP_NONE; + return SQLITE_OK; +} + +static int memRbtreeLast(RbtCursor* pCur, int *pRes) +{ + if( pCur->pTree->pHead ){ + pCur->pNode = pCur->pTree->pHead; + while( pCur->pNode->pRight ){ + pCur->pNode = pCur->pNode->pRight; + } + } + if( pCur->pNode ){ + *pRes = 0; + }else{ + *pRes = 1; + } + pCur->eSkip = SKIP_NONE; + return SQLITE_OK; +} + +/* +** Advance the cursor to the next entry in the database. If +** successful then set *pRes=0. If the cursor +** was already pointing to the last entry in the database before +** this routine was called, then set *pRes=1. +*/ +static int memRbtreeNext(RbtCursor* pCur, int *pRes) +{ + if( pCur->pNode && pCur->eSkip != SKIP_NEXT ){ + if( pCur->pNode->pRight ){ + pCur->pNode = pCur->pNode->pRight; + while( pCur->pNode->pLeft ) + pCur->pNode = pCur->pNode->pLeft; + }else{ + BtRbNode * pX = pCur->pNode; + pCur->pNode = pX->pParent; + while( pCur->pNode && (pCur->pNode->pRight == pX) ){ + pX = pCur->pNode; + pCur->pNode = pX->pParent; + } + } + } + pCur->eSkip = SKIP_NONE; + + if( !pCur->pNode ){ + *pRes = 1; + }else{ + *pRes = 0; + } + + return SQLITE_OK; +} + +static int memRbtreePrevious(RbtCursor* pCur, int *pRes) +{ + if( pCur->pNode && pCur->eSkip != SKIP_PREV ){ + if( pCur->pNode->pLeft ){ + pCur->pNode = pCur->pNode->pLeft; + while( pCur->pNode->pRight ) + pCur->pNode = pCur->pNode->pRight; + }else{ + BtRbNode * pX = pCur->pNode; + pCur->pNode = pX->pParent; + while( pCur->pNode && (pCur->pNode->pLeft == pX) ){ + pX = pCur->pNode; + pCur->pNode = pX->pParent; + } + } + } + pCur->eSkip = SKIP_NONE; + + if( !pCur->pNode ){ + *pRes = 1; + }else{ + *pRes = 0; + } + + return SQLITE_OK; +} + +static int memRbtreeKeySize(RbtCursor* pCur, int *pSize) +{ + if( pCur->pNode ){ + *pSize = pCur->pNode->nKey; + }else{ + *pSize = 0; + } + return SQLITE_OK; +} + +static int memRbtreeKey(RbtCursor* pCur, int offset, int amt, char *zBuf) +{ + if( !pCur->pNode ) return 0; + if( !pCur->pNode->pKey || ((amt + offset) <= pCur->pNode->nKey) ){ + memcpy(zBuf, ((char*)pCur->pNode->pKey)+offset, amt); + }else{ + memcpy(zBuf, ((char*)pCur->pNode->pKey)+offset, pCur->pNode->nKey-offset); + amt = pCur->pNode->nKey-offset; + } + return amt; +} + +static int memRbtreeDataSize(RbtCursor* pCur, int *pSize) +{ + if( pCur->pNode ){ + *pSize = pCur->pNode->nData; + }else{ + *pSize = 0; + } + return SQLITE_OK; +} + +static int memRbtreeData(RbtCursor *pCur, int offset, int amt, char *zBuf) +{ + if( !pCur->pNode ) return 0; + if( (amt + offset) <= pCur->pNode->nData ){ + memcpy(zBuf, ((char*)pCur->pNode->pData)+offset, amt); + }else{ + memcpy(zBuf, ((char*)pCur->pNode->pData)+offset ,pCur->pNode->nData-offset); + amt = pCur->pNode->nData-offset; + } + return amt; +} + +static int memRbtreeCloseCursor(RbtCursor* pCur) +{ + if( pCur->pTree->pCursors==pCur ){ + pCur->pTree->pCursors = pCur->pShared; + }else{ + RbtCursor *p = pCur->pTree->pCursors; + while( p && p->pShared!=pCur ){ p = p->pShared; } + assert( p!=0 ); + if( p ){ + p->pShared = pCur->pShared; + } + } + sqliteFree(pCur); + return SQLITE_OK; +} + +static int memRbtreeGetMeta(Rbtree* tree, int* aMeta) +{ + memcpy( aMeta, tree->aMetaData, sizeof(int) * SQLITE_N_BTREE_META ); + return SQLITE_OK; +} + +static int memRbtreeUpdateMeta(Rbtree* tree, int* aMeta) +{ + memcpy( tree->aMetaData, aMeta, sizeof(int) * SQLITE_N_BTREE_META ); + return SQLITE_OK; +} + +/* + * Check that each table in the Rbtree meets the requirements for a red-black + * binary tree. If an error is found, return an explanation of the problem in + * memory obtained from sqliteMalloc(). Parameters aRoot and nRoot are ignored. + */ +static char *memRbtreeIntegrityCheck(Rbtree* tree, int* aRoot, int nRoot) +{ + char * msg = 0; + HashElem *p; + + for(p=sqliteHashFirst(&tree->tblHash); p; p=sqliteHashNext(p)){ + BtRbTree *pTree = sqliteHashData(p); + check_redblack_tree(pTree, &msg); + } + + return msg; +} + +static int memRbtreeSetCacheSize(Rbtree* tree, int sz) +{ + return SQLITE_OK; +} + +static int memRbtreeSetSafetyLevel(Rbtree *pBt, int level){ + return SQLITE_OK; +} + +static int memRbtreeBeginTrans(Rbtree* tree) +{ + if( tree->eTransState != TRANS_NONE ) + return SQLITE_ERROR; + + assert( tree->pTransRollback == 0 ); + tree->eTransState = TRANS_INTRANSACTION; + return SQLITE_OK; +} + +/* +** Delete a linked list of BtRollbackOp structures. +*/ +static void deleteRollbackList(BtRollbackOp *pOp){ + while( pOp ){ + BtRollbackOp *pTmp = pOp->pNext; + sqliteFree(pOp->pData); + sqliteFree(pOp->pKey); + sqliteFree(pOp); + pOp = pTmp; + } +} + +static int memRbtreeCommit(Rbtree* tree){ + /* Just delete pTransRollback and pCheckRollback */ + deleteRollbackList(tree->pCheckRollback); + deleteRollbackList(tree->pTransRollback); + tree->pTransRollback = 0; + tree->pCheckRollback = 0; + tree->pCheckRollbackTail = 0; + tree->eTransState = TRANS_NONE; + return SQLITE_OK; +} + +/* + * Close the supplied Rbtree. Delete everything associated with it. + */ +static int memRbtreeClose(Rbtree* tree) +{ + HashElem *p; + memRbtreeCommit(tree); + while( (p=sqliteHashFirst(&tree->tblHash))!=0 ){ + tree->eTransState = TRANS_ROLLBACK; + memRbtreeDropTable(tree, sqliteHashKeysize(p)); + } + sqliteHashClear(&tree->tblHash); + sqliteFree(tree); + return SQLITE_OK; +} + +/* + * Execute and delete the supplied rollback-list on pRbtree. + */ +static void execute_rollback_list(Rbtree *pRbtree, BtRollbackOp *pList) +{ + BtRollbackOp *pTmp; + RbtCursor cur; + int res; + + cur.pRbtree = pRbtree; + cur.wrFlag = 1; + while( pList ){ + switch( pList->eOp ){ + case ROLLBACK_INSERT: + cur.pTree = sqliteHashFind( &pRbtree->tblHash, 0, pList->iTab ); + assert(cur.pTree); + cur.iTree = pList->iTab; + cur.eSkip = SKIP_NONE; + memRbtreeInsert( &cur, pList->pKey, + pList->nKey, pList->pData, pList->nData ); + break; + case ROLLBACK_DELETE: + cur.pTree = sqliteHashFind( &pRbtree->tblHash, 0, pList->iTab ); + assert(cur.pTree); + cur.iTree = pList->iTab; + cur.eSkip = SKIP_NONE; + memRbtreeMoveto(&cur, pList->pKey, pList->nKey, &res); + assert(res == 0); + memRbtreeDelete( &cur ); + break; + case ROLLBACK_CREATE: + btreeCreateTable(pRbtree, pList->iTab); + break; + case ROLLBACK_DROP: + memRbtreeDropTable(pRbtree, pList->iTab); + break; + default: + assert(0); + } + sqliteFree(pList->pKey); + sqliteFree(pList->pData); + pTmp = pList->pNext; + sqliteFree(pList); + pList = pTmp; + } +} + +static int memRbtreeRollback(Rbtree* tree) +{ + tree->eTransState = TRANS_ROLLBACK; + execute_rollback_list(tree, tree->pCheckRollback); + execute_rollback_list(tree, tree->pTransRollback); + tree->pTransRollback = 0; + tree->pCheckRollback = 0; + tree->pCheckRollbackTail = 0; + tree->eTransState = TRANS_NONE; + return SQLITE_OK; +} + +static int memRbtreeBeginCkpt(Rbtree* tree) +{ + if( tree->eTransState != TRANS_INTRANSACTION ) + return SQLITE_ERROR; + + assert( tree->pCheckRollback == 0 ); + assert( tree->pCheckRollbackTail == 0 ); + tree->eTransState = TRANS_INCHECKPOINT; + return SQLITE_OK; +} + +static int memRbtreeCommitCkpt(Rbtree* tree) +{ + if( tree->eTransState == TRANS_INCHECKPOINT ){ + if( tree->pCheckRollback ){ + tree->pCheckRollbackTail->pNext = tree->pTransRollback; + tree->pTransRollback = tree->pCheckRollback; + tree->pCheckRollback = 0; + tree->pCheckRollbackTail = 0; + } + tree->eTransState = TRANS_INTRANSACTION; + } + return SQLITE_OK; +} + +static int memRbtreeRollbackCkpt(Rbtree* tree) +{ + if( tree->eTransState != TRANS_INCHECKPOINT ) return SQLITE_OK; + tree->eTransState = TRANS_ROLLBACK; + execute_rollback_list(tree, tree->pCheckRollback); + tree->pCheckRollback = 0; + tree->pCheckRollbackTail = 0; + tree->eTransState = TRANS_INTRANSACTION; + return SQLITE_OK; +} + +#ifdef SQLITE_TEST +static int memRbtreePageDump(Rbtree* tree, int pgno, int rec) +{ + assert(!"Cannot call sqliteRbtreePageDump"); + return SQLITE_OK; +} + +static int memRbtreeCursorDump(RbtCursor* pCur, int* aRes) +{ + assert(!"Cannot call sqliteRbtreeCursorDump"); + return SQLITE_OK; +} +#endif + +static struct Pager *memRbtreePager(Rbtree* tree) +{ + return 0; +} + +/* +** Return the full pathname of the underlying database file. +*/ +static const char *memRbtreeGetFilename(Rbtree *pBt){ + return 0; /* A NULL return indicates there is no underlying file */ +} + +/* +** The copy file function is not implemented for the in-memory database +*/ +static int memRbtreeCopyFile(Rbtree *pBt, Rbtree *pBt2){ + return SQLITE_INTERNAL; /* Not implemented */ +} + +static BtOps sqliteRbtreeOps = { + (int(*)(Btree*)) memRbtreeClose, + (int(*)(Btree*,int)) memRbtreeSetCacheSize, + (int(*)(Btree*,int)) memRbtreeSetSafetyLevel, + (int(*)(Btree*)) memRbtreeBeginTrans, + (int(*)(Btree*)) memRbtreeCommit, + (int(*)(Btree*)) memRbtreeRollback, + (int(*)(Btree*)) memRbtreeBeginCkpt, + (int(*)(Btree*)) memRbtreeCommitCkpt, + (int(*)(Btree*)) memRbtreeRollbackCkpt, + (int(*)(Btree*,int*)) memRbtreeCreateTable, + (int(*)(Btree*,int*)) memRbtreeCreateTable, + (int(*)(Btree*,int)) memRbtreeDropTable, + (int(*)(Btree*,int)) memRbtreeClearTable, + (int(*)(Btree*,int,int,BtCursor**)) memRbtreeCursor, + (int(*)(Btree*,int*)) memRbtreeGetMeta, + (int(*)(Btree*,int*)) memRbtreeUpdateMeta, + (char*(*)(Btree*,int*,int)) memRbtreeIntegrityCheck, + (const char*(*)(Btree*)) memRbtreeGetFilename, + (int(*)(Btree*,Btree*)) memRbtreeCopyFile, + (struct Pager*(*)(Btree*)) memRbtreePager, +#ifdef SQLITE_TEST + (int(*)(Btree*,int,int)) memRbtreePageDump, +#endif +}; + +static BtCursorOps sqliteRbtreeCursorOps = { + (int(*)(BtCursor*,const void*,int,int*)) memRbtreeMoveto, + (int(*)(BtCursor*)) memRbtreeDelete, + (int(*)(BtCursor*,const void*,int,const void*,int)) memRbtreeInsert, + (int(*)(BtCursor*,int*)) memRbtreeFirst, + (int(*)(BtCursor*,int*)) memRbtreeLast, + (int(*)(BtCursor*,int*)) memRbtreeNext, + (int(*)(BtCursor*,int*)) memRbtreePrevious, + (int(*)(BtCursor*,int*)) memRbtreeKeySize, + (int(*)(BtCursor*,int,int,char*)) memRbtreeKey, + (int(*)(BtCursor*,const void*,int,int,int*)) memRbtreeKeyCompare, + (int(*)(BtCursor*,int*)) memRbtreeDataSize, + (int(*)(BtCursor*,int,int,char*)) memRbtreeData, + (int(*)(BtCursor*)) memRbtreeCloseCursor, +#ifdef SQLITE_TEST + (int(*)(BtCursor*,int*)) memRbtreeCursorDump, +#endif + +}; + +#endif /* SQLITE_OMIT_INMEMORYDB */ diff --git a/src/libs/sqlite2/build.c b/src/libs/sqlite2/build.c new file mode 100644 index 00000000..6c17f140 --- /dev/null +++ b/src/libs/sqlite2/build.c @@ -0,0 +1,2156 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C code routines that are called by the SQLite parser +** when syntax rules are reduced. The routines in this file handle the +** following kinds of SQL syntax: +** +** CREATE TABLE +** DROP TABLE +** CREATE INDEX +** DROP INDEX +** creating ID lists +** BEGIN TRANSACTION +** COMMIT +** ROLLBACK +** PRAGMA +** +** $Id: build.c 875429 2008-10-24 12:20:41Z cgilles $ +*/ +#include "sqliteInt.h" +#include + +/* +** This routine is called when a new SQL statement is beginning to +** be parsed. Check to see if the schema for the database needs +** to be read from the SQLITE_MASTER and SQLITE_TEMP_MASTER tables. +** If it does, then read it. +*/ +void sqliteBeginParse(Parse *pParse, int explainFlag){ + sqlite *db = pParse->db; + int i; + pParse->explain = explainFlag; + if((db->flags & SQLITE_Initialized)==0 && db->init.busy==0 ){ + int rc = sqliteInit(db, &pParse->zErrMsg); + if( rc!=SQLITE_OK ){ + pParse->rc = rc; + pParse->nErr++; + } + } + for(i=0; inDb; i++){ + DbClearProperty(db, i, DB_Locked); + if( !db->aDb[i].inTrans ){ + DbClearProperty(db, i, DB_Cookie); + } + } + pParse->nVar = 0; +} + +/* +** This routine is called after a single SQL statement has been +** parsed and we want to execute the VDBE code to implement +** that statement. Prior action routines should have already +** constructed VDBE code to do the work of the SQL statement. +** This routine just has to execute the VDBE code. +** +** Note that if an error occurred, it might be the case that +** no VDBE code was generated. +*/ +void sqliteExec(Parse *pParse){ + sqlite *db = pParse->db; + Vdbe *v = pParse->pVdbe; + + if( v==0 && (v = sqliteGetVdbe(pParse))!=0 ){ + sqliteVdbeAddOp(v, OP_Halt, 0, 0); + } + if( sqlite_malloc_failed ) return; + if( v && pParse->nErr==0 ){ + FILE *trace = (db->flags & SQLITE_VdbeTrace)!=0 ? stdout : 0; + sqliteVdbeTrace(v, trace); + sqliteVdbeMakeReady(v, pParse->nVar, pParse->explain); + pParse->rc = pParse->nErr ? SQLITE_ERROR : SQLITE_DONE; + pParse->colNamesSet = 0; + }else if( pParse->rc==SQLITE_OK ){ + pParse->rc = SQLITE_ERROR; + } + pParse->nTab = 0; + pParse->nMem = 0; + pParse->nSet = 0; + pParse->nAgg = 0; + pParse->nVar = 0; +} + +/* +** Locate the in-memory structure that describes +** a particular database table given the name +** of that table and (optionally) the name of the database +** containing the table. Return NULL if not found. +** +** If zDatabase is 0, all databases are searched for the +** table and the first matching table is returned. (No checking +** for duplicate table names is done.) The search order is +** TEMP first, then MAIN, then any auxiliary databases added +** using the ATTACH command. +** +** See also sqliteLocateTable(). +*/ +Table *sqliteFindTable(sqlite *db, const char *zName, const char *zDatabase){ + Table *p = 0; + int i; + for(i=0; inDb; i++){ + int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ + if( zDatabase!=0 && sqliteStrICmp(zDatabase, db->aDb[j].zName) ) continue; + p = sqliteHashFind(&db->aDb[j].tblHash, zName, strlen(zName)+1); + if( p ) break; + } + return p; +} + +/* +** Locate the in-memory structure that describes +** a particular database table given the name +** of that table and (optionally) the name of the database +** containing the table. Return NULL if not found. +** Also leave an error message in pParse->zErrMsg. +** +** The difference between this routine and sqliteFindTable() +** is that this routine leaves an error message in pParse->zErrMsg +** where sqliteFindTable() does not. +*/ +Table *sqliteLocateTable(Parse *pParse, const char *zName, const char *zDbase){ + Table *p; + + p = sqliteFindTable(pParse->db, zName, zDbase); + if( p==0 ){ + if( zDbase ){ + sqliteErrorMsg(pParse, "no such table: %s.%s", zDbase, zName); + }else if( sqliteFindTable(pParse->db, zName, 0)!=0 ){ + sqliteErrorMsg(pParse, "table \"%s\" is not in database \"%s\"", + zName, zDbase); + }else{ + sqliteErrorMsg(pParse, "no such table: %s", zName); + } + } + return p; +} + +/* +** Locate the in-memory structure that describes +** a particular index given the name of that index +** and the name of the database that contains the index. +** Return NULL if not found. +** +** If zDatabase is 0, all databases are searched for the +** table and the first matching index is returned. (No checking +** for duplicate index names is done.) The search order is +** TEMP first, then MAIN, then any auxiliary databases added +** using the ATTACH command. +*/ +Index *sqliteFindIndex(sqlite *db, const char *zName, const char *zDb){ + Index *p = 0; + int i; + for(i=0; inDb; i++){ + int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ + if( zDb && sqliteStrICmp(zDb, db->aDb[j].zName) ) continue; + p = sqliteHashFind(&db->aDb[j].idxHash, zName, strlen(zName)+1); + if( p ) break; + } + return p; +} + +/* +** Remove the given index from the index hash table, and free +** its memory structures. +** +** The index is removed from the database hash tables but +** it is not unlinked from the Table that it indexes. +** Unlinking from the Table must be done by the calling function. +*/ +static void sqliteDeleteIndex(sqlite *db, Index *p){ + Index *pOld; + + assert( db!=0 && p->zName!=0 ); + pOld = sqliteHashInsert(&db->aDb[p->iDb].idxHash, p->zName, + strlen(p->zName)+1, 0); + if( pOld!=0 && pOld!=p ){ + sqliteHashInsert(&db->aDb[p->iDb].idxHash, pOld->zName, + strlen(pOld->zName)+1, pOld); + } + sqliteFree(p); +} + +/* +** Unlink the given index from its table, then remove +** the index from the index hash table and free its memory +** structures. +*/ +void sqliteUnlinkAndDeleteIndex(sqlite *db, Index *pIndex){ + if( pIndex->pTable->pIndex==pIndex ){ + pIndex->pTable->pIndex = pIndex->pNext; + }else{ + Index *p; + for(p=pIndex->pTable->pIndex; p && p->pNext!=pIndex; p=p->pNext){} + if( p && p->pNext==pIndex ){ + p->pNext = pIndex->pNext; + } + } + sqliteDeleteIndex(db, pIndex); +} + +/* +** Erase all schema information from the in-memory hash tables of +** database connection. This routine is called to reclaim memory +** before the connection closes. It is also called during a rollback +** if there were schema changes during the transaction. +** +** If iDb<=0 then reset the internal schema tables for all database +** files. If iDb>=2 then reset the internal schema for only the +** single file indicated. +*/ +void sqliteResetInternalSchema(sqlite *db, int iDb){ + HashElem *pElem; + Hash temp1; + Hash temp2; + int i, j; + + assert( iDb>=0 && iDbnDb ); + db->flags &= ~SQLITE_Initialized; + for(i=iDb; inDb; i++){ + Db *pDb = &db->aDb[i]; + temp1 = pDb->tblHash; + temp2 = pDb->trigHash; + sqliteHashInit(&pDb->trigHash, SQLITE_HASH_STRING, 0); + sqliteHashClear(&pDb->aFKey); + sqliteHashClear(&pDb->idxHash); + for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){ + Trigger *pTrigger = sqliteHashData(pElem); + sqliteDeleteTrigger(pTrigger); + } + sqliteHashClear(&temp2); + sqliteHashInit(&pDb->tblHash, SQLITE_HASH_STRING, 0); + for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){ + Table *pTab = sqliteHashData(pElem); + sqliteDeleteTable(db, pTab); + } + sqliteHashClear(&temp1); + DbClearProperty(db, i, DB_SchemaLoaded); + if( iDb>0 ) return; + } + assert( iDb==0 ); + db->flags &= ~SQLITE_InternChanges; + + /* If one or more of the auxiliary database files has been closed, + ** then remove then from the auxiliary database list. We take the + ** opportunity to do this here since we have just deleted all of the + ** schema hash tables and therefore do not have to make any changes + ** to any of those tables. + */ + for(i=0; inDb; i++){ + struct Db *pDb = &db->aDb[i]; + if( pDb->pBt==0 ){ + if( pDb->pAux && pDb->xFreeAux ) pDb->xFreeAux(pDb->pAux); + pDb->pAux = 0; + } + } + for(i=j=2; inDb; i++){ + struct Db *pDb = &db->aDb[i]; + if( pDb->pBt==0 ){ + sqliteFree(pDb->zName); + pDb->zName = 0; + continue; + } + if( jaDb[j] = db->aDb[i]; + } + j++; + } + memset(&db->aDb[j], 0, (db->nDb-j)*sizeof(db->aDb[j])); + db->nDb = j; + if( db->nDb<=2 && db->aDb!=db->aDbStatic ){ + memcpy(db->aDbStatic, db->aDb, 2*sizeof(db->aDb[0])); + sqliteFree(db->aDb); + db->aDb = db->aDbStatic; + } +} + +/* +** This routine is called whenever a rollback occurs. If there were +** schema changes during the transaction, then we have to reset the +** internal hash tables and reload them from disk. +*/ +void sqliteRollbackInternalChanges(sqlite *db){ + if( db->flags & SQLITE_InternChanges ){ + sqliteResetInternalSchema(db, 0); + } +} + +/* +** This routine is called when a commit occurs. +*/ +void sqliteCommitInternalChanges(sqlite *db){ + db->aDb[0].schema_cookie = db->next_cookie; + db->flags &= ~SQLITE_InternChanges; +} + +/* +** Remove the memory data structures associated with the given +** Table. No changes are made to disk by this routine. +** +** This routine just deletes the data structure. It does not unlink +** the table data structure from the hash table. Nor does it remove +** foreign keys from the sqlite.aFKey hash table. But it does destroy +** memory structures of the indices and foreign keys associated with +** the table. +** +** Indices associated with the table are unlinked from the "db" +** data structure if db!=NULL. If db==NULL, indices attached to +** the table are deleted, but it is assumed they have already been +** unlinked. +*/ +void sqliteDeleteTable(sqlite *db, Table *pTable){ + int i; + Index *pIndex, *pNext; + FKey *pFKey, *pNextFKey; + + if( pTable==0 ) return; + + /* Delete all indices associated with this table + */ + for(pIndex = pTable->pIndex; pIndex; pIndex=pNext){ + pNext = pIndex->pNext; + assert( pIndex->iDb==pTable->iDb || (pTable->iDb==0 && pIndex->iDb==1) ); + sqliteDeleteIndex(db, pIndex); + } + + /* Delete all foreign keys associated with this table. The keys + ** should have already been unlinked from the db->aFKey hash table + */ + for(pFKey=pTable->pFKey; pFKey; pFKey=pNextFKey){ + pNextFKey = pFKey->pNextFrom; + assert( pTable->iDbnDb ); + assert( sqliteHashFind(&db->aDb[pTable->iDb].aFKey, + pFKey->zTo, strlen(pFKey->zTo)+1)!=pFKey ); + sqliteFree(pFKey); + } + + /* Delete the Table structure itself. + */ + for(i=0; inCol; i++){ + sqliteFree(pTable->aCol[i].zName); + sqliteFree(pTable->aCol[i].zDflt); + sqliteFree(pTable->aCol[i].zType); + } + sqliteFree(pTable->zName); + sqliteFree(pTable->aCol); + sqliteSelectDelete(pTable->pSelect); + sqliteFree(pTable); +} + +/* +** Unlink the given table from the hash tables and the delete the +** table structure with all its indices and foreign keys. +*/ +static void sqliteUnlinkAndDeleteTable(sqlite *db, Table *p){ + Table *pOld; + FKey *pF1, *pF2; + int i = p->iDb; + assert( db!=0 ); + pOld = sqliteHashInsert(&db->aDb[i].tblHash, p->zName, strlen(p->zName)+1, 0); + assert( pOld==0 || pOld==p ); + for(pF1=p->pFKey; pF1; pF1=pF1->pNextFrom){ + int nTo = strlen(pF1->zTo) + 1; + pF2 = sqliteHashFind(&db->aDb[i].aFKey, pF1->zTo, nTo); + if( pF2==pF1 ){ + sqliteHashInsert(&db->aDb[i].aFKey, pF1->zTo, nTo, pF1->pNextTo); + }else{ + while( pF2 && pF2->pNextTo!=pF1 ){ pF2=pF2->pNextTo; } + if( pF2 ){ + pF2->pNextTo = pF1->pNextTo; + } + } + } + sqliteDeleteTable(db, p); +} + +/* +** Construct the name of a user table or index from a token. +** +** Space to hold the name is obtained from sqliteMalloc() and must +** be freed by the calling function. +*/ +char *sqliteTableNameFromToken(Token *pName){ + char *zName = sqliteStrNDup(pName->z, pName->n); + sqliteDequote(zName); + return zName; +} + +/* +** Generate code to open the appropriate master table. The table +** opened will be SQLITE_MASTER for persistent tables and +** SQLITE_TEMP_MASTER for temporary tables. The table is opened +** on cursor 0. +*/ +void sqliteOpenMasterTable(Vdbe *v, int isTemp){ + sqliteVdbeAddOp(v, OP_Integer, isTemp, 0); + sqliteVdbeAddOp(v, OP_OpenWrite, 0, 2); +} + +/* +** Begin constructing a new table representation in memory. This is +** the first of several action routines that get called in response +** to a CREATE TABLE statement. In particular, this routine is called +** after seeing tokens "CREATE" and "TABLE" and the table name. The +** pStart token is the CREATE and pName is the table name. The isTemp +** flag is true if the table should be stored in the auxiliary database +** file instead of in the main database file. This is normally the case +** when the "TEMP" or "TEMPORARY" keyword occurs in between +** CREATE and TABLE. +** +** The new table record is initialized and put in pParse->pNewTable. +** As more of the CREATE TABLE statement is parsed, additional action +** routines will be called to add more information to this record. +** At the end of the CREATE TABLE statement, the sqliteEndTable() routine +** is called to complete the construction of the new table record. +*/ +void sqliteStartTable( + Parse *pParse, /* Parser context */ + Token *pStart, /* The "CREATE" token */ + Token *pName, /* Name of table or view to create */ + int isTemp, /* True if this is a TEMP table */ + int isView /* True if this is a VIEW */ +){ + Table *pTable; + Index *pIdx; + char *zName; + sqlite *db = pParse->db; + Vdbe *v; + int iDb; + + pParse->sFirstToken = *pStart; + zName = sqliteTableNameFromToken(pName); + if( zName==0 ) return; + if( db->init.iDb==1 ) isTemp = 1; +#ifndef SQLITE_OMIT_AUTHORIZATION + assert( (isTemp & 1)==isTemp ); + { + int code; + char *zDb = isTemp ? "temp" : "main"; + if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){ + sqliteFree(zName); + return; + } + if( isView ){ + if( isTemp ){ + code = SQLITE_CREATE_TEMP_VIEW; + }else{ + code = SQLITE_CREATE_VIEW; + } + }else{ + if( isTemp ){ + code = SQLITE_CREATE_TEMP_TABLE; + }else{ + code = SQLITE_CREATE_TABLE; + } + } + if( sqliteAuthCheck(pParse, code, zName, 0, zDb) ){ + sqliteFree(zName); + return; + } + } +#endif + + + /* Before trying to create a temporary table, make sure the Btree for + ** holding temporary tables is open. + */ + if( isTemp && db->aDb[1].pBt==0 && !pParse->explain ){ + int rc = sqliteBtreeFactory(db, 0, 0, MAX_PAGES, &db->aDb[1].pBt); + if( rc!=SQLITE_OK ){ + sqliteErrorMsg(pParse, "unable to open a temporary database " + "file for storing temporary tables"); + pParse->nErr++; + return; + } + if( db->flags & SQLITE_InTrans ){ + rc = sqliteBtreeBeginTrans(db->aDb[1].pBt); + if( rc!=SQLITE_OK ){ + sqliteErrorMsg(pParse, "unable to get a write lock on " + "the temporary database file"); + return; + } + } + } + + /* Make sure the new table name does not collide with an existing + ** index or table name. Issue an error message if it does. + ** + ** If we are re-reading the sqlite_master table because of a schema + ** change and a new permanent table is found whose name collides with + ** an existing temporary table, that is not an error. + */ + pTable = sqliteFindTable(db, zName, 0); + iDb = isTemp ? 1 : db->init.iDb; + if( pTable!=0 && (pTable->iDb==iDb || !db->init.busy) ){ + sqliteErrorMsg(pParse, "table %T already exists", pName); + sqliteFree(zName); + return; + } + if( (pIdx = sqliteFindIndex(db, zName, 0))!=0 && + (pIdx->iDb==0 || !db->init.busy) ){ + sqliteErrorMsg(pParse, "there is already an index named %s", zName); + sqliteFree(zName); + return; + } + pTable = sqliteMalloc( sizeof(Table) ); + if( pTable==0 ){ + sqliteFree(zName); + return; + } + pTable->zName = zName; + pTable->nCol = 0; + pTable->aCol = 0; + pTable->iPKey = -1; + pTable->pIndex = 0; + pTable->iDb = iDb; + if( pParse->pNewTable ) sqliteDeleteTable(db, pParse->pNewTable); + pParse->pNewTable = pTable; + + /* Begin generating the code that will insert the table record into + ** the SQLITE_MASTER table. Note in particular that we must go ahead + ** and allocate the record number for the table entry now. Before any + ** PRIMARY KEY or UNIQUE keywords are parsed. Those keywords will cause + ** indices to be created and the table record must come before the + ** indices. Hence, the record number for the table must be allocated + ** now. + */ + if( !db->init.busy && (v = sqliteGetVdbe(pParse))!=0 ){ + sqliteBeginWriteOperation(pParse, 0, isTemp); + if( !isTemp ){ + sqliteVdbeAddOp(v, OP_Integer, db->file_format, 0); + sqliteVdbeAddOp(v, OP_SetCookie, 0, 1); + } + sqliteOpenMasterTable(v, isTemp); + sqliteVdbeAddOp(v, OP_NewRecno, 0, 0); + sqliteVdbeAddOp(v, OP_Dup, 0, 0); + sqliteVdbeAddOp(v, OP_String, 0, 0); + sqliteVdbeAddOp(v, OP_PutIntKey, 0, 0); + } +} + +/* +** Add a new column to the table currently being constructed. +** +** The parser calls this routine once for each column declaration +** in a CREATE TABLE statement. sqliteStartTable() gets called +** first to get things going. Then this routine is called for each +** column. +*/ +void sqliteAddColumn(Parse *pParse, Token *pName){ + Table *p; + int i; + char *z = 0; + Column *pCol; + if( (p = pParse->pNewTable)==0 ) return; + sqliteSetNString(&z, pName->z, pName->n, 0); + if( z==0 ) return; + sqliteDequote(z); + for(i=0; inCol; i++){ + if( sqliteStrICmp(z, p->aCol[i].zName)==0 ){ + sqliteErrorMsg(pParse, "duplicate column name: %s", z); + sqliteFree(z); + return; + } + } + if( (p->nCol & 0x7)==0 ){ + Column *aNew; + aNew = sqliteRealloc( p->aCol, (p->nCol+8)*sizeof(p->aCol[0])); + if( aNew==0 ) return; + p->aCol = aNew; + } + pCol = &p->aCol[p->nCol]; + memset(pCol, 0, sizeof(p->aCol[0])); + pCol->zName = z; + pCol->sortOrder = SQLITE_SO_NUM; + p->nCol++; +} + +/* +** This routine is called by the parser while in the middle of +** parsing a CREATE TABLE statement. A "NOT NULL" constraint has +** been seen on a column. This routine sets the notNull flag on +** the column currently under construction. +*/ +void sqliteAddNotNull(Parse *pParse, int onError){ + Table *p; + int i; + if( (p = pParse->pNewTable)==0 ) return; + i = p->nCol-1; + if( i>=0 ) p->aCol[i].notNull = onError; +} + +/* +** This routine is called by the parser while in the middle of +** parsing a CREATE TABLE statement. The pFirst token is the first +** token in the sequence of tokens that describe the type of the +** column currently under construction. pLast is the last token +** in the sequence. Use this information to construct a string +** that contains the typename of the column and store that string +** in zType. +*/ +void sqliteAddColumnType(Parse *pParse, Token *pFirst, Token *pLast){ + Table *p; + int i, j; + int n; + char *z, **pz; + Column *pCol; + if( (p = pParse->pNewTable)==0 ) return; + i = p->nCol-1; + if( i<0 ) return; + pCol = &p->aCol[i]; + pz = &pCol->zType; + n = pLast->n + Addr(pLast->z) - Addr(pFirst->z); + sqliteSetNString(pz, pFirst->z, n, 0); + z = *pz; + if( z==0 ) return; + for(i=j=0; z[i]; i++){ + int c = z[i]; + if( isspace(c) ) continue; + z[j++] = c; + } + z[j] = 0; + if( pParse->db->file_format>=4 ){ + pCol->sortOrder = sqliteCollateType(z, n); + }else{ + pCol->sortOrder = SQLITE_SO_NUM; + } +} + +/* +** The given token is the default value for the last column added to +** the table currently under construction. If "minusFlag" is true, it +** means the value token was preceded by a minus sign. +** +** This routine is called by the parser while in the middle of +** parsing a CREATE TABLE statement. +*/ +void sqliteAddDefaultValue(Parse *pParse, Token *pVal, int minusFlag){ + Table *p; + int i; + char **pz; + if( (p = pParse->pNewTable)==0 ) return; + i = p->nCol-1; + if( i<0 ) return; + pz = &p->aCol[i].zDflt; + if( minusFlag ){ + sqliteSetNString(pz, "-", 1, pVal->z, pVal->n, 0); + }else{ + sqliteSetNString(pz, pVal->z, pVal->n, 0); + } + sqliteDequote(*pz); +} + +/* +** Designate the PRIMARY KEY for the table. pList is a list of names +** of columns that form the primary key. If pList is NULL, then the +** most recently added column of the table is the primary key. +** +** A table can have at most one primary key. If the table already has +** a primary key (and this is the second primary key) then create an +** error. +** +** If the PRIMARY KEY is on a single column whose datatype is INTEGER, +** then we will try to use that column as the row id. (Exception: +** For backwards compatibility with older databases, do not do this +** if the file format version number is less than 1.) Set the Table.iPKey +** field of the table under construction to be the index of the +** INTEGER PRIMARY KEY column. Table.iPKey is set to -1 if there is +** no INTEGER PRIMARY KEY. +** +** If the key is not an INTEGER PRIMARY KEY, then create a unique +** index for the key. No index is created for INTEGER PRIMARY KEYs. +*/ +void sqliteAddPrimaryKey(Parse *pParse, IdList *pList, int onError){ + Table *pTab = pParse->pNewTable; + char *zType = 0; + int iCol = -1, i; + if( pTab==0 ) goto primary_key_exit; + if( pTab->hasPrimKey ){ + sqliteErrorMsg(pParse, + "table \"%s\" has more than one primary key", pTab->zName); + goto primary_key_exit; + } + pTab->hasPrimKey = 1; + if( pList==0 ){ + iCol = pTab->nCol - 1; + pTab->aCol[iCol].isPrimKey = 1; + }else{ + for(i=0; inId; i++){ + for(iCol=0; iColnCol; iCol++){ + if( sqliteStrICmp(pList->a[i].zName, pTab->aCol[iCol].zName)==0 ) break; + } + if( iColnCol ) pTab->aCol[iCol].isPrimKey = 1; + } + if( pList->nId>1 ) iCol = -1; + } + if( iCol>=0 && iColnCol ){ + zType = pTab->aCol[iCol].zType; + } + if( pParse->db->file_format>=1 && + zType && sqliteStrICmp(zType, "INTEGER")==0 ){ + pTab->iPKey = iCol; + pTab->keyConf = onError; + }else{ + sqliteCreateIndex(pParse, 0, 0, pList, onError, 0, 0); + pList = 0; + } + +primary_key_exit: + sqliteIdListDelete(pList); + return; +} + +/* +** Return the appropriate collating type given a type name. +** +** The collation type is text (SQLITE_SO_TEXT) if the type +** name contains the character stream "text" or "blob" or +** "clob". Any other type name is collated as numeric +** (SQLITE_SO_NUM). +*/ +int sqliteCollateType(const char *zType, int nType){ + int i; + for(i=0; ipNewTable)==0 ) return; + i = p->nCol-1; + if( i>=0 ) p->aCol[i].sortOrder = collType; +} + +/* +** Come up with a new random value for the schema cookie. Make sure +** the new value is different from the old. +** +** The schema cookie is used to determine when the schema for the +** database changes. After each schema change, the cookie value +** changes. When a process first reads the schema it records the +** cookie. Thereafter, whenever it goes to access the database, +** it checks the cookie to make sure the schema has not changed +** since it was last read. +** +** This plan is not completely bullet-proof. It is possible for +** the schema to change multiple times and for the cookie to be +** set back to prior value. But schema changes are infrequent +** and the probability of hitting the same cookie value is only +** 1 chance in 2^32. So we're safe enough. +*/ +void sqliteChangeCookie(sqlite *db, Vdbe *v){ + if( db->next_cookie==db->aDb[0].schema_cookie ){ + unsigned char r; + sqliteRandomness(1, &r); + db->next_cookie = db->aDb[0].schema_cookie + r + 1; + db->flags |= SQLITE_InternChanges; + sqliteVdbeAddOp(v, OP_Integer, db->next_cookie, 0); + sqliteVdbeAddOp(v, OP_SetCookie, 0, 0); + } +} + +/* +** Measure the number of characters needed to output the given +** identifier. The number returned includes any quotes used +** but does not include the null terminator. +*/ +static int identLength(const char *z){ + int n; + int needQuote = 0; + for(n=0; *z; n++, z++){ + if( *z=='\'' ){ n++; needQuote=1; } + } + return n + needQuote*2; +} + +/* +** Write an identifier onto the end of the given string. Add +** quote characters as needed. +*/ +static void identPut(char *z, int *pIdx, char *zIdent){ + int i, j, needQuote; + i = *pIdx; + for(j=0; zIdent[j]; j++){ + if( !isalnum(zIdent[j]) && zIdent[j]!='_' ) break; + } + needQuote = zIdent[j]!=0 || isdigit(zIdent[0]) + || sqliteKeywordCode(zIdent, j)!=TK_ID; + if( needQuote ) z[i++] = '\''; + for(j=0; zIdent[j]; j++){ + z[i++] = zIdent[j]; + if( zIdent[j]=='\'' ) z[i++] = '\''; + } + if( needQuote ) z[i++] = '\''; + z[i] = 0; + *pIdx = i; +} + +/* +** Generate a CREATE TABLE statement appropriate for the given +** table. Memory to hold the text of the statement is obtained +** from sqliteMalloc() and must be freed by the calling function. +*/ +static char *createTableStmt(Table *p){ + int i, k, n; + char *zStmt; + char *zSep, *zSep2, *zEnd; + n = 0; + for(i=0; inCol; i++){ + n += identLength(p->aCol[i].zName); + } + n += identLength(p->zName); + if( n<40 ){ + zSep = ""; + zSep2 = ","; + zEnd = ")"; + }else{ + zSep = "\n "; + zSep2 = ",\n "; + zEnd = "\n)"; + } + n += 35 + 6*p->nCol; + zStmt = sqliteMallocRaw( n ); + if( zStmt==0 ) return 0; + strcpy(zStmt, p->iDb==1 ? "CREATE TEMP TABLE " : "CREATE TABLE "); + k = strlen(zStmt); + identPut(zStmt, &k, p->zName); + zStmt[k++] = '('; + for(i=0; inCol; i++){ + strcpy(&zStmt[k], zSep); + k += strlen(&zStmt[k]); + zSep = zSep2; + identPut(zStmt, &k, p->aCol[i].zName); + } + strcpy(&zStmt[k], zEnd); + return zStmt; +} + +/* +** This routine is called to report the final ")" that terminates +** a CREATE TABLE statement. +** +** The table structure that other action routines have been building +** is added to the internal hash tables, assuming no errors have +** occurred. +** +** An entry for the table is made in the master table on disk, unless +** this is a temporary table or db->init.busy==1. When db->init.busy==1 +** it means we are reading the sqlite_master table because we just +** connected to the database or because the sqlite_master table has +** recently changes, so the entry for this table already exists in +** the sqlite_master table. We do not want to create it again. +** +** If the pSelect argument is not NULL, it means that this routine +** was called to create a table generated from a +** "CREATE TABLE ... AS SELECT ..." statement. The column names of +** the new table will match the result set of the SELECT. +*/ +void sqliteEndTable(Parse *pParse, Token *pEnd, Select *pSelect){ + Table *p; + sqlite *db = pParse->db; + + if( (pEnd==0 && pSelect==0) || pParse->nErr || sqlite_malloc_failed ) return; + p = pParse->pNewTable; + if( p==0 ) return; + + /* If the table is generated from a SELECT, then construct the + ** list of columns and the text of the table. + */ + if( pSelect ){ + Table *pSelTab = sqliteResultSetOfSelect(pParse, 0, pSelect); + if( pSelTab==0 ) return; + assert( p->aCol==0 ); + p->nCol = pSelTab->nCol; + p->aCol = pSelTab->aCol; + pSelTab->nCol = 0; + pSelTab->aCol = 0; + sqliteDeleteTable(0, pSelTab); + } + + /* If the db->init.busy is 1 it means we are reading the SQL off the + ** "sqlite_master" or "sqlite_temp_master" table on the disk. + ** So do not write to the disk again. Extract the root page number + ** for the table from the db->init.newTnum field. (The page number + ** should have been put there by the sqliteOpenCb routine.) + */ + if( db->init.busy ){ + p->tnum = db->init.newTnum; + } + + /* If not initializing, then create a record for the new table + ** in the SQLITE_MASTER table of the database. The record number + ** for the new table entry should already be on the stack. + ** + ** If this is a TEMPORARY table, write the entry into the auxiliary + ** file instead of into the main database file. + */ + if( !db->init.busy ){ + int n; + Vdbe *v; + + v = sqliteGetVdbe(pParse); + if( v==0 ) return; + if( p->pSelect==0 ){ + /* A regular table */ + sqliteVdbeOp3(v, OP_CreateTable, 0, p->iDb, (char*)&p->tnum, P3_POINTER); + }else{ + /* A view */ + sqliteVdbeAddOp(v, OP_Integer, 0, 0); + } + p->tnum = 0; + sqliteVdbeAddOp(v, OP_Pull, 1, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, p->pSelect==0?"table":"view", P3_STATIC); + sqliteVdbeOp3(v, OP_String, 0, 0, p->zName, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, p->zName, 0); + sqliteVdbeAddOp(v, OP_Dup, 4, 0); + sqliteVdbeAddOp(v, OP_String, 0, 0); + if( pSelect ){ + char *z = createTableStmt(p); + n = z ? strlen(z) : 0; + sqliteVdbeChangeP3(v, -1, z, n); + sqliteFree(z); + }else{ + assert( pEnd!=0 ); + n = Addr(pEnd->z) - Addr(pParse->sFirstToken.z) + 1; + sqliteVdbeChangeP3(v, -1, pParse->sFirstToken.z, n); + } + sqliteVdbeAddOp(v, OP_MakeRecord, 5, 0); + sqliteVdbeAddOp(v, OP_PutIntKey, 0, 0); + if( !p->iDb ){ + sqliteChangeCookie(db, v); + } + sqliteVdbeAddOp(v, OP_Close, 0, 0); + if( pSelect ){ + sqliteVdbeAddOp(v, OP_Integer, p->iDb, 0); + sqliteVdbeAddOp(v, OP_OpenWrite, 1, 0); + pParse->nTab = 2; + sqliteSelect(pParse, pSelect, SRT_Table, 1, 0, 0, 0); + } + sqliteEndWriteOperation(pParse); + } + + /* Add the table to the in-memory representation of the database. + */ + if( pParse->explain==0 && pParse->nErr==0 ){ + Table *pOld; + FKey *pFKey; + pOld = sqliteHashInsert(&db->aDb[p->iDb].tblHash, + p->zName, strlen(p->zName)+1, p); + if( pOld ){ + assert( p==pOld ); /* Malloc must have failed inside HashInsert() */ + return; + } + for(pFKey=p->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + int nTo = strlen(pFKey->zTo) + 1; + pFKey->pNextTo = sqliteHashFind(&db->aDb[p->iDb].aFKey, pFKey->zTo, nTo); + sqliteHashInsert(&db->aDb[p->iDb].aFKey, pFKey->zTo, nTo, pFKey); + } + pParse->pNewTable = 0; + db->nTable++; + db->flags |= SQLITE_InternChanges; + } +} + +/* +** The parser calls this routine in order to create a new VIEW +*/ +void sqliteCreateView( + Parse *pParse, /* The parsing context */ + Token *pBegin, /* The CREATE token that begins the statement */ + Token *pName, /* The token that holds the name of the view */ + Select *pSelect, /* A SELECT statement that will become the new view */ + int isTemp /* TRUE for a TEMPORARY view */ +){ + Table *p; + int n; + const char *z; + Token sEnd; + DbFixer sFix; + + sqliteStartTable(pParse, pBegin, pName, isTemp, 1); + p = pParse->pNewTable; + if( p==0 || pParse->nErr ){ + sqliteSelectDelete(pSelect); + return; + } + if( sqliteFixInit(&sFix, pParse, p->iDb, "view", pName) + && sqliteFixSelect(&sFix, pSelect) + ){ + sqliteSelectDelete(pSelect); + return; + } + + /* Make a copy of the entire SELECT statement that defines the view. + ** This will force all the Expr.token.z values to be dynamically + ** allocated rather than point to the input string - which means that + ** they will persist after the current sqlite_exec() call returns. + */ + p->pSelect = sqliteSelectDup(pSelect); + sqliteSelectDelete(pSelect); + if( !pParse->db->init.busy ){ + sqliteViewGetColumnNames(pParse, p); + } + + /* Locate the end of the CREATE VIEW statement. Make sEnd point to + ** the end. + */ + sEnd = pParse->sLastToken; + if( sEnd.z[0]!=0 && sEnd.z[0]!=';' ){ + sEnd.z += sEnd.n; + } + sEnd.n = 0; + n = sEnd.z - pBegin->z; + z = pBegin->z; + while( n>0 && (z[n-1]==';' || isspace(z[n-1])) ){ n--; } + sEnd.z = &z[n-1]; + sEnd.n = 1; + + /* Use sqliteEndTable() to add the view to the SQLITE_MASTER table */ + sqliteEndTable(pParse, &sEnd, 0); + return; +} + +/* +** The Table structure pTable is really a VIEW. Fill in the names of +** the columns of the view in the pTable structure. Return the number +** of errors. If an error is seen leave an error message in pParse->zErrMsg. +*/ +int sqliteViewGetColumnNames(Parse *pParse, Table *pTable){ + ExprList *pEList; + Select *pSel; + Table *pSelTab; + int nErr = 0; + + assert( pTable ); + + /* A positive nCol means the columns names for this view are + ** already known. + */ + if( pTable->nCol>0 ) return 0; + + /* A negative nCol is a special marker meaning that we are currently + ** trying to compute the column names. If we enter this routine with + ** a negative nCol, it means two or more views form a loop, like this: + ** + ** CREATE VIEW one AS SELECT * FROM two; + ** CREATE VIEW two AS SELECT * FROM one; + ** + ** Actually, this error is caught previously and so the following test + ** should always fail. But we will leave it in place just to be safe. + */ + if( pTable->nCol<0 ){ + sqliteErrorMsg(pParse, "view %s is circularly defined", pTable->zName); + return 1; + } + + /* If we get this far, it means we need to compute the table names. + */ + assert( pTable->pSelect ); /* If nCol==0, then pTable must be a VIEW */ + pSel = pTable->pSelect; + + /* Note that the call to sqliteResultSetOfSelect() will expand any + ** "*" elements in this list. But we will need to restore the list + ** back to its original configuration afterwards, so we save a copy of + ** the original in pEList. + */ + pEList = pSel->pEList; + pSel->pEList = sqliteExprListDup(pEList); + if( pSel->pEList==0 ){ + pSel->pEList = pEList; + return 1; /* Malloc failed */ + } + pTable->nCol = -1; + pSelTab = sqliteResultSetOfSelect(pParse, 0, pSel); + if( pSelTab ){ + assert( pTable->aCol==0 ); + pTable->nCol = pSelTab->nCol; + pTable->aCol = pSelTab->aCol; + pSelTab->nCol = 0; + pSelTab->aCol = 0; + sqliteDeleteTable(0, pSelTab); + DbSetProperty(pParse->db, pTable->iDb, DB_UnresetViews); + }else{ + pTable->nCol = 0; + nErr++; + } + sqliteSelectUnbind(pSel); + sqliteExprListDelete(pSel->pEList); + pSel->pEList = pEList; + return nErr; +} + +/* +** Clear the column names from the VIEW pTable. +** +** This routine is called whenever any other table or view is modified. +** The view passed into this routine might depend directly or indirectly +** on the modified or deleted table so we need to clear the old column +** names so that they will be recomputed. +*/ +static void sqliteViewResetColumnNames(Table *pTable){ + int i; + Column *pCol; + assert( pTable!=0 && pTable->pSelect!=0 ); + for(i=0, pCol=pTable->aCol; inCol; i++, pCol++){ + sqliteFree(pCol->zName); + sqliteFree(pCol->zDflt); + sqliteFree(pCol->zType); + } + sqliteFree(pTable->aCol); + pTable->aCol = 0; + pTable->nCol = 0; +} + +/* +** Clear the column names from every VIEW in database idx. +*/ +static void sqliteViewResetAll(sqlite *db, int idx){ + HashElem *i; + if( !DbHasProperty(db, idx, DB_UnresetViews) ) return; + for(i=sqliteHashFirst(&db->aDb[idx].tblHash); i; i=sqliteHashNext(i)){ + Table *pTab = sqliteHashData(i); + if( pTab->pSelect ){ + sqliteViewResetColumnNames(pTab); + } + } + DbClearProperty(db, idx, DB_UnresetViews); +} + +/* +** Given a token, look up a table with that name. If not found, leave +** an error for the parser to find and return NULL. +*/ +Table *sqliteTableFromToken(Parse *pParse, Token *pTok){ + char *zName; + Table *pTab; + zName = sqliteTableNameFromToken(pTok); + if( zName==0 ) return 0; + pTab = sqliteFindTable(pParse->db, zName, 0); + sqliteFree(zName); + if( pTab==0 ){ + sqliteErrorMsg(pParse, "no such table: %T", pTok); + } + return pTab; +} + +/* +** This routine is called to do the work of a DROP TABLE statement. +** pName is the name of the table to be dropped. +*/ +void sqliteDropTable(Parse *pParse, Token *pName, int isView){ + Table *pTable; + Vdbe *v; + int base; + sqlite *db = pParse->db; + int iDb; + + if( pParse->nErr || sqlite_malloc_failed ) return; + pTable = sqliteTableFromToken(pParse, pName); + if( pTable==0 ) return; + iDb = pTable->iDb; + assert( iDb>=0 && iDbnDb ); +#ifndef SQLITE_OMIT_AUTHORIZATION + { + int code; + const char *zTab = SCHEMA_TABLE(pTable->iDb); + const char *zDb = db->aDb[pTable->iDb].zName; + if( sqliteAuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb)){ + return; + } + if( isView ){ + if( iDb==1 ){ + code = SQLITE_DROP_TEMP_VIEW; + }else{ + code = SQLITE_DROP_VIEW; + } + }else{ + if( iDb==1 ){ + code = SQLITE_DROP_TEMP_TABLE; + }else{ + code = SQLITE_DROP_TABLE; + } + } + if( sqliteAuthCheck(pParse, code, pTable->zName, 0, zDb) ){ + return; + } + if( sqliteAuthCheck(pParse, SQLITE_DELETE, pTable->zName, 0, zDb) ){ + return; + } + } +#endif + if( pTable->readOnly ){ + sqliteErrorMsg(pParse, "table %s may not be dropped", pTable->zName); + pParse->nErr++; + return; + } + if( isView && pTable->pSelect==0 ){ + sqliteErrorMsg(pParse, "use DROP TABLE to delete table %s", pTable->zName); + return; + } + if( !isView && pTable->pSelect ){ + sqliteErrorMsg(pParse, "use DROP VIEW to delete view %s", pTable->zName); + return; + } + + /* Generate code to remove the table from the master table + ** on disk. + */ + v = sqliteGetVdbe(pParse); + if( v ){ + static VdbeOpList dropTable[] = { + { OP_Rewind, 0, ADDR(8), 0}, + { OP_String, 0, 0, 0}, /* 1 */ + { OP_MemStore, 1, 1, 0}, + { OP_MemLoad, 1, 0, 0}, /* 3 */ + { OP_Column, 0, 2, 0}, + { OP_Ne, 0, ADDR(7), 0}, + { OP_Delete, 0, 0, 0}, + { OP_Next, 0, ADDR(3), 0}, /* 7 */ + }; + Index *pIdx; + Trigger *pTrigger; + sqliteBeginWriteOperation(pParse, 0, pTable->iDb); + + /* Drop all triggers associated with the table being dropped */ + pTrigger = pTable->pTrigger; + while( pTrigger ){ + assert( pTrigger->iDb==pTable->iDb || pTrigger->iDb==1 ); + sqliteDropTriggerPtr(pParse, pTrigger, 1); + if( pParse->explain ){ + pTrigger = pTrigger->pNext; + }else{ + pTrigger = pTable->pTrigger; + } + } + + /* Drop all SQLITE_MASTER entries that refer to the table */ + sqliteOpenMasterTable(v, pTable->iDb); + base = sqliteVdbeAddOpList(v, ArraySize(dropTable), dropTable); + sqliteVdbeChangeP3(v, base+1, pTable->zName, 0); + + /* Drop all SQLITE_TEMP_MASTER entries that refer to the table */ + if( pTable->iDb!=1 ){ + sqliteOpenMasterTable(v, 1); + base = sqliteVdbeAddOpList(v, ArraySize(dropTable), dropTable); + sqliteVdbeChangeP3(v, base+1, pTable->zName, 0); + } + + if( pTable->iDb==0 ){ + sqliteChangeCookie(db, v); + } + sqliteVdbeAddOp(v, OP_Close, 0, 0); + if( !isView ){ + sqliteVdbeAddOp(v, OP_Destroy, pTable->tnum, pTable->iDb); + for(pIdx=pTable->pIndex; pIdx; pIdx=pIdx->pNext){ + sqliteVdbeAddOp(v, OP_Destroy, pIdx->tnum, pIdx->iDb); + } + } + sqliteEndWriteOperation(pParse); + } + + /* Delete the in-memory description of the table. + ** + ** Exception: if the SQL statement began with the EXPLAIN keyword, + ** then no changes should be made. + */ + if( !pParse->explain ){ + sqliteUnlinkAndDeleteTable(db, pTable); + db->flags |= SQLITE_InternChanges; + } + sqliteViewResetAll(db, iDb); +} + +/* +** This routine constructs a P3 string suitable for an OP_MakeIdxKey +** opcode and adds that P3 string to the most recently inserted instruction +** in the virtual machine. The P3 string consists of a single character +** for each column in the index pIdx of table pTab. If the column uses +** a numeric sort order, then the P3 string character corresponding to +** that column is 'n'. If the column uses a text sort order, then the +** P3 string is 't'. See the OP_MakeIdxKey opcode documentation for +** additional information. See also the sqliteAddKeyType() routine. +*/ +void sqliteAddIdxKeyType(Vdbe *v, Index *pIdx){ + char *zType; + Table *pTab; + int i, n; + assert( pIdx!=0 && pIdx->pTable!=0 ); + pTab = pIdx->pTable; + n = pIdx->nColumn; + zType = sqliteMallocRaw( n+1 ); + if( zType==0 ) return; + for(i=0; iaiColumn[i]; + assert( iCol>=0 && iColnCol ); + if( (pTab->aCol[iCol].sortOrder & SQLITE_SO_TYPEMASK)==SQLITE_SO_TEXT ){ + zType[i] = 't'; + }else{ + zType[i] = 'n'; + } + } + zType[n] = 0; + sqliteVdbeChangeP3(v, -1, zType, n); + sqliteFree(zType); +} + +/* +** This routine is called to create a new foreign key on the table +** currently under construction. pFromCol determines which columns +** in the current table point to the foreign key. If pFromCol==0 then +** connect the key to the last column inserted. pTo is the name of +** the table referred to. pToCol is a list of tables in the other +** pTo table that the foreign key points to. flags contains all +** information about the conflict resolution algorithms specified +** in the ON DELETE, ON UPDATE and ON INSERT clauses. +** +** An FKey structure is created and added to the table currently +** under construction in the pParse->pNewTable field. The new FKey +** is not linked into db->aFKey at this point - that does not happen +** until sqliteEndTable(). +** +** The foreign key is set for IMMEDIATE processing. A subsequent call +** to sqliteDeferForeignKey() might change this to DEFERRED. +*/ +void sqliteCreateForeignKey( + Parse *pParse, /* Parsing context */ + IdList *pFromCol, /* Columns in this table that point to other table */ + Token *pTo, /* Name of the other table */ + IdList *pToCol, /* Columns in the other table */ + int flags /* Conflict resolution algorithms. */ +){ + Table *p = pParse->pNewTable; + int nByte; + int i; + int nCol; + char *z; + FKey *pFKey = 0; + + assert( pTo!=0 ); + if( p==0 || pParse->nErr ) goto fk_end; + if( pFromCol==0 ){ + int iCol = p->nCol-1; + if( iCol<0 ) goto fk_end; + if( pToCol && pToCol->nId!=1 ){ + sqliteErrorMsg(pParse, "foreign key on %s" + " should reference only one column of table %T", + p->aCol[iCol].zName, pTo); + goto fk_end; + } + nCol = 1; + }else if( pToCol && pToCol->nId!=pFromCol->nId ){ + sqliteErrorMsg(pParse, + "number of columns in foreign key does not match the number of " + "columns in the referenced table"); + goto fk_end; + }else{ + nCol = pFromCol->nId; + } + nByte = sizeof(*pFKey) + nCol*sizeof(pFKey->aCol[0]) + pTo->n + 1; + if( pToCol ){ + for(i=0; inId; i++){ + nByte += strlen(pToCol->a[i].zName) + 1; + } + } + pFKey = sqliteMalloc( nByte ); + if( pFKey==0 ) goto fk_end; + pFKey->pFrom = p; + pFKey->pNextFrom = p->pFKey; + z = (char*)&pFKey[1]; + pFKey->aCol = (struct sColMap*)z; + z += sizeof(struct sColMap)*nCol; + pFKey->zTo = z; + memcpy(z, pTo->z, pTo->n); + z[pTo->n] = 0; + z += pTo->n+1; + pFKey->pNextTo = 0; + pFKey->nCol = nCol; + if( pFromCol==0 ){ + pFKey->aCol[0].iFrom = p->nCol-1; + }else{ + for(i=0; inCol; j++){ + if( sqliteStrICmp(p->aCol[j].zName, pFromCol->a[i].zName)==0 ){ + pFKey->aCol[i].iFrom = j; + break; + } + } + if( j>=p->nCol ){ + sqliteErrorMsg(pParse, + "unknown column \"%s\" in foreign key definition", + pFromCol->a[i].zName); + goto fk_end; + } + } + } + if( pToCol ){ + for(i=0; ia[i].zName); + pFKey->aCol[i].zCol = z; + memcpy(z, pToCol->a[i].zName, n); + z[n] = 0; + z += n+1; + } + } + pFKey->isDeferred = 0; + pFKey->deleteConf = flags & 0xff; + pFKey->updateConf = (flags >> 8 ) & 0xff; + pFKey->insertConf = (flags >> 16 ) & 0xff; + + /* Link the foreign key to the table as the last step. + */ + p->pFKey = pFKey; + pFKey = 0; + +fk_end: + sqliteFree(pFKey); + sqliteIdListDelete(pFromCol); + sqliteIdListDelete(pToCol); +} + +/* +** This routine is called when an INITIALLY IMMEDIATE or INITIALLY DEFERRED +** clause is seen as part of a foreign key definition. The isDeferred +** parameter is 1 for INITIALLY DEFERRED and 0 for INITIALLY IMMEDIATE. +** The behavior of the most recently created foreign key is adjusted +** accordingly. +*/ +void sqliteDeferForeignKey(Parse *pParse, int isDeferred){ + Table *pTab; + FKey *pFKey; + if( (pTab = pParse->pNewTable)==0 || (pFKey = pTab->pFKey)==0 ) return; + pFKey->isDeferred = isDeferred; +} + +/* +** Create a new index for an SQL table. pIndex is the name of the index +** and pTable is the name of the table that is to be indexed. Both will +** be NULL for a primary key or an index that is created to satisfy a +** UNIQUE constraint. If pTable and pIndex are NULL, use pParse->pNewTable +** as the table to be indexed. pParse->pNewTable is a table that is +** currently being constructed by a CREATE TABLE statement. +** +** pList is a list of columns to be indexed. pList will be NULL if this +** is a primary key or unique-constraint on the most recent column added +** to the table currently under construction. +*/ +void sqliteCreateIndex( + Parse *pParse, /* All information about this parse */ + Token *pName, /* Name of the index. May be NULL */ + SrcList *pTable, /* Name of the table to index. Use pParse->pNewTable if 0 */ + IdList *pList, /* A list of columns to be indexed */ + int onError, /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ + Token *pStart, /* The CREATE token that begins a CREATE TABLE statement */ + Token *pEnd /* The ")" that closes the CREATE INDEX statement */ +){ + Table *pTab; /* Table to be indexed */ + Index *pIndex; /* The index to be created */ + char *zName = 0; + int i, j; + Token nullId; /* Fake token for an empty ID list */ + DbFixer sFix; /* For assigning database names to pTable */ + int isTemp; /* True for a temporary index */ + sqlite *db = pParse->db; + + if( pParse->nErr || sqlite_malloc_failed ) goto exit_create_index; + if( db->init.busy + && sqliteFixInit(&sFix, pParse, db->init.iDb, "index", pName) + && sqliteFixSrcList(&sFix, pTable) + ){ + goto exit_create_index; + } + + /* + ** Find the table that is to be indexed. Return early if not found. + */ + if( pTable!=0 ){ + assert( pName!=0 ); + assert( pTable->nSrc==1 ); + pTab = sqliteSrcListLookup(pParse, pTable); + }else{ + assert( pName==0 ); + pTab = pParse->pNewTable; + } + if( pTab==0 || pParse->nErr ) goto exit_create_index; + if( pTab->readOnly ){ + sqliteErrorMsg(pParse, "table %s may not be indexed", pTab->zName); + goto exit_create_index; + } + if( pTab->iDb>=2 && db->init.busy==0 ){ + sqliteErrorMsg(pParse, "table %s may not have indices added", pTab->zName); + goto exit_create_index; + } + if( pTab->pSelect ){ + sqliteErrorMsg(pParse, "views may not be indexed"); + goto exit_create_index; + } + isTemp = pTab->iDb==1; + + /* + ** Find the name of the index. Make sure there is not already another + ** index or table with the same name. + ** + ** Exception: If we are reading the names of permanent indices from the + ** sqlite_master table (because some other process changed the schema) and + ** one of the index names collides with the name of a temporary table or + ** index, then we will continue to process this index. + ** + ** If pName==0 it means that we are + ** dealing with a primary key or UNIQUE constraint. We have to invent our + ** own name. + */ + if( pName && !db->init.busy ){ + Index *pISameName; /* Another index with the same name */ + Table *pTSameName; /* A table with same name as the index */ + zName = sqliteTableNameFromToken(pName); + if( zName==0 ) goto exit_create_index; + if( (pISameName = sqliteFindIndex(db, zName, 0))!=0 ){ + sqliteErrorMsg(pParse, "index %s already exists", zName); + goto exit_create_index; + } + if( (pTSameName = sqliteFindTable(db, zName, 0))!=0 ){ + sqliteErrorMsg(pParse, "there is already a table named %s", zName); + goto exit_create_index; + } + }else if( pName==0 ){ + char zBuf[30]; + int n; + Index *pLoop; + for(pLoop=pTab->pIndex, n=1; pLoop; pLoop=pLoop->pNext, n++){} + sprintf(zBuf,"%d)",n); + zName = 0; + sqliteSetString(&zName, "(", pTab->zName, " autoindex ", zBuf, (char*)0); + if( zName==0 ) goto exit_create_index; + }else{ + zName = sqliteTableNameFromToken(pName); + } + + /* Check for authorization to create an index. + */ +#ifndef SQLITE_OMIT_AUTHORIZATION + { + const char *zDb = db->aDb[pTab->iDb].zName; + + assert( pTab->iDb==db->init.iDb || isTemp ); + if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){ + goto exit_create_index; + } + i = SQLITE_CREATE_INDEX; + if( isTemp ) i = SQLITE_CREATE_TEMP_INDEX; + if( sqliteAuthCheck(pParse, i, zName, pTab->zName, zDb) ){ + goto exit_create_index; + } + } +#endif + + /* If pList==0, it means this routine was called to make a primary + ** key out of the last column added to the table under construction. + ** So create a fake list to simulate this. + */ + if( pList==0 ){ + nullId.z = pTab->aCol[pTab->nCol-1].zName; + nullId.n = strlen(nullId.z); + pList = sqliteIdListAppend(0, &nullId); + if( pList==0 ) goto exit_create_index; + } + + /* + ** Allocate the index structure. + */ + pIndex = sqliteMalloc( sizeof(Index) + strlen(zName) + 1 + + sizeof(int)*pList->nId ); + if( pIndex==0 ) goto exit_create_index; + pIndex->aiColumn = (int*)&pIndex[1]; + pIndex->zName = (char*)&pIndex->aiColumn[pList->nId]; + strcpy(pIndex->zName, zName); + pIndex->pTable = pTab; + pIndex->nColumn = pList->nId; + pIndex->onError = onError; + pIndex->autoIndex = pName==0; + pIndex->iDb = isTemp ? 1 : db->init.iDb; + + /* Scan the names of the columns of the table to be indexed and + ** load the column indices into the Index structure. Report an error + ** if any column is not found. + */ + for(i=0; inId; i++){ + for(j=0; jnCol; j++){ + if( sqliteStrICmp(pList->a[i].zName, pTab->aCol[j].zName)==0 ) break; + } + if( j>=pTab->nCol ){ + sqliteErrorMsg(pParse, "table %s has no column named %s", + pTab->zName, pList->a[i].zName); + sqliteFree(pIndex); + goto exit_create_index; + } + pIndex->aiColumn[i] = j; + } + + /* Link the new Index structure to its table and to the other + ** in-memory database structures. + */ + if( !pParse->explain ){ + Index *p; + p = sqliteHashInsert(&db->aDb[pIndex->iDb].idxHash, + pIndex->zName, strlen(pIndex->zName)+1, pIndex); + if( p ){ + assert( p==pIndex ); /* Malloc must have failed */ + sqliteFree(pIndex); + goto exit_create_index; + } + db->flags |= SQLITE_InternChanges; + } + + /* When adding an index to the list of indices for a table, make + ** sure all indices labeled OE_Replace come after all those labeled + ** OE_Ignore. This is necessary for the correct operation of UPDATE + ** and INSERT. + */ + if( onError!=OE_Replace || pTab->pIndex==0 + || pTab->pIndex->onError==OE_Replace){ + pIndex->pNext = pTab->pIndex; + pTab->pIndex = pIndex; + }else{ + Index *pOther = pTab->pIndex; + while( pOther->pNext && pOther->pNext->onError!=OE_Replace ){ + pOther = pOther->pNext; + } + pIndex->pNext = pOther->pNext; + pOther->pNext = pIndex; + } + + /* If the db->init.busy is 1 it means we are reading the SQL off the + ** "sqlite_master" table on the disk. So do not write to the disk + ** again. Extract the table number from the db->init.newTnum field. + */ + if( db->init.busy && pTable!=0 ){ + pIndex->tnum = db->init.newTnum; + } + + /* If the db->init.busy is 0 then create the index on disk. This + ** involves writing the index into the master table and filling in the + ** index with the current table contents. + ** + ** The db->init.busy is 0 when the user first enters a CREATE INDEX + ** command. db->init.busy is 1 when a database is opened and + ** CREATE INDEX statements are read out of the master table. In + ** the latter case the index already exists on disk, which is why + ** we don't want to recreate it. + ** + ** If pTable==0 it means this index is generated as a primary key + ** or UNIQUE constraint of a CREATE TABLE statement. Since the table + ** has just been created, it contains no data and the index initialization + ** step can be skipped. + */ + else if( db->init.busy==0 ){ + int n; + Vdbe *v; + int lbl1, lbl2; + int i; + int addr; + + v = sqliteGetVdbe(pParse); + if( v==0 ) goto exit_create_index; + if( pTable!=0 ){ + sqliteBeginWriteOperation(pParse, 0, isTemp); + sqliteOpenMasterTable(v, isTemp); + } + sqliteVdbeAddOp(v, OP_NewRecno, 0, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, "index", P3_STATIC); + sqliteVdbeOp3(v, OP_String, 0, 0, pIndex->zName, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, pTab->zName, 0); + sqliteVdbeOp3(v, OP_CreateIndex, 0, isTemp,(char*)&pIndex->tnum,P3_POINTER); + pIndex->tnum = 0; + if( pTable ){ + sqliteVdbeCode(v, + OP_Dup, 0, 0, + OP_Integer, isTemp, 0, + OP_OpenWrite, 1, 0, + 0); + } + addr = sqliteVdbeAddOp(v, OP_String, 0, 0); + if( pStart && pEnd ){ + n = Addr(pEnd->z) - Addr(pStart->z) + 1; + sqliteVdbeChangeP3(v, addr, pStart->z, n); + } + sqliteVdbeAddOp(v, OP_MakeRecord, 5, 0); + sqliteVdbeAddOp(v, OP_PutIntKey, 0, 0); + if( pTable ){ + sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqliteVdbeOp3(v, OP_OpenRead, 2, pTab->tnum, pTab->zName, 0); + lbl2 = sqliteVdbeMakeLabel(v); + sqliteVdbeAddOp(v, OP_Rewind, 2, lbl2); + lbl1 = sqliteVdbeAddOp(v, OP_Recno, 2, 0); + for(i=0; inColumn; i++){ + int iCol = pIndex->aiColumn[i]; + if( pTab->iPKey==iCol ){ + sqliteVdbeAddOp(v, OP_Dup, i, 0); + }else{ + sqliteVdbeAddOp(v, OP_Column, 2, iCol); + } + } + sqliteVdbeAddOp(v, OP_MakeIdxKey, pIndex->nColumn, 0); + if( db->file_format>=4 ) sqliteAddIdxKeyType(v, pIndex); + sqliteVdbeOp3(v, OP_IdxPut, 1, pIndex->onError!=OE_None, + "indexed columns are not unique", P3_STATIC); + sqliteVdbeAddOp(v, OP_Next, 2, lbl1); + sqliteVdbeResolveLabel(v, lbl2); + sqliteVdbeAddOp(v, OP_Close, 2, 0); + sqliteVdbeAddOp(v, OP_Close, 1, 0); + } + if( pTable!=0 ){ + if( !isTemp ){ + sqliteChangeCookie(db, v); + } + sqliteVdbeAddOp(v, OP_Close, 0, 0); + sqliteEndWriteOperation(pParse); + } + } + + /* Clean up before exiting */ +exit_create_index: + sqliteIdListDelete(pList); + sqliteSrcListDelete(pTable); + sqliteFree(zName); + return; +} + +/* +** This routine will drop an existing named index. This routine +** implements the DROP INDEX statement. +*/ +void sqliteDropIndex(Parse *pParse, SrcList *pName){ + Index *pIndex; + Vdbe *v; + sqlite *db = pParse->db; + + if( pParse->nErr || sqlite_malloc_failed ) return; + assert( pName->nSrc==1 ); + pIndex = sqliteFindIndex(db, pName->a[0].zName, pName->a[0].zDatabase); + if( pIndex==0 ){ + sqliteErrorMsg(pParse, "no such index: %S", pName, 0); + goto exit_drop_index; + } + if( pIndex->autoIndex ){ + sqliteErrorMsg(pParse, "index associated with UNIQUE " + "or PRIMARY KEY constraint cannot be dropped", 0); + goto exit_drop_index; + } + if( pIndex->iDb>1 ){ + sqliteErrorMsg(pParse, "cannot alter schema of attached " + "databases", 0); + goto exit_drop_index; + } +#ifndef SQLITE_OMIT_AUTHORIZATION + { + int code = SQLITE_DROP_INDEX; + Table *pTab = pIndex->pTable; + const char *zDb = db->aDb[pIndex->iDb].zName; + const char *zTab = SCHEMA_TABLE(pIndex->iDb); + if( sqliteAuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){ + goto exit_drop_index; + } + if( pIndex->iDb ) code = SQLITE_DROP_TEMP_INDEX; + if( sqliteAuthCheck(pParse, code, pIndex->zName, pTab->zName, zDb) ){ + goto exit_drop_index; + } + } +#endif + + /* Generate code to remove the index and from the master table */ + v = sqliteGetVdbe(pParse); + if( v ){ + static VdbeOpList dropIndex[] = { + { OP_Rewind, 0, ADDR(9), 0}, + { OP_String, 0, 0, 0}, /* 1 */ + { OP_MemStore, 1, 1, 0}, + { OP_MemLoad, 1, 0, 0}, /* 3 */ + { OP_Column, 0, 1, 0}, + { OP_Eq, 0, ADDR(8), 0}, + { OP_Next, 0, ADDR(3), 0}, + { OP_Goto, 0, ADDR(9), 0}, + { OP_Delete, 0, 0, 0}, /* 8 */ + }; + int base; + + sqliteBeginWriteOperation(pParse, 0, pIndex->iDb); + sqliteOpenMasterTable(v, pIndex->iDb); + base = sqliteVdbeAddOpList(v, ArraySize(dropIndex), dropIndex); + sqliteVdbeChangeP3(v, base+1, pIndex->zName, 0); + if( pIndex->iDb==0 ){ + sqliteChangeCookie(db, v); + } + sqliteVdbeAddOp(v, OP_Close, 0, 0); + sqliteVdbeAddOp(v, OP_Destroy, pIndex->tnum, pIndex->iDb); + sqliteEndWriteOperation(pParse); + } + + /* Delete the in-memory description of this index. + */ + if( !pParse->explain ){ + sqliteUnlinkAndDeleteIndex(db, pIndex); + db->flags |= SQLITE_InternChanges; + } + +exit_drop_index: + sqliteSrcListDelete(pName); +} + +/* +** Append a new element to the given IdList. Create a new IdList if +** need be. +** +** A new IdList is returned, or NULL if malloc() fails. +*/ +IdList *sqliteIdListAppend(IdList *pList, Token *pToken){ + if( pList==0 ){ + pList = sqliteMalloc( sizeof(IdList) ); + if( pList==0 ) return 0; + pList->nAlloc = 0; + } + if( pList->nId>=pList->nAlloc ){ + struct IdList_item *a; + pList->nAlloc = pList->nAlloc*2 + 5; + a = sqliteRealloc(pList->a, pList->nAlloc*sizeof(pList->a[0]) ); + if( a==0 ){ + sqliteIdListDelete(pList); + return 0; + } + pList->a = a; + } + memset(&pList->a[pList->nId], 0, sizeof(pList->a[0])); + if( pToken ){ + char **pz = &pList->a[pList->nId].zName; + sqliteSetNString(pz, pToken->z, pToken->n, 0); + if( *pz==0 ){ + sqliteIdListDelete(pList); + return 0; + }else{ + sqliteDequote(*pz); + } + } + pList->nId++; + return pList; +} + +/* +** Append a new table name to the given SrcList. Create a new SrcList if +** need be. A new entry is created in the SrcList even if pToken is NULL. +** +** A new SrcList is returned, or NULL if malloc() fails. +** +** If pDatabase is not null, it means that the table has an optional +** database name prefix. Like this: "database.table". The pDatabase +** points to the table name and the pTable points to the database name. +** The SrcList.a[].zName field is filled with the table name which might +** come from pTable (if pDatabase is NULL) or from pDatabase. +** SrcList.a[].zDatabase is filled with the database name from pTable, +** or with NULL if no database is specified. +** +** In other words, if call like this: +** +** sqliteSrcListAppend(A,B,0); +** +** Then B is a table name and the database name is unspecified. If called +** like this: +** +** sqliteSrcListAppend(A,B,C); +** +** Then C is the table name and B is the database name. +*/ +SrcList *sqliteSrcListAppend(SrcList *pList, Token *pTable, Token *pDatabase){ + if( pList==0 ){ + pList = sqliteMalloc( sizeof(SrcList) ); + if( pList==0 ) return 0; + pList->nAlloc = 1; + } + if( pList->nSrc>=pList->nAlloc ){ + SrcList *pNew; + pList->nAlloc *= 2; + pNew = sqliteRealloc(pList, + sizeof(*pList) + (pList->nAlloc-1)*sizeof(pList->a[0]) ); + if( pNew==0 ){ + sqliteSrcListDelete(pList); + return 0; + } + pList = pNew; + } + memset(&pList->a[pList->nSrc], 0, sizeof(pList->a[0])); + if( pDatabase && pDatabase->z==0 ){ + pDatabase = 0; + } + if( pDatabase && pTable ){ + Token *pTemp = pDatabase; + pDatabase = pTable; + pTable = pTemp; + } + if( pTable ){ + char **pz = &pList->a[pList->nSrc].zName; + sqliteSetNString(pz, pTable->z, pTable->n, 0); + if( *pz==0 ){ + sqliteSrcListDelete(pList); + return 0; + }else{ + sqliteDequote(*pz); + } + } + if( pDatabase ){ + char **pz = &pList->a[pList->nSrc].zDatabase; + sqliteSetNString(pz, pDatabase->z, pDatabase->n, 0); + if( *pz==0 ){ + sqliteSrcListDelete(pList); + return 0; + }else{ + sqliteDequote(*pz); + } + } + pList->a[pList->nSrc].iCursor = -1; + pList->nSrc++; + return pList; +} + +/* +** Assign cursors to all tables in a SrcList +*/ +void sqliteSrcListAssignCursors(Parse *pParse, SrcList *pList){ + int i; + for(i=0; inSrc; i++){ + if( pList->a[i].iCursor<0 ){ + pList->a[i].iCursor = pParse->nTab++; + } + } +} + +/* +** Add an alias to the last identifier on the given identifier list. +*/ +void sqliteSrcListAddAlias(SrcList *pList, Token *pToken){ + if( pList && pList->nSrc>0 ){ + int i = pList->nSrc - 1; + sqliteSetNString(&pList->a[i].zAlias, pToken->z, pToken->n, 0); + sqliteDequote(pList->a[i].zAlias); + } +} + +/* +** Delete an IdList. +*/ +void sqliteIdListDelete(IdList *pList){ + int i; + if( pList==0 ) return; + for(i=0; inId; i++){ + sqliteFree(pList->a[i].zName); + } + sqliteFree(pList->a); + sqliteFree(pList); +} + +/* +** Return the index in pList of the identifier named zId. Return -1 +** if not found. +*/ +int sqliteIdListIndex(IdList *pList, const char *zName){ + int i; + if( pList==0 ) return -1; + for(i=0; inId; i++){ + if( sqliteStrICmp(pList->a[i].zName, zName)==0 ) return i; + } + return -1; +} + +/* +** Delete an entire SrcList including all its substructure. +*/ +void sqliteSrcListDelete(SrcList *pList){ + int i; + if( pList==0 ) return; + for(i=0; inSrc; i++){ + sqliteFree(pList->a[i].zDatabase); + sqliteFree(pList->a[i].zName); + sqliteFree(pList->a[i].zAlias); + if( pList->a[i].pTab && pList->a[i].pTab->isTransient ){ + sqliteDeleteTable(0, pList->a[i].pTab); + } + sqliteSelectDelete(pList->a[i].pSelect); + sqliteExprDelete(pList->a[i].pOn); + sqliteIdListDelete(pList->a[i].pUsing); + } + sqliteFree(pList); +} + +/* +** Begin a transaction +*/ +void sqliteBeginTransaction(Parse *pParse, int onError){ + sqlite *db; + + if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return; + if( pParse->nErr || sqlite_malloc_failed ) return; + if( sqliteAuthCheck(pParse, SQLITE_TRANSACTION, "BEGIN", 0, 0) ) return; + if( db->flags & SQLITE_InTrans ){ + sqliteErrorMsg(pParse, "cannot start a transaction within a transaction"); + return; + } + sqliteBeginWriteOperation(pParse, 0, 0); + if( !pParse->explain ){ + db->flags |= SQLITE_InTrans; + db->onError = onError; + } +} + +/* +** Commit a transaction +*/ +void sqliteCommitTransaction(Parse *pParse){ + sqlite *db; + + if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return; + if( pParse->nErr || sqlite_malloc_failed ) return; + if( sqliteAuthCheck(pParse, SQLITE_TRANSACTION, "COMMIT", 0, 0) ) return; + if( (db->flags & SQLITE_InTrans)==0 ){ + sqliteErrorMsg(pParse, "cannot commit - no transaction is active"); + return; + } + if( !pParse->explain ){ + db->flags &= ~SQLITE_InTrans; + } + sqliteEndWriteOperation(pParse); + if( !pParse->explain ){ + db->onError = OE_Default; + } +} + +/* +** Rollback a transaction +*/ +void sqliteRollbackTransaction(Parse *pParse){ + sqlite *db; + Vdbe *v; + + if( pParse==0 || (db=pParse->db)==0 || db->aDb[0].pBt==0 ) return; + if( pParse->nErr || sqlite_malloc_failed ) return; + if( sqliteAuthCheck(pParse, SQLITE_TRANSACTION, "ROLLBACK", 0, 0) ) return; + if( (db->flags & SQLITE_InTrans)==0 ){ + sqliteErrorMsg(pParse, "cannot rollback - no transaction is active"); + return; + } + v = sqliteGetVdbe(pParse); + if( v ){ + sqliteVdbeAddOp(v, OP_Rollback, 0, 0); + } + if( !pParse->explain ){ + db->flags &= ~SQLITE_InTrans; + db->onError = OE_Default; + } +} + +/* +** Generate VDBE code that will verify the schema cookie for all +** named database files. +*/ +void sqliteCodeVerifySchema(Parse *pParse, int iDb){ + sqlite *db = pParse->db; + Vdbe *v = sqliteGetVdbe(pParse); + assert( iDb>=0 && iDbnDb ); + assert( db->aDb[iDb].pBt!=0 ); + if( iDb!=1 && !DbHasProperty(db, iDb, DB_Cookie) ){ + sqliteVdbeAddOp(v, OP_VerifyCookie, iDb, db->aDb[iDb].schema_cookie); + DbSetProperty(db, iDb, DB_Cookie); + } +} + +/* +** Generate VDBE code that prepares for doing an operation that +** might change the database. +** +** This routine starts a new transaction if we are not already within +** a transaction. If we are already within a transaction, then a checkpoint +** is set if the setCheckpoint parameter is true. A checkpoint should +** be set for operations that might fail (due to a constraint) part of +** the way through and which will need to undo some writes without having to +** rollback the whole transaction. For operations where all constraints +** can be checked before any changes are made to the database, it is never +** necessary to undo a write and the checkpoint should not be set. +** +** Only database iDb and the temp database are made writable by this call. +** If iDb==0, then the main and temp databases are made writable. If +** iDb==1 then only the temp database is made writable. If iDb>1 then the +** specified auxiliary database and the temp database are made writable. +*/ +void sqliteBeginWriteOperation(Parse *pParse, int setCheckpoint, int iDb){ + Vdbe *v; + sqlite *db = pParse->db; + if( DbHasProperty(db, iDb, DB_Locked) ) return; + v = sqliteGetVdbe(pParse); + if( v==0 ) return; + if( !db->aDb[iDb].inTrans ){ + sqliteVdbeAddOp(v, OP_Transaction, iDb, 0); + DbSetProperty(db, iDb, DB_Locked); + sqliteCodeVerifySchema(pParse, iDb); + if( iDb!=1 ){ + sqliteBeginWriteOperation(pParse, setCheckpoint, 1); + } + }else if( setCheckpoint ){ + sqliteVdbeAddOp(v, OP_Checkpoint, iDb, 0); + DbSetProperty(db, iDb, DB_Locked); + } +} + +/* +** Generate code that concludes an operation that may have changed +** the database. If a statement transaction was started, then emit +** an OP_Commit that will cause the changes to be committed to disk. +** +** Note that checkpoints are automatically committed at the end of +** a statement. Note also that there can be multiple calls to +** sqliteBeginWriteOperation() but there should only be a single +** call to sqliteEndWriteOperation() at the conclusion of the statement. +*/ +void sqliteEndWriteOperation(Parse *pParse){ + Vdbe *v; + sqlite *db = pParse->db; + if( pParse->trigStack ) return; /* if this is in a trigger */ + v = sqliteGetVdbe(pParse); + if( v==0 ) return; + if( db->flags & SQLITE_InTrans ){ + /* A BEGIN has executed. Do not commit until we see an explicit + ** COMMIT statement. */ + }else{ + sqliteVdbeAddOp(v, OP_Commit, 0, 0); + } +} diff --git a/src/libs/sqlite2/copy.c b/src/libs/sqlite2/copy.c new file mode 100644 index 00000000..a289a7be --- /dev/null +++ b/src/libs/sqlite2/copy.c @@ -0,0 +1,110 @@ +/* +** 2003 April 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to implement the COPY command. +** +** $Id: copy.c 326789 2004-07-07 21:25:56Z pahlibar $ +*/ +#include "sqliteInt.h" + +/* +** The COPY command is for compatibility with PostgreSQL and specificially +** for the ability to read the output of pg_dump. The format is as +** follows: +** +** COPY table FROM file [USING DELIMITERS string] +** +** "table" is an existing table name. We will read lines of code from +** file to fill this table with data. File might be "stdin". The optional +** delimiter string identifies the field separators. The default is a tab. +*/ +void sqliteCopy( + Parse *pParse, /* The parser context */ + SrcList *pTableName, /* The name of the table into which we will insert */ + Token *pFilename, /* The file from which to obtain information */ + Token *pDelimiter, /* Use this as the field delimiter */ + int onError /* What to do if a constraint fails */ +){ + Table *pTab; + int i; + Vdbe *v; + int addr, end; + char *zFile = 0; + const char *zDb; + sqlite *db = pParse->db; + + + if( sqlite_malloc_failed ) goto copy_cleanup; + assert( pTableName->nSrc==1 ); + pTab = sqliteSrcListLookup(pParse, pTableName); + if( pTab==0 || sqliteIsReadOnly(pParse, pTab, 0) ) goto copy_cleanup; + zFile = sqliteStrNDup(pFilename->z, pFilename->n); + sqliteDequote(zFile); + assert( pTab->iDbnDb ); + zDb = db->aDb[pTab->iDb].zName; + if( sqliteAuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, zDb) + || sqliteAuthCheck(pParse, SQLITE_COPY, pTab->zName, zFile, zDb) ){ + goto copy_cleanup; + } + v = sqliteGetVdbe(pParse); + if( v ){ + sqliteBeginWriteOperation(pParse, 1, pTab->iDb); + addr = sqliteVdbeOp3(v, OP_FileOpen, 0, 0, pFilename->z, pFilename->n); + sqliteVdbeDequoteP3(v, addr); + sqliteOpenTableAndIndices(pParse, pTab, 0); + if( db->flags & SQLITE_CountRows ){ + sqliteVdbeAddOp(v, OP_Integer, 0, 0); /* Initialize the row count */ + } + end = sqliteVdbeMakeLabel(v); + addr = sqliteVdbeAddOp(v, OP_FileRead, pTab->nCol, end); + if( pDelimiter ){ + sqliteVdbeChangeP3(v, addr, pDelimiter->z, pDelimiter->n); + sqliteVdbeDequoteP3(v, addr); + }else{ + sqliteVdbeChangeP3(v, addr, "\t", 1); + } + if( pTab->iPKey>=0 ){ + sqliteVdbeAddOp(v, OP_FileColumn, pTab->iPKey, 0); + sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0); + }else{ + sqliteVdbeAddOp(v, OP_NewRecno, 0, 0); + } + for(i=0; inCol; i++){ + if( i==pTab->iPKey ){ + /* The integer primary key column is filled with NULL since its + ** value is always pulled from the record number */ + sqliteVdbeAddOp(v, OP_String, 0, 0); + }else{ + sqliteVdbeAddOp(v, OP_FileColumn, i, 0); + } + } + sqliteGenerateConstraintChecks(pParse, pTab, 0, 0, pTab->iPKey>=0, + 0, onError, addr); + sqliteCompleteInsertion(pParse, pTab, 0, 0, 0, 0, -1); + if( (db->flags & SQLITE_CountRows)!=0 ){ + sqliteVdbeAddOp(v, OP_AddImm, 1, 0); /* Increment row count */ + } + sqliteVdbeAddOp(v, OP_Goto, 0, addr); + sqliteVdbeResolveLabel(v, end); + sqliteVdbeAddOp(v, OP_Noop, 0, 0); + sqliteEndWriteOperation(pParse); + if( db->flags & SQLITE_CountRows ){ + sqliteVdbeAddOp(v, OP_ColumnName, 0, 1); + sqliteVdbeChangeP3(v, -1, "rows inserted", P3_STATIC); + sqliteVdbeAddOp(v, OP_Callback, 1, 0); + } + } + +copy_cleanup: + sqliteSrcListDelete(pTableName); + sqliteFree(zFile); + return; +} diff --git a/src/libs/sqlite2/date.c b/src/libs/sqlite2/date.c new file mode 100644 index 00000000..9198b26f --- /dev/null +++ b/src/libs/sqlite2/date.c @@ -0,0 +1,875 @@ +/* +** 2003 October 31 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the C functions that implement date and time +** functions for SQLite. +** +** There is only one exported symbol in this file - the function +** sqliteRegisterDateTimeFunctions() found at the bottom of the file. +** All other code has file scope. +** +** $Id: date.c 875429 2008-10-24 12:20:41Z cgilles $ +** +** NOTES: +** +** SQLite processes all times and dates as Julian Day numbers. The +** dates and times are stored as the number of days since noon +** in Greenwich on November 24, 4714 B.C. according to the Gregorian +** calendar system. +** +** 1970-01-01 00:00:00 is JD 2440587.5 +** 2000-01-01 00:00:00 is JD 2451544.5 +** +** This implemention requires years to be expressed as a 4-digit number +** which means that only dates between 0000-01-01 and 9999-12-31 can +** be represented, even though julian day numbers allow a much wider +** range of dates. +** +** The Gregorian calendar system is used for all dates and times, +** even those that predate the Gregorian calendar. Historians usually +** use the Julian calendar for dates prior to 1582-10-15 and for some +** dates afterwards, depending on locale. Beware of this difference. +** +** The conversion algorithms are implemented based on descriptions +** in the following text: +** +** Jean Meeus +** Astronomical Algorithms, 2nd Edition, 1998 +** ISBM 0-943396-61-1 +** Willmann-Bell, Inc +** Richmond, Virginia (USA) +*/ +#include "os.h" +#include "sqliteInt.h" +#include +#include +#include +#include + +#ifndef SQLITE_OMIT_DATETIME_FUNCS + +/* +** A structure for holding a single date and time. +*/ +typedef struct DateTime DateTime; +struct DateTime { + double rJD; /* The julian day number */ + int Y, M, D; /* Year, month, and day */ + int h, m; /* Hour and minutes */ + int tz; /* Timezone offset in minutes */ + double s; /* Seconds */ + char validYMD; /* True if Y,M,D are valid */ + char validHMS; /* True if h,m,s are valid */ + char validJD; /* True if rJD is valid */ + char validTZ; /* True if tz is valid */ +}; + + +/* +** Convert zDate into one or more integers. Additional arguments +** come in groups of 5 as follows: +** +** N number of digits in the integer +** min minimum allowed value of the integer +** max maximum allowed value of the integer +** nextC first character after the integer +** pVal where to write the integers value. +** +** Conversions continue until one with nextC==0 is encountered. +** The function returns the number of successful conversions. +*/ +static int getDigits(const char *zDate, ...){ + va_list ap; + int val; + int N; + int min; + int max; + int nextC; + int *pVal; + int cnt = 0; + va_start(ap, zDate); + do{ + N = va_arg(ap, int); + min = va_arg(ap, int); + max = va_arg(ap, int); + nextC = va_arg(ap, int); + pVal = va_arg(ap, int*); + val = 0; + while( N-- ){ + if( !isdigit(*zDate) ){ + return cnt; + } + val = val*10 + *zDate - '0'; + zDate++; + } + if( valmax || (nextC!=0 && nextC!=*zDate) ){ + return cnt; + } + *pVal = val; + zDate++; + cnt++; + }while( nextC ); + return cnt; +} + +/* +** Read text from z[] and convert into a floating point number. Return +** the number of digits converted. +*/ +static int getValue(const char *z, double *pR){ + const char *zEnd; + *pR = sqliteAtoF(z, &zEnd); + return zEnd - z; +} + +/* +** Parse a timezone extension on the end of a date-time. +** The extension is of the form: +** +** (+/-)HH:MM +** +** If the parse is successful, write the number of minutes +** of change in *pnMin and return 0. If a parser error occurs, +** return 0. +** +** A missing specifier is not considered an error. +*/ +static int parseTimezone(const char *zDate, DateTime *p){ + int sgn = 0; + int nHr, nMn; + while( isspace(*zDate) ){ zDate++; } + p->tz = 0; + if( *zDate=='-' ){ + sgn = -1; + }else if( *zDate=='+' ){ + sgn = +1; + }else{ + return *zDate!=0; + } + zDate++; + if( getDigits(zDate, 2, 0, 14, ':', &nHr, 2, 0, 59, 0, &nMn)!=2 ){ + return 1; + } + zDate += 5; + p->tz = sgn*(nMn + nHr*60); + while( isspace(*zDate) ){ zDate++; } + return *zDate!=0; +} + +/* +** Parse times of the form HH:MM or HH:MM:SS or HH:MM:SS.FFFF. +** The HH, MM, and SS must each be exactly 2 digits. The +** fractional seconds FFFF can be one or more digits. +** +** Return 1 if there is a parsing error and 0 on success. +*/ +static int parseHhMmSs(const char *zDate, DateTime *p){ + int h, m, s; + double ms = 0.0; + if( getDigits(zDate, 2, 0, 24, ':', &h, 2, 0, 59, 0, &m)!=2 ){ + return 1; + } + zDate += 5; + if( *zDate==':' ){ + zDate++; + if( getDigits(zDate, 2, 0, 59, 0, &s)!=1 ){ + return 1; + } + zDate += 2; + if( *zDate=='.' && isdigit(zDate[1]) ){ + double rScale = 1.0; + zDate++; + while( isdigit(*zDate) ){ + ms = ms*10.0 + *zDate - '0'; + rScale *= 10.0; + zDate++; + } + ms /= rScale; + } + }else{ + s = 0; + } + p->validJD = 0; + p->validHMS = 1; + p->h = h; + p->m = m; + p->s = s + ms; + if( parseTimezone(zDate, p) ) return 1; + p->validTZ = p->tz!=0; + return 0; +} + +/* +** Convert from YYYY-MM-DD HH:MM:SS to julian day. We always assume +** that the YYYY-MM-DD is according to the Gregorian calendar. +** +** Reference: Meeus page 61 +*/ +static void computeJD(DateTime *p){ + int Y, M, D, A, B, X1, X2; + + if( p->validJD ) return; + if( p->validYMD ){ + Y = p->Y; + M = p->M; + D = p->D; + }else{ + Y = 2000; /* If no YMD specified, assume 2000-Jan-01 */ + M = 1; + D = 1; + } + if( M<=2 ){ + Y--; + M += 12; + } + A = Y/100; + B = 2 - A + (A/4); + X1 = 365.25*(Y+4716); + X2 = 30.6001*(M+1); + p->rJD = X1 + X2 + D + B - 1524.5; + p->validJD = 1; + p->validYMD = 0; + if( p->validHMS ){ + p->rJD += (p->h*3600.0 + p->m*60.0 + p->s)/86400.0; + if( p->validTZ ){ + p->rJD += p->tz*60/86400.0; + p->validHMS = 0; + p->validTZ = 0; + } + } +} + +/* +** Parse dates of the form +** +** YYYY-MM-DD HH:MM:SS.FFF +** YYYY-MM-DD HH:MM:SS +** YYYY-MM-DD HH:MM +** YYYY-MM-DD +** +** Write the result into the DateTime structure and return 0 +** on success and 1 if the input string is not a well-formed +** date. +*/ +static int parseYyyyMmDd(const char *zDate, DateTime *p){ + int Y, M, D, neg; + + if( zDate[0]=='-' ){ + zDate++; + neg = 1; + }else{ + neg = 0; + } + if( getDigits(zDate,4,0,9999,'-',&Y,2,1,12,'-',&M,2,1,31,0,&D)!=3 ){ + return 1; + } + zDate += 10; + while( isspace(*zDate) ){ zDate++; } + if( parseHhMmSs(zDate, p)==0 ){ + /* We got the time */ + }else if( *zDate==0 ){ + p->validHMS = 0; + }else{ + return 1; + } + p->validJD = 0; + p->validYMD = 1; + p->Y = neg ? -Y : Y; + p->M = M; + p->D = D; + if( p->validTZ ){ + computeJD(p); + } + return 0; +} + +/* +** Attempt to parse the given string into a Julian Day Number. Return +** the number of errors. +** +** The following are acceptable forms for the input string: +** +** YYYY-MM-DD HH:MM:SS.FFF +/-HH:MM +** DDDD.DD +** now +** +** In the first form, the +/-HH:MM is always optional. The fractional +** seconds extension (the ".FFF") is optional. The seconds portion +** (":SS.FFF") is option. The year and date can be omitted as long +** as there is a time string. The time string can be omitted as long +** as there is a year and date. +*/ +static int parseDateOrTime(const char *zDate, DateTime *p){ + memset(p, 0, sizeof(*p)); + if( parseYyyyMmDd(zDate,p)==0 ){ + return 0; + }else if( parseHhMmSs(zDate, p)==0 ){ + return 0; + }else if( sqliteStrICmp(zDate,"now")==0){ + double r; + if( sqliteOsCurrentTime(&r)==0 ){ + p->rJD = r; + p->validJD = 1; + return 0; + } + return 1; + }else if( sqliteIsNumber(zDate) ){ + p->rJD = sqliteAtoF(zDate, 0); + p->validJD = 1; + return 0; + } + return 1; +} + +/* +** Compute the Year, Month, and Day from the julian day number. +*/ +static void computeYMD(DateTime *p){ + int Z, A, B, C, D, E, X1; + if( p->validYMD ) return; + if( !p->validJD ){ + p->Y = 2000; + p->M = 1; + p->D = 1; + }else{ + Z = p->rJD + 0.5; + A = (Z - 1867216.25)/36524.25; + A = Z + 1 + A - (A/4); + B = A + 1524; + C = (B - 122.1)/365.25; + D = 365.25*C; + E = (B-D)/30.6001; + X1 = 30.6001*E; + p->D = B - D - X1; + p->M = E<14 ? E-1 : E-13; + p->Y = p->M>2 ? C - 4716 : C - 4715; + } + p->validYMD = 1; +} + +/* +** Compute the Hour, Minute, and Seconds from the julian day number. +*/ +static void computeHMS(DateTime *p){ + int Z, s; + if( p->validHMS ) return; + Z = p->rJD + 0.5; + s = (p->rJD + 0.5 - Z)*86400000.0 + 0.5; + p->s = 0.001*s; + s = p->s; + p->s -= s; + p->h = s/3600; + s -= p->h*3600; + p->m = s/60; + p->s += s - p->m*60; + p->validHMS = 1; +} + +/* +** Compute both YMD and HMS +*/ +static void computeYMD_HMS(DateTime *p){ + computeYMD(p); + computeHMS(p); +} + +/* +** Clear the YMD and HMS and the TZ +*/ +static void clearYMD_HMS_TZ(DateTime *p){ + p->validYMD = 0; + p->validHMS = 0; + p->validTZ = 0; +} + +/* +** Compute the difference (in days) between localtime and UTC (a.k.a. GMT) +** for the time value p where p is in UTC. +*/ +static double localtimeOffset(DateTime *p){ + DateTime x, y; + time_t t; + struct tm *pTm; + x = *p; + computeYMD_HMS(&x); + if( x.Y<1971 || x.Y>=2038 ){ + x.Y = 2000; + x.M = 1; + x.D = 1; + x.h = 0; + x.m = 0; + x.s = 0.0; + } else { + int s = x.s + 0.5; + x.s = s; + } + x.tz = 0; + x.validJD = 0; + computeJD(&x); + t = (x.rJD-2440587.5)*86400.0 + 0.5; + sqliteOsEnterMutex(); + pTm = localtime(&t); + y.Y = pTm->tm_year + 1900; + y.M = pTm->tm_mon + 1; + y.D = pTm->tm_mday; + y.h = pTm->tm_hour; + y.m = pTm->tm_min; + y.s = pTm->tm_sec; + sqliteOsLeaveMutex(); + y.validYMD = 1; + y.validHMS = 1; + y.validJD = 0; + y.validTZ = 0; + computeJD(&y); + return y.rJD - x.rJD; +} + +/* +** Process a modifier to a date-time stamp. The modifiers are +** as follows: +** +** NNN days +** NNN hours +** NNN minutes +** NNN.NNNN seconds +** NNN months +** NNN years +** start of month +** start of year +** start of week +** start of day +** weekday N +** unixepoch +** localtime +** utc +** +** Return 0 on success and 1 if there is any kind of error. +*/ +static int parseModifier(const char *zMod, DateTime *p){ + int rc = 1; + int n; + double r; + char *z, zBuf[30]; + z = zBuf; + for(n=0; nrJD += localtimeOffset(p); + clearYMD_HMS_TZ(p); + rc = 0; + } + break; + } + case 'u': { + /* + ** unixepoch + ** + ** Treat the current value of p->rJD as the number of + ** seconds since 1970. Convert to a real julian day number. + */ + if( strcmp(z, "unixepoch")==0 && p->validJD ){ + p->rJD = p->rJD/86400.0 + 2440587.5; + clearYMD_HMS_TZ(p); + rc = 0; + }else if( strcmp(z, "utc")==0 ){ + double c1; + computeJD(p); + c1 = localtimeOffset(p); + p->rJD -= c1; + clearYMD_HMS_TZ(p); + p->rJD += c1 - localtimeOffset(p); + rc = 0; + } + break; + } + case 'w': { + /* + ** weekday N + ** + ** Move the date to the same time on the next occurrance of + ** weekday N where 0==Sunday, 1==Monday, and so forth. If the + ** date is already on the appropriate weekday, this is a no-op. + */ + if( strncmp(z, "weekday ", 8)==0 && getValue(&z[8],&r)>0 + && (n=r)==r && n>=0 && r<7 ){ + int Z; + computeYMD_HMS(p); + p->validTZ = 0; + p->validJD = 0; + computeJD(p); + Z = p->rJD + 1.5; + Z %= 7; + if( Z>n ) Z -= 7; + p->rJD += n - Z; + clearYMD_HMS_TZ(p); + rc = 0; + } + break; + } + case 's': { + /* + ** start of TTTTT + ** + ** Move the date backwards to the beginning of the current day, + ** or month or year. + */ + if( strncmp(z, "start of ", 9)!=0 ) break; + z += 9; + computeYMD(p); + p->validHMS = 1; + p->h = p->m = 0; + p->s = 0.0; + p->validTZ = 0; + p->validJD = 0; + if( strcmp(z,"month")==0 ){ + p->D = 1; + rc = 0; + }else if( strcmp(z,"year")==0 ){ + computeYMD(p); + p->M = 1; + p->D = 1; + rc = 0; + }else if( strcmp(z,"day")==0 ){ + rc = 0; + } + break; + } + case '+': + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + n = getValue(z, &r); + if( n<=0 ) break; + if( z[n]==':' ){ + /* A modifier of the form (+|-)HH:MM:SS.FFF adds (or subtracts) the + ** specified number of hours, minutes, seconds, and fractional seconds + ** to the time. The ".FFF" may be omitted. The ":SS.FFF" may be + ** omitted. + */ + const char *z2 = z; + DateTime tx; + int day; + if( !isdigit(*z2) ) z2++; + memset(&tx, 0, sizeof(tx)); + if( parseHhMmSs(z2, &tx) ) break; + computeJD(&tx); + tx.rJD -= 0.5; + day = (int)tx.rJD; + tx.rJD -= day; + if( z[0]=='-' ) tx.rJD = -tx.rJD; + computeJD(p); + clearYMD_HMS_TZ(p); + p->rJD += tx.rJD; + rc = 0; + break; + } + z += n; + while( isspace(z[0]) ) z++; + n = strlen(z); + if( n>10 || n<3 ) break; + if( z[n-1]=='s' ){ z[n-1] = 0; n--; } + computeJD(p); + rc = 0; + if( n==3 && strcmp(z,"day")==0 ){ + p->rJD += r; + }else if( n==4 && strcmp(z,"hour")==0 ){ + p->rJD += r/24.0; + }else if( n==6 && strcmp(z,"minute")==0 ){ + p->rJD += r/(24.0*60.0); + }else if( n==6 && strcmp(z,"second")==0 ){ + p->rJD += r/(24.0*60.0*60.0); + }else if( n==5 && strcmp(z,"month")==0 ){ + int x, y; + computeYMD_HMS(p); + p->M += r; + x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; + p->Y += x; + p->M -= x*12; + p->validJD = 0; + computeJD(p); + y = r; + if( y!=r ){ + p->rJD += (r - y)*30.0; + } + }else if( n==4 && strcmp(z,"year")==0 ){ + computeYMD_HMS(p); + p->Y += r; + p->validJD = 0; + computeJD(p); + }else{ + rc = 1; + } + clearYMD_HMS_TZ(p); + break; + } + default: { + break; + } + } + return rc; +} + +/* +** Process time function arguments. argv[0] is a date-time stamp. +** argv[1] and following are modifiers. Parse them all and write +** the resulting time into the DateTime structure p. Return 0 +** on success and 1 if there are any errors. +*/ +static int isDate(int argc, const char **argv, DateTime *p){ + int i; + if( argc==0 ) return 1; + if( argv[0]==0 || parseDateOrTime(argv[0], p) ) return 1; + for(i=1; izErrMsg and return NULL. If all tables +** are found, return a pointer to the last table. +*/ +Table *sqliteSrcListLookup(Parse *pParse, SrcList *pSrc){ + Table *pTab = 0; + int i; + for(i=0; inSrc; i++){ + const char *zTab = pSrc->a[i].zName; + const char *zDb = pSrc->a[i].zDatabase; + pTab = sqliteLocateTable(pParse, zTab, zDb); + pSrc->a[i].pTab = pTab; + } + return pTab; +} + +/* +** Check to make sure the given table is writable. If it is not +** writable, generate an error message and return 1. If it is +** writable return 0; +*/ +int sqliteIsReadOnly(Parse *pParse, Table *pTab, int viewOk){ + if( pTab->readOnly ){ + sqliteErrorMsg(pParse, "table %s may not be modified", pTab->zName); + return 1; + } + if( !viewOk && pTab->pSelect ){ + sqliteErrorMsg(pParse, "cannot modify %s because it is a view",pTab->zName); + return 1; + } + return 0; +} + +/* +** Process a DELETE FROM statement. +*/ +void sqliteDeleteFrom( + Parse *pParse, /* The parser context */ + SrcList *pTabList, /* The table from which we should delete things */ + Expr *pWhere /* The WHERE clause. May be null */ +){ + Vdbe *v; /* The virtual database engine */ + Table *pTab; /* The table from which records will be deleted */ + const char *zDb; /* Name of database holding pTab */ + int end, addr; /* A couple addresses of generated code */ + int i; /* Loop counter */ + WhereInfo *pWInfo; /* Information about the WHERE clause */ + Index *pIdx; /* For looping over indices of the table */ + int iCur; /* VDBE Cursor number for pTab */ + sqlite *db; /* Main database structure */ + int isView; /* True if attempting to delete from a view */ + AuthContext sContext; /* Authorization context */ + + int row_triggers_exist = 0; /* True if any triggers exist */ + int before_triggers; /* True if there are BEFORE triggers */ + int after_triggers; /* True if there are AFTER triggers */ + int oldIdx = -1; /* Cursor for the OLD table of AFTER triggers */ + + sContext.pParse = 0; + if( pParse->nErr || sqlite_malloc_failed ){ + pTabList = 0; + goto delete_from_cleanup; + } + db = pParse->db; + assert( pTabList->nSrc==1 ); + + /* Locate the table which we want to delete. This table has to be + ** put in an SrcList structure because some of the subroutines we + ** will be calling are designed to work with multiple tables and expect + ** an SrcList* parameter instead of just a Table* parameter. + */ + pTab = sqliteSrcListLookup(pParse, pTabList); + if( pTab==0 ) goto delete_from_cleanup; + before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, + TK_DELETE, TK_BEFORE, TK_ROW, 0); + after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, + TK_DELETE, TK_AFTER, TK_ROW, 0); + row_triggers_exist = before_triggers || after_triggers; + isView = pTab->pSelect!=0; + if( sqliteIsReadOnly(pParse, pTab, before_triggers) ){ + goto delete_from_cleanup; + } + assert( pTab->iDbnDb ); + zDb = db->aDb[pTab->iDb].zName; + if( sqliteAuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb) ){ + goto delete_from_cleanup; + } + + /* If pTab is really a view, make sure it has been initialized. + */ + if( isView && sqliteViewGetColumnNames(pParse, pTab) ){ + goto delete_from_cleanup; + } + + /* Allocate a cursor used to store the old.* data for a trigger. + */ + if( row_triggers_exist ){ + oldIdx = pParse->nTab++; + } + + /* Resolve the column names in all the expressions. + */ + assert( pTabList->nSrc==1 ); + iCur = pTabList->a[0].iCursor = pParse->nTab++; + if( pWhere ){ + if( sqliteExprResolveIds(pParse, pTabList, 0, pWhere) ){ + goto delete_from_cleanup; + } + if( sqliteExprCheck(pParse, pWhere, 0, 0) ){ + goto delete_from_cleanup; + } + } + + /* Start the view context + */ + if( isView ){ + sqliteAuthContextPush(pParse, &sContext, pTab->zName); + } + + /* Begin generating code. + */ + v = sqliteGetVdbe(pParse); + if( v==0 ){ + goto delete_from_cleanup; + } + sqliteBeginWriteOperation(pParse, row_triggers_exist, pTab->iDb); + + /* If we are trying to delete from a view, construct that view into + ** a temporary table. + */ + if( isView ){ + Select *pView = sqliteSelectDup(pTab->pSelect); + sqliteSelect(pParse, pView, SRT_TempTable, iCur, 0, 0, 0); + sqliteSelectDelete(pView); + } + + /* Initialize the counter of the number of rows deleted, if + ** we are counting rows. + */ + if( db->flags & SQLITE_CountRows ){ + sqliteVdbeAddOp(v, OP_Integer, 0, 0); + } + + /* Special case: A DELETE without a WHERE clause deletes everything. + ** It is easier just to erase the whole table. Note, however, that + ** this means that the row change count will be incorrect. + */ + if( pWhere==0 && !row_triggers_exist ){ + if( db->flags & SQLITE_CountRows ){ + /* If counting rows deleted, just count the total number of + ** entries in the table. */ + int endOfLoop = sqliteVdbeMakeLabel(v); + int addr; + if( !isView ){ + sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqliteVdbeAddOp(v, OP_OpenRead, iCur, pTab->tnum); + } + sqliteVdbeAddOp(v, OP_Rewind, iCur, sqliteVdbeCurrentAddr(v)+2); + addr = sqliteVdbeAddOp(v, OP_AddImm, 1, 0); + sqliteVdbeAddOp(v, OP_Next, iCur, addr); + sqliteVdbeResolveLabel(v, endOfLoop); + sqliteVdbeAddOp(v, OP_Close, iCur, 0); + } + if( !isView ){ + sqliteVdbeAddOp(v, OP_Clear, pTab->tnum, pTab->iDb); + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + sqliteVdbeAddOp(v, OP_Clear, pIdx->tnum, pIdx->iDb); + } + } + } + + /* The usual case: There is a WHERE clause so we have to scan through + ** the table and pick which records to delete. + */ + else{ + /* Begin the database scan + */ + pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 1, 0); + if( pWInfo==0 ) goto delete_from_cleanup; + + /* Remember the key of every item to be deleted. + */ + sqliteVdbeAddOp(v, OP_ListWrite, 0, 0); + if( db->flags & SQLITE_CountRows ){ + sqliteVdbeAddOp(v, OP_AddImm, 1, 0); + } + + /* End the database scan loop. + */ + sqliteWhereEnd(pWInfo); + + /* Open the pseudo-table used to store OLD if there are triggers. + */ + if( row_triggers_exist ){ + sqliteVdbeAddOp(v, OP_OpenPseudo, oldIdx, 0); + } + + /* Delete every item whose key was written to the list during the + ** database scan. We have to delete items after the scan is complete + ** because deleting an item can change the scan order. + */ + sqliteVdbeAddOp(v, OP_ListRewind, 0, 0); + end = sqliteVdbeMakeLabel(v); + + /* This is the beginning of the delete loop when there are + ** row triggers. + */ + if( row_triggers_exist ){ + addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end); + sqliteVdbeAddOp(v, OP_Dup, 0, 0); + if( !isView ){ + sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqliteVdbeAddOp(v, OP_OpenRead, iCur, pTab->tnum); + } + sqliteVdbeAddOp(v, OP_MoveTo, iCur, 0); + + sqliteVdbeAddOp(v, OP_Recno, iCur, 0); + sqliteVdbeAddOp(v, OP_RowData, iCur, 0); + sqliteVdbeAddOp(v, OP_PutIntKey, oldIdx, 0); + if( !isView ){ + sqliteVdbeAddOp(v, OP_Close, iCur, 0); + } + + sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_BEFORE, pTab, -1, + oldIdx, (pParse->trigStack)?pParse->trigStack->orconf:OE_Default, + addr); + } + + if( !isView ){ + /* Open cursors for the table we are deleting from and all its + ** indices. If there are row triggers, this happens inside the + ** OP_ListRead loop because the cursor have to all be closed + ** before the trigger fires. If there are no row triggers, the + ** cursors are opened only once on the outside the loop. + */ + pParse->nTab = iCur + 1; + sqliteOpenTableAndIndices(pParse, pTab, iCur); + + /* This is the beginning of the delete loop when there are no + ** row triggers */ + if( !row_triggers_exist ){ + addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end); + } + + /* Delete the row */ + sqliteGenerateRowDelete(db, v, pTab, iCur, pParse->trigStack==0); + } + + /* If there are row triggers, close all cursors then invoke + ** the AFTER triggers + */ + if( row_triggers_exist ){ + if( !isView ){ + for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + sqliteVdbeAddOp(v, OP_Close, iCur + i, pIdx->tnum); + } + sqliteVdbeAddOp(v, OP_Close, iCur, 0); + } + sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_AFTER, pTab, -1, + oldIdx, (pParse->trigStack)?pParse->trigStack->orconf:OE_Default, + addr); + } + + /* End of the delete loop */ + sqliteVdbeAddOp(v, OP_Goto, 0, addr); + sqliteVdbeResolveLabel(v, end); + sqliteVdbeAddOp(v, OP_ListReset, 0, 0); + + /* Close the cursors after the loop if there are no row triggers */ + if( !row_triggers_exist ){ + for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + sqliteVdbeAddOp(v, OP_Close, iCur + i, pIdx->tnum); + } + sqliteVdbeAddOp(v, OP_Close, iCur, 0); + pParse->nTab = iCur; + } + } + sqliteVdbeAddOp(v, OP_SetCounts, 0, 0); + sqliteEndWriteOperation(pParse); + + /* + ** Return the number of rows that were deleted. + */ + if( db->flags & SQLITE_CountRows ){ + sqliteVdbeAddOp(v, OP_ColumnName, 0, 1); + sqliteVdbeChangeP3(v, -1, "rows deleted", P3_STATIC); + sqliteVdbeAddOp(v, OP_Callback, 1, 0); + } + +delete_from_cleanup: + sqliteAuthContextPop(&sContext); + sqliteSrcListDelete(pTabList); + sqliteExprDelete(pWhere); + return; +} + +/* +** This routine generates VDBE code that causes a single row of a +** single table to be deleted. +** +** The VDBE must be in a particular state when this routine is called. +** These are the requirements: +** +** 1. A read/write cursor pointing to pTab, the table containing the row +** to be deleted, must be opened as cursor number "base". +** +** 2. Read/write cursors for all indices of pTab must be open as +** cursor number base+i for the i-th index. +** +** 3. The record number of the row to be deleted must be on the top +** of the stack. +** +** This routine pops the top of the stack to remove the record number +** and then generates code to remove both the table record and all index +** entries that point to that record. +*/ +void sqliteGenerateRowDelete( + sqlite *db, /* The database containing the index */ + Vdbe *v, /* Generate code into this VDBE */ + Table *pTab, /* Table containing the row to be deleted */ + int iCur, /* Cursor number for the table */ + int count /* Increment the row change counter */ +){ + int addr; + addr = sqliteVdbeAddOp(v, OP_NotExists, iCur, 0); + sqliteGenerateRowIndexDelete(db, v, pTab, iCur, 0); + sqliteVdbeAddOp(v, OP_Delete, iCur, + (count?OPFLAG_NCHANGE:0) | OPFLAG_CSCHANGE); + sqliteVdbeChangeP2(v, addr, sqliteVdbeCurrentAddr(v)); +} + +/* +** This routine generates VDBE code that causes the deletion of all +** index entries associated with a single row of a single table. +** +** The VDBE must be in a particular state when this routine is called. +** These are the requirements: +** +** 1. A read/write cursor pointing to pTab, the table containing the row +** to be deleted, must be opened as cursor number "iCur". +** +** 2. Read/write cursors for all indices of pTab must be open as +** cursor number iCur+i for the i-th index. +** +** 3. The "iCur" cursor must be pointing to the row that is to be +** deleted. +*/ +void sqliteGenerateRowIndexDelete( + sqlite *db, /* The database containing the index */ + Vdbe *v, /* Generate code into this VDBE */ + Table *pTab, /* Table containing the row to be deleted */ + int iCur, /* Cursor number for the table */ + char *aIdxUsed /* Only delete if aIdxUsed!=0 && aIdxUsed[i]!=0 */ +){ + int i; + Index *pIdx; + + for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ + int j; + if( aIdxUsed!=0 && aIdxUsed[i-1]==0 ) continue; + sqliteVdbeAddOp(v, OP_Recno, iCur, 0); + for(j=0; jnColumn; j++){ + int idx = pIdx->aiColumn[j]; + if( idx==pTab->iPKey ){ + sqliteVdbeAddOp(v, OP_Dup, j, 0); + }else{ + sqliteVdbeAddOp(v, OP_Column, iCur, idx); + } + } + sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0); + if( db->file_format>=4 ) sqliteAddIdxKeyType(v, pIdx); + sqliteVdbeAddOp(v, OP_IdxDelete, iCur+i, 0); + } +} diff --git a/src/libs/sqlite2/encode.c b/src/libs/sqlite2/encode.c new file mode 100644 index 00000000..7799b8b0 --- /dev/null +++ b/src/libs/sqlite2/encode.c @@ -0,0 +1,257 @@ +/* +** 2002 April 25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains helper routines used to translate binary data into +** a null-terminated string (suitable for use in SQLite) and back again. +** These are convenience routines for use by people who want to store binary +** data in an SQLite database. The code in this file is not used by any other +** part of the SQLite library. +** +** $Id: encode.c 875429 2008-10-24 12:20:41Z cgilles $ +*/ +#include +#include + +/* +** How This Encoder Works +** +** The output is allowed to contain any character except 0x27 (') and +** 0x00. This is accomplished by using an escape character to encode +** 0x27 and 0x00 as a two-byte sequence. The escape character is always +** 0x01. An 0x00 is encoded as the two byte sequence 0x01 0x01. The +** 0x27 character is encoded as the two byte sequence 0x01 0x28. Finally, +** the escape character itself is encoded as the two-character sequence +** 0x01 0x02. +** +** To summarize, the encoder works by using an escape sequences as follows: +** +** 0x00 -> 0x01 0x01 +** 0x01 -> 0x01 0x02 +** 0x27 -> 0x01 0x28 +** +** If that were all the encoder did, it would work, but in certain cases +** it could double the size of the encoded string. For example, to +** encode a string of 100 0x27 characters would require 100 instances of +** the 0x01 0x03 escape sequence resulting in a 200-character output. +** We would prefer to keep the size of the encoded string smaller than +** this. +** +** To minimize the encoding size, we first add a fixed offset value to each +** byte in the sequence. The addition is modulo 256. (That is to say, if +** the sum of the original character value and the offset exceeds 256, then +** the higher order bits are truncated.) The offset is chosen to minimize +** the number of characters in the string that need to be escaped. For +** example, in the case above where the string was composed of 100 0x27 +** characters, the offset might be 0x01. Each of the 0x27 characters would +** then be converted into an 0x28 character which would not need to be +** escaped at all and so the 100 character input string would be converted +** into just 100 characters of output. Actually 101 characters of output - +** we have to record the offset used as the first byte in the sequence so +** that the string can be decoded. Since the offset value is stored as +** part of the output string and the output string is not allowed to contain +** characters 0x00 or 0x27, the offset cannot be 0x00 or 0x27. +** +** Here, then, are the encoding steps: +** +** (1) Choose an offset value and make it the first character of +** output. +** +** (2) Copy each input character into the output buffer, one by +** one, adding the offset value as you copy. +** +** (3) If the value of an input character plus offset is 0x00, replace +** that one character by the two-character sequence 0x01 0x01. +** If the sum is 0x01, replace it with 0x01 0x02. If the sum +** is 0x27, replace it with 0x01 0x03. +** +** (4) Put a 0x00 terminator at the end of the output. +** +** Decoding is obvious: +** +** (5) Copy encoded characters except the first into the decode +** buffer. Set the first encoded character aside for use as +** the offset in step 7 below. +** +** (6) Convert each 0x01 0x01 sequence into a single character 0x00. +** Convert 0x01 0x02 into 0x01. Convert 0x01 0x28 into 0x27. +** +** (7) Subtract the offset value that was the first character of +** the encoded buffer from all characters in the output buffer. +** +** The only tricky part is step (1) - how to compute an offset value to +** minimize the size of the output buffer. This is accomplished by testing +** all offset values and picking the one that results in the fewest number +** of escapes. To do that, we first scan the entire input and count the +** number of occurances of each character value in the input. Suppose +** the number of 0x00 characters is N(0), the number of occurances of 0x01 +** is N(1), and so forth up to the number of occurances of 0xff is N(255). +** An offset of 0 is not allowed so we don't have to test it. The number +** of escapes required for an offset of 1 is N(1)+N(2)+N(40). The number +** of escapes required for an offset of 2 is N(2)+N(3)+N(41). And so forth. +** In this way we find the offset that gives the minimum number of escapes, +** and thus minimizes the length of the output string. +*/ + +/* +** Encode a binary buffer "in" of size n bytes so that it contains +** no instances of characters '\'' or '\000'. The output is +** null-terminated and can be used as a string value in an INSERT +** or UPDATE statement. Use sqlite_decode_binary() to convert the +** string back into its original binary. +** +** The result is written into a preallocated output buffer "out". +** "out" must be able to hold at least 2 +(257*n)/254 bytes. +** In other words, the output will be expanded by as much as 3 +** bytes for every 254 bytes of input plus 2 bytes of fixed overhead. +** (This is approximately 2 + 1.0118*n or about a 1.2% size increase.) +** +** The return value is the number of characters in the encoded +** string, excluding the "\000" terminator. +** +** If out==NULL then no output is generated but the routine still returns +** the number of characters that would have been generated if out had +** not been NULL. +*/ +int sqlite_encode_binary(const unsigned char *in, int n, unsigned char *out){ + int i, j, e, m; + unsigned char x; + int cnt[256]; + if( n<=0 ){ + if( out ){ + out[0] = 'x'; + out[1] = 0; + } + return 1; + } + memset(cnt, 0, sizeof(cnt)); + for(i=n-1; i>=0; i--){ cnt[in[i]]++; } + m = n; + for(i=1; i<256; i++){ + int sum; + if( i=='\'' ) continue; + sum = cnt[i] + cnt[(i+1)&0xff] + cnt[(i+'\'')&0xff]; + if( sum +/* +** The subroutines above are not tested by the usual test suite. To test +** these routines, compile just this one file with a -DENCODER_TEST=1 option +** and run the result. +*/ +int main(int argc, char **argv){ + int i, j, n, m, nOut, nByteIn, nByteOut; + unsigned char in[30000]; + unsigned char out[33000]; + + nByteIn = nByteOut = 0; + for(i=0; i%d (max %d)", n, strlen(out)+1, m); + if( strlen(out)+1>m ){ + printf(" ERROR output too big\n"); + exit(1); + } + for(j=0; out[j]; j++){ + if( out[j]=='\'' ){ + printf(" ERROR contains (')\n"); + exit(1); + } + } + j = sqlite_decode_binary(out, out); + if( j!=n ){ + printf(" ERROR decode size %d\n", j); + exit(1); + } + if( memcmp(in, out, n)!=0 ){ + printf(" ERROR decode mismatch\n"); + exit(1); + } + printf(" OK\n"); + } + fprintf(stderr,"Finished. Total encoding: %d->%d bytes\n", + nByteIn, nByteOut); + fprintf(stderr,"Avg size increase: %.3f%%\n", + (nByteOut-nByteIn)*100.0/(double)nByteIn); +} +#endif /* ENCODER_TEST */ diff --git a/src/libs/sqlite2/expr.c b/src/libs/sqlite2/expr.c new file mode 100644 index 00000000..af4aa596 --- /dev/null +++ b/src/libs/sqlite2/expr.c @@ -0,0 +1,1662 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains routines used for analyzing expressions and +** for generating VDBE code that evaluates expressions in SQLite. +** +** $Id: expr.c 875429 2008-10-24 12:20:41Z cgilles $ +*/ +#include "sqliteInt.h" +#include + +/* +** Construct a new expression node and return a pointer to it. Memory +** for this node is obtained from sqliteMalloc(). The calling function +** is responsible for making sure the node eventually gets freed. +*/ +Expr *sqliteExpr(int op, Expr *pLeft, Expr *pRight, Token *pToken){ + Expr *pNew; + pNew = sqliteMalloc( sizeof(Expr) ); + if( pNew==0 ){ + /* When malloc fails, we leak memory from pLeft and pRight */ + return 0; + } + pNew->op = op; + pNew->pLeft = pLeft; + pNew->pRight = pRight; + if( pToken ){ + assert( pToken->dyn==0 ); + pNew->token = *pToken; + pNew->span = *pToken; + }else{ + assert( pNew->token.dyn==0 ); + assert( pNew->token.z==0 ); + assert( pNew->token.n==0 ); + if( pLeft && pRight ){ + sqliteExprSpan(pNew, &pLeft->span, &pRight->span); + }else{ + pNew->span = pNew->token; + } + } + return pNew; +} + +/* +** Set the Expr.span field of the given expression to span all +** text between the two given tokens. +*/ +void sqliteExprSpan(Expr *pExpr, Token *pLeft, Token *pRight){ + assert( pRight!=0 ); + assert( pLeft!=0 ); + /* Note: pExpr might be NULL due to a prior malloc failure */ + if( pExpr && pRight->z && pLeft->z ){ + if( pLeft->dyn==0 && pRight->dyn==0 ){ + pExpr->span.z = pLeft->z; + pExpr->span.n = pRight->n + Addr(pRight->z) - Addr(pLeft->z); + }else{ + pExpr->span.z = 0; + } + } +} + +/* +** Construct a new expression node for a function with multiple +** arguments. +*/ +Expr *sqliteExprFunction(ExprList *pList, Token *pToken){ + Expr *pNew; + pNew = sqliteMalloc( sizeof(Expr) ); + if( pNew==0 ){ + /* sqliteExprListDelete(pList); // Leak pList when malloc fails */ + return 0; + } + pNew->op = TK_FUNCTION; + pNew->pList = pList; + if( pToken ){ + assert( pToken->dyn==0 ); + pNew->token = *pToken; + }else{ + pNew->token.z = 0; + } + pNew->span = pNew->token; + return pNew; +} + +/* +** Recursively delete an expression tree. +*/ +void sqliteExprDelete(Expr *p){ + if( p==0 ) return; + if( p->span.dyn ) sqliteFree((char*)p->span.z); + if( p->token.dyn ) sqliteFree((char*)p->token.z); + sqliteExprDelete(p->pLeft); + sqliteExprDelete(p->pRight); + sqliteExprListDelete(p->pList); + sqliteSelectDelete(p->pSelect); + sqliteFree(p); +} + + +/* +** The following group of routines make deep copies of expressions, +** expression lists, ID lists, and select statements. The copies can +** be deleted (by being passed to their respective ...Delete() routines) +** without effecting the originals. +** +** The expression list, ID, and source lists return by sqliteExprListDup(), +** sqliteIdListDup(), and sqliteSrcListDup() can not be further expanded +** by subsequent calls to sqlite*ListAppend() routines. +** +** Any tables that the SrcList might point to are not duplicated. +*/ +Expr *sqliteExprDup(Expr *p){ + Expr *pNew; + if( p==0 ) return 0; + pNew = sqliteMallocRaw( sizeof(*p) ); + if( pNew==0 ) return 0; + memcpy(pNew, p, sizeof(*pNew)); + if( p->token.z!=0 ){ + pNew->token.z = sqliteStrNDup(p->token.z, p->token.n); + pNew->token.dyn = 1; + }else{ + assert( pNew->token.z==0 ); + } + pNew->span.z = 0; + pNew->pLeft = sqliteExprDup(p->pLeft); + pNew->pRight = sqliteExprDup(p->pRight); + pNew->pList = sqliteExprListDup(p->pList); + pNew->pSelect = sqliteSelectDup(p->pSelect); + return pNew; +} +void sqliteTokenCopy(Token *pTo, Token *pFrom){ + if( pTo->dyn ) sqliteFree((char*)pTo->z); + if( pFrom->z ){ + pTo->n = pFrom->n; + pTo->z = sqliteStrNDup(pFrom->z, pFrom->n); + pTo->dyn = 1; + }else{ + pTo->z = 0; + } +} +ExprList *sqliteExprListDup(ExprList *p){ + ExprList *pNew; + struct ExprList_item *pItem; + int i; + if( p==0 ) return 0; + pNew = sqliteMalloc( sizeof(*pNew) ); + if( pNew==0 ) return 0; + pNew->nExpr = pNew->nAlloc = p->nExpr; + pNew->a = pItem = sqliteMalloc( p->nExpr*sizeof(p->a[0]) ); + if( pItem==0 ){ + sqliteFree(pNew); + return 0; + } + for(i=0; inExpr; i++, pItem++){ + Expr *pNewExpr, *pOldExpr; + pItem->pExpr = pNewExpr = sqliteExprDup(pOldExpr = p->a[i].pExpr); + if( pOldExpr->span.z!=0 && pNewExpr ){ + /* Always make a copy of the span for top-level expressions in the + ** expression list. The logic in SELECT processing that determines + ** the names of columns in the result set needs this information */ + sqliteTokenCopy(&pNewExpr->span, &pOldExpr->span); + } + assert( pNewExpr==0 || pNewExpr->span.z!=0 + || pOldExpr->span.z==0 || sqlite_malloc_failed ); + pItem->zName = sqliteStrDup(p->a[i].zName); + pItem->sortOrder = p->a[i].sortOrder; + pItem->isAgg = p->a[i].isAgg; + pItem->done = 0; + } + return pNew; +} +SrcList *sqliteSrcListDup(SrcList *p){ + SrcList *pNew; + int i; + int nByte; + if( p==0 ) return 0; + nByte = sizeof(*p) + (p->nSrc>0 ? sizeof(p->a[0]) * (p->nSrc-1) : 0); + pNew = sqliteMallocRaw( nByte ); + if( pNew==0 ) return 0; + pNew->nSrc = pNew->nAlloc = p->nSrc; + for(i=0; inSrc; i++){ + struct SrcList_item *pNewItem = &pNew->a[i]; + struct SrcList_item *pOldItem = &p->a[i]; + pNewItem->zDatabase = sqliteStrDup(pOldItem->zDatabase); + pNewItem->zName = sqliteStrDup(pOldItem->zName); + pNewItem->zAlias = sqliteStrDup(pOldItem->zAlias); + pNewItem->jointype = pOldItem->jointype; + pNewItem->iCursor = pOldItem->iCursor; + pNewItem->pTab = 0; + pNewItem->pSelect = sqliteSelectDup(pOldItem->pSelect); + pNewItem->pOn = sqliteExprDup(pOldItem->pOn); + pNewItem->pUsing = sqliteIdListDup(pOldItem->pUsing); + } + return pNew; +} +IdList *sqliteIdListDup(IdList *p){ + IdList *pNew; + int i; + if( p==0 ) return 0; + pNew = sqliteMallocRaw( sizeof(*pNew) ); + if( pNew==0 ) return 0; + pNew->nId = pNew->nAlloc = p->nId; + pNew->a = sqliteMallocRaw( p->nId*sizeof(p->a[0]) ); + if( pNew->a==0 ) return 0; + for(i=0; inId; i++){ + struct IdList_item *pNewItem = &pNew->a[i]; + struct IdList_item *pOldItem = &p->a[i]; + pNewItem->zName = sqliteStrDup(pOldItem->zName); + pNewItem->idx = pOldItem->idx; + } + return pNew; +} +Select *sqliteSelectDup(Select *p){ + Select *pNew; + if( p==0 ) return 0; + pNew = sqliteMallocRaw( sizeof(*p) ); + if( pNew==0 ) return 0; + pNew->isDistinct = p->isDistinct; + pNew->pEList = sqliteExprListDup(p->pEList); + pNew->pSrc = sqliteSrcListDup(p->pSrc); + pNew->pWhere = sqliteExprDup(p->pWhere); + pNew->pGroupBy = sqliteExprListDup(p->pGroupBy); + pNew->pHaving = sqliteExprDup(p->pHaving); + pNew->pOrderBy = sqliteExprListDup(p->pOrderBy); + pNew->op = p->op; + pNew->pPrior = sqliteSelectDup(p->pPrior); + pNew->nLimit = p->nLimit; + pNew->nOffset = p->nOffset; + pNew->zSelect = 0; + pNew->iLimit = -1; + pNew->iOffset = -1; + return pNew; +} + + +/* +** Add a new element to the end of an expression list. If pList is +** initially NULL, then create a new expression list. +*/ +ExprList *sqliteExprListAppend(ExprList *pList, Expr *pExpr, Token *pName){ + if( pList==0 ){ + pList = sqliteMalloc( sizeof(ExprList) ); + if( pList==0 ){ + /* sqliteExprDelete(pExpr); // Leak memory if malloc fails */ + return 0; + } + assert( pList->nAlloc==0 ); + } + if( pList->nAlloc<=pList->nExpr ){ + pList->nAlloc = pList->nAlloc*2 + 4; + pList->a = sqliteRealloc(pList->a, pList->nAlloc*sizeof(pList->a[0])); + if( pList->a==0 ){ + /* sqliteExprDelete(pExpr); // Leak memory if malloc fails */ + pList->nExpr = pList->nAlloc = 0; + return pList; + } + } + assert( pList->a!=0 ); + if( pExpr || pName ){ + struct ExprList_item *pItem = &pList->a[pList->nExpr++]; + memset(pItem, 0, sizeof(*pItem)); + pItem->pExpr = pExpr; + if( pName ){ + sqliteSetNString(&pItem->zName, pName->z, pName->n, 0); + sqliteDequote(pItem->zName); + } + } + return pList; +} + +/* +** Delete an entire expression list. +*/ +void sqliteExprListDelete(ExprList *pList){ + int i; + if( pList==0 ) return; + assert( pList->a!=0 || (pList->nExpr==0 && pList->nAlloc==0) ); + assert( pList->nExpr<=pList->nAlloc ); + for(i=0; inExpr; i++){ + sqliteExprDelete(pList->a[i].pExpr); + sqliteFree(pList->a[i].zName); + } + sqliteFree(pList->a); + sqliteFree(pList); +} + +/* +** Walk an expression tree. Return 1 if the expression is constant +** and 0 if it involves variables. +** +** For the purposes of this function, a double-quoted string (ex: "abc") +** is considered a variable but a single-quoted string (ex: 'abc') is +** a constant. +*/ +int sqliteExprIsConstant(Expr *p){ + switch( p->op ){ + case TK_ID: + case TK_COLUMN: + case TK_DOT: + case TK_FUNCTION: + return 0; + case TK_NULL: + case TK_STRING: + case TK_INTEGER: + case TK_FLOAT: + case TK_VARIABLE: + return 1; + default: { + if( p->pLeft && !sqliteExprIsConstant(p->pLeft) ) return 0; + if( p->pRight && !sqliteExprIsConstant(p->pRight) ) return 0; + if( p->pList ){ + int i; + for(i=0; ipList->nExpr; i++){ + if( !sqliteExprIsConstant(p->pList->a[i].pExpr) ) return 0; + } + } + return p->pLeft!=0 || p->pRight!=0 || (p->pList && p->pList->nExpr>0); + } + } + return 0; +} + +/* +** If the given expression codes a constant integer that is small enough +** to fit in a 32-bit integer, return 1 and put the value of the integer +** in *pValue. If the expression is not an integer or if it is too big +** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged. +*/ +int sqliteExprIsInteger(Expr *p, int *pValue){ + switch( p->op ){ + case TK_INTEGER: { + if( sqliteFitsIn32Bits(p->token.z) ){ + *pValue = atoi(p->token.z); + return 1; + } + break; + } + case TK_STRING: { + const char *z = p->token.z; + int n = p->token.n; + if( n>0 && z[0]=='-' ){ z++; n--; } + while( n>0 && *z && isdigit(*z) ){ z++; n--; } + if( n==0 && sqliteFitsIn32Bits(p->token.z) ){ + *pValue = atoi(p->token.z); + return 1; + } + break; + } + case TK_UPLUS: { + return sqliteExprIsInteger(p->pLeft, pValue); + } + case TK_UMINUS: { + int v; + if( sqliteExprIsInteger(p->pLeft, &v) ){ + *pValue = -v; + return 1; + } + break; + } + default: break; + } + return 0; +} + +/* +** Return TRUE if the given string is a row-id column name. +*/ +int sqliteIsRowid(const char *z){ + if( sqliteStrICmp(z, "_ROWID_")==0 ) return 1; + if( sqliteStrICmp(z, "ROWID")==0 ) return 1; + if( sqliteStrICmp(z, "OID")==0 ) return 1; + return 0; +} + +/* +** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up +** that name in the set of source tables in pSrcList and make the pExpr +** expression node refer back to that source column. The following changes +** are made to pExpr: +** +** pExpr->iDb Set the index in db->aDb[] of the database holding +** the table. +** pExpr->iTable Set to the cursor number for the table obtained +** from pSrcList. +** pExpr->iColumn Set to the column number within the table. +** pExpr->dataType Set to the appropriate data type for the column. +** pExpr->op Set to TK_COLUMN. +** pExpr->pLeft Any expression this points to is deleted +** pExpr->pRight Any expression this points to is deleted. +** +** The pDbToken is the name of the database (the "X"). This value may be +** NULL meaning that name is of the form Y.Z or Z. Any available database +** can be used. The pTableToken is the name of the table (the "Y"). This +** value can be NULL if pDbToken is also NULL. If pTableToken is NULL it +** means that the form of the name is Z and that columns from any table +** can be used. +** +** If the name cannot be resolved unambiguously, leave an error message +** in pParse and return non-zero. Return zero on success. +*/ +static int lookupName( + Parse *pParse, /* The parsing context */ + Token *pDbToken, /* Name of the database containing table, or NULL */ + Token *pTableToken, /* Name of table containing column, or NULL */ + Token *pColumnToken, /* Name of the column. */ + SrcList *pSrcList, /* List of tables used to resolve column names */ + ExprList *pEList, /* List of expressions used to resolve "AS" */ + Expr *pExpr /* Make this EXPR node point to the selected column */ +){ + char *zDb = 0; /* Name of the database. The "X" in X.Y.Z */ + char *zTab = 0; /* Name of the table. The "Y" in X.Y.Z or Y.Z */ + char *zCol = 0; /* Name of the column. The "Z" */ + int i, j; /* Loop counters */ + int cnt = 0; /* Number of matching column names */ + int cntTab = 0; /* Number of matching table names */ + sqlite *db = pParse->db; /* The database */ + + assert( pColumnToken && pColumnToken->z ); /* The Z in X.Y.Z cannot be NULL */ + if( pDbToken && pDbToken->z ){ + zDb = sqliteStrNDup(pDbToken->z, pDbToken->n); + sqliteDequote(zDb); + }else{ + zDb = 0; + } + if( pTableToken && pTableToken->z ){ + zTab = sqliteStrNDup(pTableToken->z, pTableToken->n); + sqliteDequote(zTab); + }else{ + assert( zDb==0 ); + zTab = 0; + } + zCol = sqliteStrNDup(pColumnToken->z, pColumnToken->n); + sqliteDequote(zCol); + if( sqlite_malloc_failed ){ + return 1; /* Leak memory (zDb and zTab) if malloc fails */ + } + assert( zTab==0 || pEList==0 ); + + pExpr->iTable = -1; + for(i=0; inSrc; i++){ + struct SrcList_item *pItem = &pSrcList->a[i]; + Table *pTab = pItem->pTab; + Column *pCol; + + if( pTab==0 ) continue; + assert( pTab->nCol>0 ); + if( zTab ){ + if( pItem->zAlias ){ + char *zTabName = pItem->zAlias; + if( sqliteStrICmp(zTabName, zTab)!=0 ) continue; + }else{ + char *zTabName = pTab->zName; + if( zTabName==0 || sqliteStrICmp(zTabName, zTab)!=0 ) continue; + if( zDb!=0 && sqliteStrICmp(db->aDb[pTab->iDb].zName, zDb)!=0 ){ + continue; + } + } + } + if( 0==(cntTab++) ){ + pExpr->iTable = pItem->iCursor; + pExpr->iDb = pTab->iDb; + } + for(j=0, pCol=pTab->aCol; jnCol; j++, pCol++){ + if( sqliteStrICmp(pCol->zName, zCol)==0 ){ + cnt++; + pExpr->iTable = pItem->iCursor; + pExpr->iDb = pTab->iDb; + /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */ + pExpr->iColumn = j==pTab->iPKey ? -1 : j; + pExpr->dataType = pCol->sortOrder & SQLITE_SO_TYPEMASK; + break; + } + } + } + + /* If we have not already resolved the name, then maybe + ** it is a new.* or old.* trigger argument reference + */ + if( zDb==0 && zTab!=0 && cnt==0 && pParse->trigStack!=0 ){ + TriggerStack *pTriggerStack = pParse->trigStack; + Table *pTab = 0; + if( pTriggerStack->newIdx != -1 && sqliteStrICmp("new", zTab) == 0 ){ + pExpr->iTable = pTriggerStack->newIdx; + assert( pTriggerStack->pTab ); + pTab = pTriggerStack->pTab; + }else if( pTriggerStack->oldIdx != -1 && sqliteStrICmp("old", zTab) == 0 ){ + pExpr->iTable = pTriggerStack->oldIdx; + assert( pTriggerStack->pTab ); + pTab = pTriggerStack->pTab; + } + + if( pTab ){ + int j; + Column *pCol = pTab->aCol; + + pExpr->iDb = pTab->iDb; + cntTab++; + for(j=0; j < pTab->nCol; j++, pCol++) { + if( sqliteStrICmp(pCol->zName, zCol)==0 ){ + cnt++; + pExpr->iColumn = j==pTab->iPKey ? -1 : j; + pExpr->dataType = pCol->sortOrder & SQLITE_SO_TYPEMASK; + break; + } + } + } + } + + /* + ** Perhaps the name is a reference to the ROWID + */ + if( cnt==0 && cntTab==1 && sqliteIsRowid(zCol) ){ + cnt = 1; + pExpr->iColumn = -1; + pExpr->dataType = SQLITE_SO_NUM; + } + + /* + ** If the input is of the form Z (not Y.Z or X.Y.Z) then the name Z + ** might refer to an result-set alias. This happens, for example, when + ** we are resolving names in the WHERE clause of the following command: + ** + ** SELECT a+b AS x FROM table WHERE x<10; + ** + ** In cases like this, replace pExpr with a copy of the expression that + ** forms the result set entry ("a+b" in the example) and return immediately. + ** Note that the expression in the result set should have already been + ** resolved by the time the WHERE clause is resolved. + */ + if( cnt==0 && pEList!=0 ){ + for(j=0; jnExpr; j++){ + char *zAs = pEList->a[j].zName; + if( zAs!=0 && sqliteStrICmp(zAs, zCol)==0 ){ + assert( pExpr->pLeft==0 && pExpr->pRight==0 ); + pExpr->op = TK_AS; + pExpr->iColumn = j; + pExpr->pLeft = sqliteExprDup(pEList->a[j].pExpr); + sqliteFree(zCol); + assert( zTab==0 && zDb==0 ); + return 0; + } + } + } + + /* + ** If X and Y are NULL (in other words if only the column name Z is + ** supplied) and the value of Z is enclosed in double-quotes, then + ** Z is a string literal if it doesn't match any column names. In that + ** case, we need to return right away and not make any changes to + ** pExpr. + */ + if( cnt==0 && zTab==0 && pColumnToken->z[0]=='"' ){ + sqliteFree(zCol); + return 0; + } + + /* + ** cnt==0 means there was not match. cnt>1 means there were two or + ** more matches. Either way, we have an error. + */ + if( cnt!=1 ){ + char *z = 0; + char *zErr; + zErr = cnt==0 ? "no such column: %s" : "ambiguous column name: %s"; + if( zDb ){ + sqliteSetString(&z, zDb, ".", zTab, ".", zCol, 0); + }else if( zTab ){ + sqliteSetString(&z, zTab, ".", zCol, 0); + }else{ + z = sqliteStrDup(zCol); + } + sqliteErrorMsg(pParse, zErr, z); + sqliteFree(z); + } + + /* Clean up and return + */ + sqliteFree(zDb); + sqliteFree(zTab); + sqliteFree(zCol); + sqliteExprDelete(pExpr->pLeft); + pExpr->pLeft = 0; + sqliteExprDelete(pExpr->pRight); + pExpr->pRight = 0; + pExpr->op = TK_COLUMN; + sqliteAuthRead(pParse, pExpr, pSrcList); + return cnt!=1; +} + +/* +** This routine walks an expression tree and resolves references to +** table columns. Nodes of the form ID.ID or ID resolve into an +** index to the table in the table list and a column offset. The +** Expr.opcode for such nodes is changed to TK_COLUMN. The Expr.iTable +** value is changed to the index of the referenced table in pTabList +** plus the "base" value. The base value will ultimately become the +** VDBE cursor number for a cursor that is pointing into the referenced +** table. The Expr.iColumn value is changed to the index of the column +** of the referenced table. The Expr.iColumn value for the special +** ROWID column is -1. Any INTEGER PRIMARY KEY column is tried as an +** alias for ROWID. +** +** We also check for instances of the IN operator. IN comes in two +** forms: +** +** expr IN (exprlist) +** and +** expr IN (SELECT ...) +** +** The first form is handled by creating a set holding the list +** of allowed values. The second form causes the SELECT to generate +** a temporary table. +** +** This routine also looks for scalar SELECTs that are part of an expression. +** If it finds any, it generates code to write the value of that select +** into a memory cell. +** +** Unknown columns or tables provoke an error. The function returns +** the number of errors seen and leaves an error message on pParse->zErrMsg. +*/ +int sqliteExprResolveIds( + Parse *pParse, /* The parser context */ + SrcList *pSrcList, /* List of tables used to resolve column names */ + ExprList *pEList, /* List of expressions used to resolve "AS" */ + Expr *pExpr /* The expression to be analyzed. */ +){ + int i; + + if( pExpr==0 || pSrcList==0 ) return 0; + for(i=0; inSrc; i++){ + assert( pSrcList->a[i].iCursor>=0 && pSrcList->a[i].iCursornTab ); + } + switch( pExpr->op ){ + /* Double-quoted strings (ex: "abc") are used as identifiers if + ** possible. Otherwise they remain as strings. Single-quoted + ** strings (ex: 'abc') are always string literals. + */ + case TK_STRING: { + if( pExpr->token.z[0]=='\'' ) break; + /* Fall thru into the TK_ID case if this is a double-quoted string */ + } + /* A lone identifier is the name of a columnd. + */ + case TK_ID: { + if( lookupName(pParse, 0, 0, &pExpr->token, pSrcList, pEList, pExpr) ){ + return 1; + } + break; + } + + /* A table name and column name: ID.ID + ** Or a database, table and column: ID.ID.ID + */ + case TK_DOT: { + Token *pColumn; + Token *pTable; + Token *pDb; + Expr *pRight; + + pRight = pExpr->pRight; + if( pRight->op==TK_ID ){ + pDb = 0; + pTable = &pExpr->pLeft->token; + pColumn = &pRight->token; + }else{ + assert( pRight->op==TK_DOT ); + pDb = &pExpr->pLeft->token; + pTable = &pRight->pLeft->token; + pColumn = &pRight->pRight->token; + } + if( lookupName(pParse, pDb, pTable, pColumn, pSrcList, 0, pExpr) ){ + return 1; + } + break; + } + + case TK_IN: { + Vdbe *v = sqliteGetVdbe(pParse); + if( v==0 ) return 1; + if( sqliteExprResolveIds(pParse, pSrcList, pEList, pExpr->pLeft) ){ + return 1; + } + if( pExpr->pSelect ){ + /* Case 1: expr IN (SELECT ...) + ** + ** Generate code to write the results of the select into a temporary + ** table. The cursor number of the temporary table has already + ** been put in iTable by sqliteExprResolveInSelect(). + */ + pExpr->iTable = pParse->nTab++; + sqliteVdbeAddOp(v, OP_OpenTemp, pExpr->iTable, 1); + sqliteSelect(pParse, pExpr->pSelect, SRT_Set, pExpr->iTable, 0,0,0); + }else if( pExpr->pList ){ + /* Case 2: expr IN (exprlist) + ** + ** Create a set to put the exprlist values in. The Set id is stored + ** in iTable. + */ + int i, iSet; + for(i=0; ipList->nExpr; i++){ + Expr *pE2 = pExpr->pList->a[i].pExpr; + if( !sqliteExprIsConstant(pE2) ){ + sqliteErrorMsg(pParse, + "right-hand side of IN operator must be constant"); + return 1; + } + if( sqliteExprCheck(pParse, pE2, 0, 0) ){ + return 1; + } + } + iSet = pExpr->iTable = pParse->nSet++; + for(i=0; ipList->nExpr; i++){ + Expr *pE2 = pExpr->pList->a[i].pExpr; + switch( pE2->op ){ + case TK_FLOAT: + case TK_INTEGER: + case TK_STRING: { + int addr; + assert( pE2->token.z ); + addr = sqliteVdbeOp3(v, OP_SetInsert, iSet, 0, + pE2->token.z, pE2->token.n); + sqliteVdbeDequoteP3(v, addr); + break; + } + default: { + sqliteExprCode(pParse, pE2); + sqliteVdbeAddOp(v, OP_SetInsert, iSet, 0); + break; + } + } + } + } + break; + } + + case TK_SELECT: { + /* This has to be a scalar SELECT. Generate code to put the + ** value of this select in a memory cell and record the number + ** of the memory cell in iColumn. + */ + pExpr->iColumn = pParse->nMem++; + if( sqliteSelect(pParse, pExpr->pSelect, SRT_Mem, pExpr->iColumn,0,0,0) ){ + return 1; + } + break; + } + + /* For all else, just recursively walk the tree */ + default: { + if( pExpr->pLeft + && sqliteExprResolveIds(pParse, pSrcList, pEList, pExpr->pLeft) ){ + return 1; + } + if( pExpr->pRight + && sqliteExprResolveIds(pParse, pSrcList, pEList, pExpr->pRight) ){ + return 1; + } + if( pExpr->pList ){ + int i; + ExprList *pList = pExpr->pList; + for(i=0; inExpr; i++){ + Expr *pArg = pList->a[i].pExpr; + if( sqliteExprResolveIds(pParse, pSrcList, pEList, pArg) ){ + return 1; + } + } + } + } + } + return 0; +} + +/* +** pExpr is a node that defines a function of some kind. It might +** be a syntactic function like "count(x)" or it might be a function +** that implements an operator, like "a LIKE b". +** +** This routine makes *pzName point to the name of the function and +** *pnName hold the number of characters in the function name. +*/ +static void getFunctionName(Expr *pExpr, const char **pzName, int *pnName){ + switch( pExpr->op ){ + case TK_FUNCTION: { + *pzName = pExpr->token.z; + *pnName = pExpr->token.n; + break; + } + case TK_LIKE: { + *pzName = "like"; + *pnName = 4; + break; + } + case TK_GLOB: { + *pzName = "glob"; + *pnName = 4; + break; + } + default: { + *pzName = "can't happen"; + *pnName = 12; + break; + } + } +} + +/* +** Error check the functions in an expression. Make sure all +** function names are recognized and all functions have the correct +** number of arguments. Leave an error message in pParse->zErrMsg +** if anything is amiss. Return the number of errors. +** +** if pIsAgg is not null and this expression is an aggregate function +** (like count(*) or max(value)) then write a 1 into *pIsAgg. +*/ +int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){ + int nErr = 0; + if( pExpr==0 ) return 0; + switch( pExpr->op ){ + case TK_GLOB: + case TK_LIKE: + case TK_FUNCTION: { + int n = pExpr->pList ? pExpr->pList->nExpr : 0; /* Number of arguments */ + int no_such_func = 0; /* True if no such function exists */ + int wrong_num_args = 0; /* True if wrong number of arguments */ + int is_agg = 0; /* True if is an aggregate function */ + int i; + int nId; /* Number of characters in function name */ + const char *zId; /* The function name. */ + FuncDef *pDef; + + getFunctionName(pExpr, &zId, &nId); + pDef = sqliteFindFunction(pParse->db, zId, nId, n, 0); + if( pDef==0 ){ + pDef = sqliteFindFunction(pParse->db, zId, nId, -1, 0); + if( pDef==0 ){ + no_such_func = 1; + }else{ + wrong_num_args = 1; + } + }else{ + is_agg = pDef->xFunc==0; + } + if( is_agg && !allowAgg ){ + sqliteErrorMsg(pParse, "misuse of aggregate function %.*s()", nId, zId); + nErr++; + is_agg = 0; + }else if( no_such_func ){ + sqliteErrorMsg(pParse, "no such function: %.*s", nId, zId); + nErr++; + }else if( wrong_num_args ){ + sqliteErrorMsg(pParse,"wrong number of arguments to function %.*s()", + nId, zId); + nErr++; + } + if( is_agg ){ + pExpr->op = TK_AGG_FUNCTION; + if( pIsAgg ) *pIsAgg = 1; + } + for(i=0; nErr==0 && ipList->a[i].pExpr, + allowAgg && !is_agg, pIsAgg); + } + if( pDef==0 ){ + /* Already reported an error */ + }else if( pDef->dataType>=0 ){ + if( pDef->dataTypedataType = + sqliteExprType(pExpr->pList->a[pDef->dataType].pExpr); + }else{ + pExpr->dataType = SQLITE_SO_NUM; + } + }else if( pDef->dataType==SQLITE_ARGS ){ + pDef->dataType = SQLITE_SO_TEXT; + for(i=0; ipList->a[i].pExpr)==SQLITE_SO_NUM ){ + pExpr->dataType = SQLITE_SO_NUM; + break; + } + } + }else if( pDef->dataType==SQLITE_NUMERIC ){ + pExpr->dataType = SQLITE_SO_NUM; + }else{ + pExpr->dataType = SQLITE_SO_TEXT; + } + } + default: { + if( pExpr->pLeft ){ + nErr = sqliteExprCheck(pParse, pExpr->pLeft, allowAgg, pIsAgg); + } + if( nErr==0 && pExpr->pRight ){ + nErr = sqliteExprCheck(pParse, pExpr->pRight, allowAgg, pIsAgg); + } + if( nErr==0 && pExpr->pList ){ + int n = pExpr->pList->nExpr; + int i; + for(i=0; nErr==0 && ipList->a[i].pExpr; + nErr = sqliteExprCheck(pParse, pE2, allowAgg, pIsAgg); + } + } + break; + } + } + return nErr; +} + +/* +** Return either SQLITE_SO_NUM or SQLITE_SO_TEXT to indicate whether the +** given expression should sort as numeric values or as text. +** +** The sqliteExprResolveIds() and sqliteExprCheck() routines must have +** both been called on the expression before it is passed to this routine. +*/ +int sqliteExprType(Expr *p){ + if( p==0 ) return SQLITE_SO_NUM; + while( p ) switch( p->op ){ + case TK_PLUS: + case TK_MINUS: + case TK_STAR: + case TK_SLASH: + case TK_AND: + case TK_OR: + case TK_ISNULL: + case TK_NOTNULL: + case TK_NOT: + case TK_UMINUS: + case TK_UPLUS: + case TK_BITAND: + case TK_BITOR: + case TK_BITNOT: + case TK_LSHIFT: + case TK_RSHIFT: + case TK_REM: + case TK_INTEGER: + case TK_FLOAT: + case TK_IN: + case TK_BETWEEN: + case TK_GLOB: + case TK_LIKE: + return SQLITE_SO_NUM; + + case TK_STRING: + case TK_NULL: + case TK_CONCAT: + case TK_VARIABLE: + return SQLITE_SO_TEXT; + + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: + if( sqliteExprType(p->pLeft)==SQLITE_SO_NUM ){ + return SQLITE_SO_NUM; + } + p = p->pRight; + break; + + case TK_AS: + p = p->pLeft; + break; + + case TK_COLUMN: + case TK_FUNCTION: + case TK_AGG_FUNCTION: + return p->dataType; + + case TK_SELECT: + assert( p->pSelect ); + assert( p->pSelect->pEList ); + assert( p->pSelect->pEList->nExpr>0 ); + p = p->pSelect->pEList->a[0].pExpr; + break; + + case TK_CASE: { + if( p->pRight && sqliteExprType(p->pRight)==SQLITE_SO_NUM ){ + return SQLITE_SO_NUM; + } + if( p->pList ){ + int i; + ExprList *pList = p->pList; + for(i=1; inExpr; i+=2){ + if( sqliteExprType(pList->a[i].pExpr)==SQLITE_SO_NUM ){ + return SQLITE_SO_NUM; + } + } + } + return SQLITE_SO_TEXT; + } + + default: + assert( p->op==TK_ABORT ); /* Can't Happen */ + break; + } + return SQLITE_SO_NUM; +} + +/* +** Generate code into the current Vdbe to evaluate the given +** expression and leave the result on the top of stack. +*/ +void sqliteExprCode(Parse *pParse, Expr *pExpr){ + Vdbe *v = pParse->pVdbe; + int op; + if( v==0 || pExpr==0 ) return; + switch( pExpr->op ){ + case TK_PLUS: op = OP_Add; break; + case TK_MINUS: op = OP_Subtract; break; + case TK_STAR: op = OP_Multiply; break; + case TK_SLASH: op = OP_Divide; break; + case TK_AND: op = OP_And; break; + case TK_OR: op = OP_Or; break; + case TK_LT: op = OP_Lt; break; + case TK_LE: op = OP_Le; break; + case TK_GT: op = OP_Gt; break; + case TK_GE: op = OP_Ge; break; + case TK_NE: op = OP_Ne; break; + case TK_EQ: op = OP_Eq; break; + case TK_ISNULL: op = OP_IsNull; break; + case TK_NOTNULL: op = OP_NotNull; break; + case TK_NOT: op = OP_Not; break; + case TK_UMINUS: op = OP_Negative; break; + case TK_BITAND: op = OP_BitAnd; break; + case TK_BITOR: op = OP_BitOr; break; + case TK_BITNOT: op = OP_BitNot; break; + case TK_LSHIFT: op = OP_ShiftLeft; break; + case TK_RSHIFT: op = OP_ShiftRight; break; + case TK_REM: op = OP_Remainder; break; + default: break; + } + switch( pExpr->op ){ + case TK_COLUMN: { + if( pParse->useAgg ){ + sqliteVdbeAddOp(v, OP_AggGet, 0, pExpr->iAgg); + }else if( pExpr->iColumn>=0 ){ + sqliteVdbeAddOp(v, OP_Column, pExpr->iTable, pExpr->iColumn); + }else{ + sqliteVdbeAddOp(v, OP_Recno, pExpr->iTable, 0); + } + break; + } + case TK_STRING: + case TK_FLOAT: + case TK_INTEGER: { + if( pExpr->op==TK_INTEGER && sqliteFitsIn32Bits(pExpr->token.z) ){ + sqliteVdbeAddOp(v, OP_Integer, atoi(pExpr->token.z), 0); + }else{ + sqliteVdbeAddOp(v, OP_String, 0, 0); + } + assert( pExpr->token.z ); + sqliteVdbeChangeP3(v, -1, pExpr->token.z, pExpr->token.n); + sqliteVdbeDequoteP3(v, -1); + break; + } + case TK_NULL: { + sqliteVdbeAddOp(v, OP_String, 0, 0); + break; + } + case TK_VARIABLE: { + sqliteVdbeAddOp(v, OP_Variable, pExpr->iTable, 0); + break; + } + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: { + if( pParse->db->file_format>=4 && sqliteExprType(pExpr)==SQLITE_SO_TEXT ){ + op += 6; /* Convert numeric opcodes to text opcodes */ + } + /* Fall through into the next case */ + } + case TK_AND: + case TK_OR: + case TK_PLUS: + case TK_STAR: + case TK_MINUS: + case TK_REM: + case TK_BITAND: + case TK_BITOR: + case TK_SLASH: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteExprCode(pParse, pExpr->pRight); + sqliteVdbeAddOp(v, op, 0, 0); + break; + } + case TK_LSHIFT: + case TK_RSHIFT: { + sqliteExprCode(pParse, pExpr->pRight); + sqliteExprCode(pParse, pExpr->pLeft); + sqliteVdbeAddOp(v, op, 0, 0); + break; + } + case TK_CONCAT: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteExprCode(pParse, pExpr->pRight); + sqliteVdbeAddOp(v, OP_Concat, 2, 0); + break; + } + case TK_UMINUS: { + assert( pExpr->pLeft ); + if( pExpr->pLeft->op==TK_FLOAT || pExpr->pLeft->op==TK_INTEGER ){ + Token *p = &pExpr->pLeft->token; + char *z = sqliteMalloc( p->n + 2 ); + sprintf(z, "-%.*s", p->n, p->z); + if( pExpr->pLeft->op==TK_INTEGER && sqliteFitsIn32Bits(z) ){ + sqliteVdbeAddOp(v, OP_Integer, atoi(z), 0); + }else{ + sqliteVdbeAddOp(v, OP_String, 0, 0); + } + sqliteVdbeChangeP3(v, -1, z, p->n+1); + sqliteFree(z); + break; + } + /* Fall through into TK_NOT */ + } + case TK_BITNOT: + case TK_NOT: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteVdbeAddOp(v, op, 0, 0); + break; + } + case TK_ISNULL: + case TK_NOTNULL: { + int dest; + sqliteVdbeAddOp(v, OP_Integer, 1, 0); + sqliteExprCode(pParse, pExpr->pLeft); + dest = sqliteVdbeCurrentAddr(v) + 2; + sqliteVdbeAddOp(v, op, 1, dest); + sqliteVdbeAddOp(v, OP_AddImm, -1, 0); + break; + } + case TK_AGG_FUNCTION: { + sqliteVdbeAddOp(v, OP_AggGet, 0, pExpr->iAgg); + break; + } + case TK_GLOB: + case TK_LIKE: + case TK_FUNCTION: { + ExprList *pList = pExpr->pList; + int nExpr = pList ? pList->nExpr : 0; + FuncDef *pDef; + int nId; + const char *zId; + getFunctionName(pExpr, &zId, &nId); + pDef = sqliteFindFunction(pParse->db, zId, nId, nExpr, 0); + assert( pDef!=0 ); + nExpr = sqliteExprCodeExprList(pParse, pList, pDef->includeTypes); + sqliteVdbeOp3(v, OP_Function, nExpr, 0, (char*)pDef, P3_POINTER); + break; + } + case TK_SELECT: { + sqliteVdbeAddOp(v, OP_MemLoad, pExpr->iColumn, 0); + break; + } + case TK_IN: { + int addr; + sqliteVdbeAddOp(v, OP_Integer, 1, 0); + sqliteExprCode(pParse, pExpr->pLeft); + addr = sqliteVdbeCurrentAddr(v); + sqliteVdbeAddOp(v, OP_NotNull, -1, addr+4); + sqliteVdbeAddOp(v, OP_Pop, 2, 0); + sqliteVdbeAddOp(v, OP_String, 0, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, addr+6); + if( pExpr->pSelect ){ + sqliteVdbeAddOp(v, OP_Found, pExpr->iTable, addr+6); + }else{ + sqliteVdbeAddOp(v, OP_SetFound, pExpr->iTable, addr+6); + } + sqliteVdbeAddOp(v, OP_AddImm, -1, 0); + break; + } + case TK_BETWEEN: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteVdbeAddOp(v, OP_Dup, 0, 0); + sqliteExprCode(pParse, pExpr->pList->a[0].pExpr); + sqliteVdbeAddOp(v, OP_Ge, 0, 0); + sqliteVdbeAddOp(v, OP_Pull, 1, 0); + sqliteExprCode(pParse, pExpr->pList->a[1].pExpr); + sqliteVdbeAddOp(v, OP_Le, 0, 0); + sqliteVdbeAddOp(v, OP_And, 0, 0); + break; + } + case TK_UPLUS: + case TK_AS: { + sqliteExprCode(pParse, pExpr->pLeft); + break; + } + case TK_CASE: { + int expr_end_label; + int jumpInst; + int addr; + int nExpr; + int i; + + assert(pExpr->pList); + assert((pExpr->pList->nExpr % 2) == 0); + assert(pExpr->pList->nExpr > 0); + nExpr = pExpr->pList->nExpr; + expr_end_label = sqliteVdbeMakeLabel(v); + if( pExpr->pLeft ){ + sqliteExprCode(pParse, pExpr->pLeft); + } + for(i=0; ipList->a[i].pExpr); + if( pExpr->pLeft ){ + sqliteVdbeAddOp(v, OP_Dup, 1, 1); + jumpInst = sqliteVdbeAddOp(v, OP_Ne, 1, 0); + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + }else{ + jumpInst = sqliteVdbeAddOp(v, OP_IfNot, 1, 0); + } + sqliteExprCode(pParse, pExpr->pList->a[i+1].pExpr); + sqliteVdbeAddOp(v, OP_Goto, 0, expr_end_label); + addr = sqliteVdbeCurrentAddr(v); + sqliteVdbeChangeP2(v, jumpInst, addr); + } + if( pExpr->pLeft ){ + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + } + if( pExpr->pRight ){ + sqliteExprCode(pParse, pExpr->pRight); + }else{ + sqliteVdbeAddOp(v, OP_String, 0, 0); + } + sqliteVdbeResolveLabel(v, expr_end_label); + break; + } + case TK_RAISE: { + if( !pParse->trigStack ){ + sqliteErrorMsg(pParse, + "RAISE() may only be used within a trigger-program"); + pParse->nErr++; + return; + } + if( pExpr->iColumn == OE_Rollback || + pExpr->iColumn == OE_Abort || + pExpr->iColumn == OE_Fail ){ + sqliteVdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, pExpr->iColumn, + pExpr->token.z, pExpr->token.n); + sqliteVdbeDequoteP3(v, -1); + } else { + assert( pExpr->iColumn == OE_Ignore ); + sqliteVdbeOp3(v, OP_Goto, 0, pParse->trigStack->ignoreJump, + "(IGNORE jump)", 0); + } + } + break; + } +} + +/* +** Generate code that pushes the value of every element of the given +** expression list onto the stack. If the includeTypes flag is true, +** then also push a string that is the datatype of each element onto +** the stack after the value. +** +** Return the number of elements pushed onto the stack. +*/ +int sqliteExprCodeExprList( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* The expression list to be coded */ + int includeTypes /* TRUE to put datatypes on the stack too */ +){ + struct ExprList_item *pItem; + int i, n; + Vdbe *v; + if( pList==0 ) return 0; + v = sqliteGetVdbe(pParse); + n = pList->nExpr; + for(pItem=pList->a, i=0; ipExpr); + if( includeTypes ){ + sqliteVdbeOp3(v, OP_String, 0, 0, + sqliteExprType(pItem->pExpr)==SQLITE_SO_NUM ? "numeric" : "text", + P3_STATIC); + } + } + return includeTypes ? n*2 : n; +} + +/* +** Generate code for a boolean expression such that a jump is made +** to the label "dest" if the expression is true but execution +** continues straight thru if the expression is false. +** +** If the expression evaluates to NULL (neither true nor false), then +** take the jump if the jumpIfNull flag is true. +*/ +void sqliteExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ + Vdbe *v = pParse->pVdbe; + int op = 0; + if( v==0 || pExpr==0 ) return; + switch( pExpr->op ){ + case TK_LT: op = OP_Lt; break; + case TK_LE: op = OP_Le; break; + case TK_GT: op = OP_Gt; break; + case TK_GE: op = OP_Ge; break; + case TK_NE: op = OP_Ne; break; + case TK_EQ: op = OP_Eq; break; + case TK_ISNULL: op = OP_IsNull; break; + case TK_NOTNULL: op = OP_NotNull; break; + default: break; + } + switch( pExpr->op ){ + case TK_AND: { + int d2 = sqliteVdbeMakeLabel(v); + sqliteExprIfFalse(pParse, pExpr->pLeft, d2, !jumpIfNull); + sqliteExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull); + sqliteVdbeResolveLabel(v, d2); + break; + } + case TK_OR: { + sqliteExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull); + sqliteExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull); + break; + } + case TK_NOT: { + sqliteExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); + break; + } + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteExprCode(pParse, pExpr->pRight); + if( pParse->db->file_format>=4 && sqliteExprType(pExpr)==SQLITE_SO_TEXT ){ + op += 6; /* Convert numeric opcodes to text opcodes */ + } + sqliteVdbeAddOp(v, op, jumpIfNull, dest); + break; + } + case TK_ISNULL: + case TK_NOTNULL: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteVdbeAddOp(v, op, 1, dest); + break; + } + case TK_IN: { + int addr; + sqliteExprCode(pParse, pExpr->pLeft); + addr = sqliteVdbeCurrentAddr(v); + sqliteVdbeAddOp(v, OP_NotNull, -1, addr+3); + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, jumpIfNull ? dest : addr+4); + if( pExpr->pSelect ){ + sqliteVdbeAddOp(v, OP_Found, pExpr->iTable, dest); + }else{ + sqliteVdbeAddOp(v, OP_SetFound, pExpr->iTable, dest); + } + break; + } + case TK_BETWEEN: { + int addr; + sqliteExprCode(pParse, pExpr->pLeft); + sqliteVdbeAddOp(v, OP_Dup, 0, 0); + sqliteExprCode(pParse, pExpr->pList->a[0].pExpr); + addr = sqliteVdbeAddOp(v, OP_Lt, !jumpIfNull, 0); + sqliteExprCode(pParse, pExpr->pList->a[1].pExpr); + sqliteVdbeAddOp(v, OP_Le, jumpIfNull, dest); + sqliteVdbeAddOp(v, OP_Integer, 0, 0); + sqliteVdbeChangeP2(v, addr, sqliteVdbeCurrentAddr(v)); + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + break; + } + default: { + sqliteExprCode(pParse, pExpr); + sqliteVdbeAddOp(v, OP_If, jumpIfNull, dest); + break; + } + } +} + +/* +** Generate code for a boolean expression such that a jump is made +** to the label "dest" if the expression is false but execution +** continues straight thru if the expression is true. +** +** If the expression evaluates to NULL (neither true nor false) then +** jump if jumpIfNull is true or fall through if jumpIfNull is false. +*/ +void sqliteExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ + Vdbe *v = pParse->pVdbe; + int op = 0; + if( v==0 || pExpr==0 ) return; + switch( pExpr->op ){ + case TK_LT: op = OP_Ge; break; + case TK_LE: op = OP_Gt; break; + case TK_GT: op = OP_Le; break; + case TK_GE: op = OP_Lt; break; + case TK_NE: op = OP_Eq; break; + case TK_EQ: op = OP_Ne; break; + case TK_ISNULL: op = OP_NotNull; break; + case TK_NOTNULL: op = OP_IsNull; break; + default: break; + } + switch( pExpr->op ){ + case TK_AND: { + sqliteExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); + sqliteExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull); + break; + } + case TK_OR: { + int d2 = sqliteVdbeMakeLabel(v); + sqliteExprIfTrue(pParse, pExpr->pLeft, d2, !jumpIfNull); + sqliteExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull); + sqliteVdbeResolveLabel(v, d2); + break; + } + case TK_NOT: { + sqliteExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull); + break; + } + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_NE: + case TK_EQ: { + if( pParse->db->file_format>=4 && sqliteExprType(pExpr)==SQLITE_SO_TEXT ){ + /* Convert numeric comparison opcodes into text comparison opcodes. + ** This step depends on the fact that the text comparision opcodes are + ** always 6 greater than their corresponding numeric comparison + ** opcodes. + */ + assert( OP_Eq+6 == OP_StrEq ); + op += 6; + } + sqliteExprCode(pParse, pExpr->pLeft); + sqliteExprCode(pParse, pExpr->pRight); + sqliteVdbeAddOp(v, op, jumpIfNull, dest); + break; + } + case TK_ISNULL: + case TK_NOTNULL: { + sqliteExprCode(pParse, pExpr->pLeft); + sqliteVdbeAddOp(v, op, 1, dest); + break; + } + case TK_IN: { + int addr; + sqliteExprCode(pParse, pExpr->pLeft); + addr = sqliteVdbeCurrentAddr(v); + sqliteVdbeAddOp(v, OP_NotNull, -1, addr+3); + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, jumpIfNull ? dest : addr+4); + if( pExpr->pSelect ){ + sqliteVdbeAddOp(v, OP_NotFound, pExpr->iTable, dest); + }else{ + sqliteVdbeAddOp(v, OP_SetNotFound, pExpr->iTable, dest); + } + break; + } + case TK_BETWEEN: { + int addr; + sqliteExprCode(pParse, pExpr->pLeft); + sqliteVdbeAddOp(v, OP_Dup, 0, 0); + sqliteExprCode(pParse, pExpr->pList->a[0].pExpr); + addr = sqliteVdbeCurrentAddr(v); + sqliteVdbeAddOp(v, OP_Ge, !jumpIfNull, addr+3); + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, dest); + sqliteExprCode(pParse, pExpr->pList->a[1].pExpr); + sqliteVdbeAddOp(v, OP_Gt, jumpIfNull, dest); + break; + } + default: { + sqliteExprCode(pParse, pExpr); + sqliteVdbeAddOp(v, OP_IfNot, jumpIfNull, dest); + break; + } + } +} + +/* +** Do a deep comparison of two expression trees. Return TRUE (non-zero) +** if they are identical and return FALSE if they differ in any way. +*/ +int sqliteExprCompare(Expr *pA, Expr *pB){ + int i; + if( pA==0 ){ + return pB==0; + }else if( pB==0 ){ + return 0; + } + if( pA->op!=pB->op ) return 0; + if( !sqliteExprCompare(pA->pLeft, pB->pLeft) ) return 0; + if( !sqliteExprCompare(pA->pRight, pB->pRight) ) return 0; + if( pA->pList ){ + if( pB->pList==0 ) return 0; + if( pA->pList->nExpr!=pB->pList->nExpr ) return 0; + for(i=0; ipList->nExpr; i++){ + if( !sqliteExprCompare(pA->pList->a[i].pExpr, pB->pList->a[i].pExpr) ){ + return 0; + } + } + }else if( pB->pList ){ + return 0; + } + if( pA->pSelect || pB->pSelect ) return 0; + if( pA->iTable!=pB->iTable || pA->iColumn!=pB->iColumn ) return 0; + if( pA->token.z ){ + if( pB->token.z==0 ) return 0; + if( pB->token.n!=pA->token.n ) return 0; + if( sqliteStrNICmp(pA->token.z, pB->token.z, pB->token.n)!=0 ) return 0; + } + return 1; +} + +/* +** Add a new element to the pParse->aAgg[] array and return its index. +*/ +static int appendAggInfo(Parse *pParse){ + if( (pParse->nAgg & 0x7)==0 ){ + int amt = pParse->nAgg + 8; + AggExpr *aAgg = sqliteRealloc(pParse->aAgg, amt*sizeof(pParse->aAgg[0])); + if( aAgg==0 ){ + return -1; + } + pParse->aAgg = aAgg; + } + memset(&pParse->aAgg[pParse->nAgg], 0, sizeof(pParse->aAgg[0])); + return pParse->nAgg++; +} + +/* +** Analyze the given expression looking for aggregate functions and +** for variables that need to be added to the pParse->aAgg[] array. +** Make additional entries to the pParse->aAgg[] array as necessary. +** +** This routine should only be called after the expression has been +** analyzed by sqliteExprResolveIds() and sqliteExprCheck(). +** +** If errors are seen, leave an error message in zErrMsg and return +** the number of errors. +*/ +int sqliteExprAnalyzeAggregates(Parse *pParse, Expr *pExpr){ + int i; + AggExpr *aAgg; + int nErr = 0; + + if( pExpr==0 ) return 0; + switch( pExpr->op ){ + case TK_COLUMN: { + aAgg = pParse->aAgg; + for(i=0; inAgg; i++){ + if( aAgg[i].isAgg ) continue; + if( aAgg[i].pExpr->iTable==pExpr->iTable + && aAgg[i].pExpr->iColumn==pExpr->iColumn ){ + break; + } + } + if( i>=pParse->nAgg ){ + i = appendAggInfo(pParse); + if( i<0 ) return 1; + pParse->aAgg[i].isAgg = 0; + pParse->aAgg[i].pExpr = pExpr; + } + pExpr->iAgg = i; + break; + } + case TK_AGG_FUNCTION: { + aAgg = pParse->aAgg; + for(i=0; inAgg; i++){ + if( !aAgg[i].isAgg ) continue; + if( sqliteExprCompare(aAgg[i].pExpr, pExpr) ){ + break; + } + } + if( i>=pParse->nAgg ){ + i = appendAggInfo(pParse); + if( i<0 ) return 1; + pParse->aAgg[i].isAgg = 1; + pParse->aAgg[i].pExpr = pExpr; + pParse->aAgg[i].pFunc = sqliteFindFunction(pParse->db, + pExpr->token.z, pExpr->token.n, + pExpr->pList ? pExpr->pList->nExpr : 0, 0); + } + pExpr->iAgg = i; + break; + } + default: { + if( pExpr->pLeft ){ + nErr = sqliteExprAnalyzeAggregates(pParse, pExpr->pLeft); + } + if( nErr==0 && pExpr->pRight ){ + nErr = sqliteExprAnalyzeAggregates(pParse, pExpr->pRight); + } + if( nErr==0 && pExpr->pList ){ + int n = pExpr->pList->nExpr; + int i; + for(i=0; nErr==0 && ipList->a[i].pExpr); + } + } + break; + } + } + return nErr; +} + +/* +** Locate a user function given a name and a number of arguments. +** Return a pointer to the FuncDef structure that defines that +** function, or return NULL if the function does not exist. +** +** If the createFlag argument is true, then a new (blank) FuncDef +** structure is created and liked into the "db" structure if a +** no matching function previously existed. When createFlag is true +** and the nArg parameter is -1, then only a function that accepts +** any number of arguments will be returned. +** +** If createFlag is false and nArg is -1, then the first valid +** function found is returned. A function is valid if either xFunc +** or xStep is non-zero. +*/ +FuncDef *sqliteFindFunction( + sqlite *db, /* An open database */ + const char *zName, /* Name of the function. Not null-terminated */ + int nName, /* Number of characters in the name */ + int nArg, /* Number of arguments. -1 means any number */ + int createFlag /* Create new entry if true and does not otherwise exist */ +){ + FuncDef *pFirst, *p, *pMaybe; + pFirst = p = (FuncDef*)sqliteHashFind(&db->aFunc, zName, nName); + if( p && !createFlag && nArg<0 ){ + while( p && p->xFunc==0 && p->xStep==0 ){ p = p->pNext; } + return p; + } + pMaybe = 0; + while( p && p->nArg!=nArg ){ + if( p->nArg<0 && !createFlag && (p->xFunc || p->xStep) ) pMaybe = p; + p = p->pNext; + } + if( p && !createFlag && p->xFunc==0 && p->xStep==0 ){ + return 0; + } + if( p==0 && pMaybe ){ + assert( createFlag==0 ); + return pMaybe; + } + if( p==0 && createFlag && (p = sqliteMalloc(sizeof(*p)))!=0 ){ + p->nArg = nArg; + p->pNext = pFirst; + p->dataType = pFirst ? pFirst->dataType : SQLITE_NUMERIC; + sqliteHashInsert(&db->aFunc, zName, nName, (void*)p); + } + return p; +} diff --git a/src/libs/sqlite2/func.c b/src/libs/sqlite2/func.c new file mode 100644 index 00000000..c86a75a3 --- /dev/null +++ b/src/libs/sqlite2/func.c @@ -0,0 +1,658 @@ +/* +** 2002 February 23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains the C functions that implement various SQL +** functions of SQLite. +** +** There is only one exported symbol in this file - the function +** sqliteRegisterBuildinFunctions() found at the bottom of the file. +** All other code has file scope. +** +** $Id: func.c 875429 2008-10-24 12:20:41Z cgilles $ +*/ +#include +#include +#include +#include +#include "sqliteInt.h" +#include "os.h" + +/* +** Implementation of the non-aggregate min() and max() functions +*/ +static void minmaxFunc(sqlite_func *context, int argc, const char **argv){ + const char *zBest; + int i; + int (*xCompare)(const char*, const char*); + int mask; /* 0 for min() or 0xffffffff for max() */ + + if( argc==0 ) return; + mask = (int)sqlite_user_data(context); + zBest = argv[0]; + if( zBest==0 ) return; + if( argv[1][0]=='n' ){ + xCompare = sqliteCompare; + }else{ + xCompare = strcmp; + } + for(i=2; i0 ){ + p1--; + } + if( p1+p2>len ){ + p2 = len-p1; + } +#ifdef SQLITE_UTF8 + for(i=0; i30 ) n = 30; + if( n<0 ) n = 0; + r = sqliteAtoF(argv[0], 0); + sprintf(zBuf,"%.*f",n,r); + sqlite_set_result_string(context, zBuf, -1); +} + +/* +** Implementation of the upper() and lower() SQL functions. +*/ +static void upperFunc(sqlite_func *context, int argc, const char **argv){ + unsigned char *z; + int i; + if( argc<1 || argv[0]==0 ) return; + z = (unsigned char*)sqlite_set_result_string(context, argv[0], -1); + if( z==0 ) return; + for(i=0; z[i]; i++){ + if( islower(z[i]) ) z[i] = toupper(z[i]); + } +} +static void lowerFunc(sqlite_func *context, int argc, const char **argv){ + unsigned char *z; + int i; + if( argc<1 || argv[0]==0 ) return; + z = (unsigned char*)sqlite_set_result_string(context, argv[0], -1); + if( z==0 ) return; + for(i=0; z[i]; i++){ + if( isupper(z[i]) ) z[i] = tolower(z[i]); + } +} + +/* +** Implementation of the IFNULL(), NVL(), and COALESCE() functions. +** All three do the same thing. They return the first non-NULL +** argument. +*/ +static void ifnullFunc(sqlite_func *context, int argc, const char **argv){ + int i; + for(i=0; i0 ){ + zResult[j++] = code + '0'; + } + } + while( j<4 ){ + zResult[j++] = '0'; + } + zResult[j] = 0; + sqlite_set_result_string(context, zResult, 4); + }else{ + sqlite_set_result_string(context, "?000", 4); + } +} +#endif + +#ifdef SQLITE_TEST +/* +** This function generates a string of random characters. Used for +** generating test data. +*/ +static void randStr(sqlite_func *context, int argc, const char **argv){ + static const unsigned char zSrc[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + ".-!,:*^+=_|?/<> "; + int iMin, iMax, n, r, i; + unsigned char zBuf[1000]; + if( argc>=1 ){ + iMin = atoi(argv[0]); + if( iMin<0 ) iMin = 0; + if( iMin>=sizeof(zBuf) ) iMin = sizeof(zBuf)-1; + }else{ + iMin = 1; + } + if( argc>=2 ){ + iMax = atoi(argv[1]); + if( iMax=sizeof(zBuf) ) iMax = sizeof(zBuf)-1; + }else{ + iMax = 50; + } + n = iMin; + if( iMax>iMin ){ + sqliteRandomness(sizeof(r), &r); + r &= 0x7fffffff; + n += r%(iMax + 1 - iMin); + } + assert( nsum += sqliteAtoF(argv[0], 0); + p->cnt++; + } +} +static void sumFinalize(sqlite_func *context){ + SumCtx *p; + p = sqlite_aggregate_context(context, sizeof(*p)); + sqlite_set_result_double(context, p ? p->sum : 0.0); +} +static void avgFinalize(sqlite_func *context){ + SumCtx *p; + p = sqlite_aggregate_context(context, sizeof(*p)); + if( p && p->cnt>0 ){ + sqlite_set_result_double(context, p->sum/(double)p->cnt); + } +} + +/* +** An instance of the following structure holds the context of a +** variance or standard deviation computation. +*/ +typedef struct StdDevCtx StdDevCtx; +struct StdDevCtx { + double sum; /* Sum of terms */ + double sum2; /* Sum of the squares of terms */ + int cnt; /* Number of terms counted */ +}; + +#if 0 /* Omit because math library is required */ +/* +** Routines used to compute the standard deviation as an aggregate. +*/ +static void stdDevStep(sqlite_func *context, int argc, const char **argv){ + StdDevCtx *p; + double x; + if( argc<1 ) return; + p = sqlite_aggregate_context(context, sizeof(*p)); + if( p && argv[0] ){ + x = sqliteAtoF(argv[0], 0); + p->sum += x; + p->sum2 += x*x; + p->cnt++; + } +} +static void stdDevFinalize(sqlite_func *context){ + double rN = sqlite_aggregate_count(context); + StdDevCtx *p = sqlite_aggregate_context(context, sizeof(*p)); + if( p && p->cnt>1 ){ + double rCnt = cnt; + sqlite_set_result_double(context, + sqrt((p->sum2 - p->sum*p->sum/rCnt)/(rCnt-1.0))); + } +} +#endif + +/* +** The following structure keeps track of state information for the +** count() aggregate function. +*/ +typedef struct CountCtx CountCtx; +struct CountCtx { + int n; +}; + +/* +** Routines to implement the count() aggregate function. +*/ +static void countStep(sqlite_func *context, int argc, const char **argv){ + CountCtx *p; + p = sqlite_aggregate_context(context, sizeof(*p)); + if( (argc==0 || argv[0]) && p ){ + p->n++; + } +} +static void countFinalize(sqlite_func *context){ + CountCtx *p; + p = sqlite_aggregate_context(context, sizeof(*p)); + sqlite_set_result_int(context, p ? p->n : 0); +} + +/* +** This function tracks state information for the min() and max() +** aggregate functions. +*/ +typedef struct MinMaxCtx MinMaxCtx; +struct MinMaxCtx { + char *z; /* The best so far */ + char zBuf[28]; /* Space that can be used for storage */ +}; + +/* +** Routines to implement min() and max() aggregate functions. +*/ +static void minmaxStep(sqlite_func *context, int argc, const char **argv){ + MinMaxCtx *p; + int (*xCompare)(const char*, const char*); + int mask; /* 0 for min() or 0xffffffff for max() */ + + assert( argc==2 ); + if( argv[0]==0 ) return; /* Ignore NULL values */ + if( argv[1][0]=='n' ){ + xCompare = sqliteCompare; + }else{ + xCompare = strcmp; + } + mask = (int)sqlite_user_data(context); + assert( mask==0 || mask==-1 ); + p = sqlite_aggregate_context(context, sizeof(*p)); + if( p==0 || argc<1 ) return; + if( p->z==0 || (xCompare(argv[0],p->z)^mask)<0 ){ + int len; + if( p->zBuf[0] ){ + sqliteFree(p->z); + } + len = strlen(argv[0]); + if( len < sizeof(p->zBuf)-1 ){ + p->z = &p->zBuf[1]; + p->zBuf[0] = 0; + }else{ + p->z = sqliteMalloc( len+1 ); + p->zBuf[0] = 1; + if( p->z==0 ) return; + } + strcpy(p->z, argv[0]); + } +} +static void minMaxFinalize(sqlite_func *context){ + MinMaxCtx *p; + p = sqlite_aggregate_context(context, sizeof(*p)); + if( p && p->z && p->zBuf[0]<2 ){ + sqlite_set_result_string(context, p->z, strlen(p->z)); + } + if( p && p->zBuf[0] ){ + sqliteFree(p->z); + } +} + +/* +** This function registered all of the above C functions as SQL +** functions. This should be the only routine in this file with +** external linkage. +*/ +void sqliteRegisterBuiltinFunctions(sqlite *db){ + static struct { + char *zName; + signed char nArg; + signed char dataType; + u8 argType; /* 0: none. 1: db 2: (-1) */ + void (*xFunc)(sqlite_func*,int,const char**); + } aFuncs[] = { + { "min", -1, SQLITE_ARGS, 0, minmaxFunc }, + { "min", 0, 0, 0, 0 }, + { "max", -1, SQLITE_ARGS, 2, minmaxFunc }, + { "max", 0, 0, 2, 0 }, + { "typeof", 1, SQLITE_TEXT, 0, typeofFunc }, + { "length", 1, SQLITE_NUMERIC, 0, lengthFunc }, + { "substr", 3, SQLITE_TEXT, 0, substrFunc }, + { "abs", 1, SQLITE_NUMERIC, 0, absFunc }, + { "round", 1, SQLITE_NUMERIC, 0, roundFunc }, + { "round", 2, SQLITE_NUMERIC, 0, roundFunc }, + { "upper", 1, SQLITE_TEXT, 0, upperFunc }, + { "lower", 1, SQLITE_TEXT, 0, lowerFunc }, + { "coalesce", -1, SQLITE_ARGS, 0, ifnullFunc }, + { "coalesce", 0, 0, 0, 0 }, + { "coalesce", 1, 0, 0, 0 }, + { "ifnull", 2, SQLITE_ARGS, 0, ifnullFunc }, + { "random", -1, SQLITE_NUMERIC, 0, randomFunc }, + { "like", 2, SQLITE_NUMERIC, 0, likeFunc }, + { "glob", 2, SQLITE_NUMERIC, 0, globFunc }, + { "nullif", 2, SQLITE_ARGS, 0, nullifFunc }, + { "sqlite_version",0,SQLITE_TEXT, 0, versionFunc}, + { "quote", 1, SQLITE_ARGS, 0, quoteFunc }, + { "last_insert_rowid", 0, SQLITE_NUMERIC, 1, last_insert_rowid }, + { "change_count", 0, SQLITE_NUMERIC, 1, change_count }, + { "last_statement_change_count", + 0, SQLITE_NUMERIC, 1, last_statement_change_count }, +#ifdef SQLITE_SOUNDEX + { "soundex", 1, SQLITE_TEXT, 0, soundexFunc}, +#endif +#ifdef SQLITE_TEST + { "randstr", 2, SQLITE_TEXT, 0, randStr }, +#endif + }; + static struct { + char *zName; + signed char nArg; + signed char dataType; + u8 argType; + void (*xStep)(sqlite_func*,int,const char**); + void (*xFinalize)(sqlite_func*); + } aAggs[] = { + { "min", 1, 0, 0, minmaxStep, minMaxFinalize }, + { "max", 1, 0, 2, minmaxStep, minMaxFinalize }, + { "sum", 1, SQLITE_NUMERIC, 0, sumStep, sumFinalize }, + { "avg", 1, SQLITE_NUMERIC, 0, sumStep, avgFinalize }, + { "count", 0, SQLITE_NUMERIC, 0, countStep, countFinalize }, + { "count", 1, SQLITE_NUMERIC, 0, countStep, countFinalize }, +#if 0 + { "stddev", 1, SQLITE_NUMERIC, 0, stdDevStep, stdDevFinalize }, +#endif + }; + static const char *azTypeFuncs[] = { "min", "max", "typeof" }; + int i; + + for(i=0; iaFunc, azTypeFuncs[i], n); + while( p ){ + p->includeTypes = 1; + p = p->pNext; + } + } + sqliteRegisterDateTimeFunctions(db); +} diff --git a/src/libs/sqlite2/hash.c b/src/libs/sqlite2/hash.c new file mode 100644 index 00000000..e0137cb3 --- /dev/null +++ b/src/libs/sqlite2/hash.c @@ -0,0 +1,356 @@ +/* +** 2001 September 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the implementation of generic hash-tables +** used in SQLite. +** +** $Id: hash.c 326789 2004-07-07 21:25:56Z pahlibar $ +*/ +#include "sqliteInt.h" +#include + +/* Turn bulk memory into a hash table object by initializing the +** fields of the Hash structure. +** +** "new" is a pointer to the hash table that is to be initialized. +** keyClass is one of the constants SQLITE_HASH_INT, SQLITE_HASH_POINTER, +** SQLITE_HASH_BINARY, or SQLITE_HASH_STRING. The value of keyClass +** determines what kind of key the hash table will use. "copyKey" is +** true if the hash table should make its own private copy of keys and +** false if it should just use the supplied pointer. CopyKey only makes +** sense for SQLITE_HASH_STRING and SQLITE_HASH_BINARY and is ignored +** for other key classes. +*/ +void sqliteHashInit(Hash *new, int keyClass, int copyKey){ + assert( new!=0 ); + assert( keyClass>=SQLITE_HASH_INT && keyClass<=SQLITE_HASH_BINARY ); + new->keyClass = keyClass; + new->copyKey = copyKey && + (keyClass==SQLITE_HASH_STRING || keyClass==SQLITE_HASH_BINARY); + new->first = 0; + new->count = 0; + new->htsize = 0; + new->ht = 0; +} + +/* Remove all entries from a hash table. Reclaim all memory. +** Call this routine to delete a hash table or to reset a hash table +** to the empty state. +*/ +void sqliteHashClear(Hash *pH){ + HashElem *elem; /* For looping over all elements of the table */ + + assert( pH!=0 ); + elem = pH->first; + pH->first = 0; + if( pH->ht ) sqliteFree(pH->ht); + pH->ht = 0; + pH->htsize = 0; + while( elem ){ + HashElem *next_elem = elem->next; + if( pH->copyKey && elem->pKey ){ + sqliteFree(elem->pKey); + } + sqliteFree(elem); + elem = next_elem; + } + pH->count = 0; +} + +/* +** Hash and comparison functions when the mode is SQLITE_HASH_INT +*/ +static int intHash(const void *pKey, int nKey){ + return nKey ^ (nKey<<8) ^ (nKey>>8); +} +static int intCompare(const void *pKey1, int n1, const void *pKey2, int n2){ + return n2 - n1; +} + +#if 0 /* NOT USED */ +/* +** Hash and comparison functions when the mode is SQLITE_HASH_POINTER +*/ +static int ptrHash(const void *pKey, int nKey){ + uptr x = Addr(pKey); + return x ^ (x<<8) ^ (x>>8); +} +static int ptrCompare(const void *pKey1, int n1, const void *pKey2, int n2){ + if( pKey1==pKey2 ) return 0; + if( pKey1 0 ){ + h = (h<<3) ^ h ^ *(z++); + } + return h & 0x7fffffff; +} +static int binCompare(const void *pKey1, int n1, const void *pKey2, int n2){ + if( n1!=n2 ) return n2-n1; + return memcmp(pKey1,pKey2,n1); +} + +/* +** Return a pointer to the appropriate hash function given the key class. +** +** The C syntax in this function definition may be unfamilar to some +** programmers, so we provide the following additional explanation: +** +** The name of the function is "hashFunction". The function takes a +** single parameter "keyClass". The return value of hashFunction() +** is a pointer to another function. Specifically, the return value +** of hashFunction() is a pointer to a function that takes two parameters +** with types "const void*" and "int" and returns an "int". +*/ +static int (*hashFunction(int keyClass))(const void*,int){ + switch( keyClass ){ + case SQLITE_HASH_INT: return &intHash; + /* case SQLITE_HASH_POINTER: return &ptrHash; // NOT USED */ + case SQLITE_HASH_STRING: return &strHash; + case SQLITE_HASH_BINARY: return &binHash;; + default: break; + } + return 0; +} + +/* +** Return a pointer to the appropriate hash function given the key class. +** +** For help in interpreted the obscure C code in the function definition, +** see the header comment on the previous function. +*/ +static int (*compareFunction(int keyClass))(const void*,int,const void*,int){ + switch( keyClass ){ + case SQLITE_HASH_INT: return &intCompare; + /* case SQLITE_HASH_POINTER: return &ptrCompare; // NOT USED */ + case SQLITE_HASH_STRING: return &strCompare; + case SQLITE_HASH_BINARY: return &binCompare; + default: break; + } + return 0; +} + + +/* Resize the hash table so that it cantains "new_size" buckets. +** "new_size" must be a power of 2. The hash table might fail +** to resize if sqliteMalloc() fails. +*/ +static void rehash(Hash *pH, int new_size){ + struct _ht *new_ht; /* The new hash table */ + HashElem *elem, *next_elem; /* For looping over existing elements */ + HashElem *x; /* Element being copied to new hash table */ + int (*xHash)(const void*,int); /* The hash function */ + + assert( (new_size & (new_size-1))==0 ); + new_ht = (struct _ht *)sqliteMalloc( new_size*sizeof(struct _ht) ); + if( new_ht==0 ) return; + if( pH->ht ) sqliteFree(pH->ht); + pH->ht = new_ht; + pH->htsize = new_size; + xHash = hashFunction(pH->keyClass); + for(elem=pH->first, pH->first=0; elem; elem = next_elem){ + int h = (*xHash)(elem->pKey, elem->nKey) & (new_size-1); + next_elem = elem->next; + x = new_ht[h].chain; + if( x ){ + elem->next = x; + elem->prev = x->prev; + if( x->prev ) x->prev->next = elem; + else pH->first = elem; + x->prev = elem; + }else{ + elem->next = pH->first; + if( pH->first ) pH->first->prev = elem; + elem->prev = 0; + pH->first = elem; + } + new_ht[h].chain = elem; + new_ht[h].count++; + } +} + +/* This function (for internal use only) locates an element in an +** hash table that matches the given key. The hash for this key has +** already been computed and is passed as the 4th parameter. +*/ +static HashElem *findElementGivenHash( + const Hash *pH, /* The pH to be searched */ + const void *pKey, /* The key we are searching for */ + int nKey, + int h /* The hash for this key. */ +){ + HashElem *elem; /* Used to loop thru the element list */ + int count; /* Number of elements left to test */ + int (*xCompare)(const void*,int,const void*,int); /* comparison function */ + + if( pH->ht ){ + elem = pH->ht[h].chain; + count = pH->ht[h].count; + xCompare = compareFunction(pH->keyClass); + while( count-- && elem ){ + if( (*xCompare)(elem->pKey,elem->nKey,pKey,nKey)==0 ){ + return elem; + } + elem = elem->next; + } + } + return 0; +} + +/* Remove a single entry from the hash table given a pointer to that +** element and a hash on the element's key. +*/ +static void removeElementGivenHash( + Hash *pH, /* The pH containing "elem" */ + HashElem* elem, /* The element to be removed from the pH */ + int h /* Hash value for the element */ +){ + if( elem->prev ){ + elem->prev->next = elem->next; + }else{ + pH->first = elem->next; + } + if( elem->next ){ + elem->next->prev = elem->prev; + } + if( pH->ht[h].chain==elem ){ + pH->ht[h].chain = elem->next; + } + pH->ht[h].count--; + if( pH->ht[h].count<=0 ){ + pH->ht[h].chain = 0; + } + if( pH->copyKey && elem->pKey ){ + sqliteFree(elem->pKey); + } + sqliteFree( elem ); + pH->count--; +} + +/* Attempt to locate an element of the hash table pH with a key +** that matches pKey,nKey. Return the data for this element if it is +** found, or NULL if there is no match. +*/ +void *sqliteHashFind(const Hash *pH, const void *pKey, int nKey){ + int h; /* A hash on key */ + HashElem *elem; /* The element that matches key */ + int (*xHash)(const void*,int); /* The hash function */ + + if( pH==0 || pH->ht==0 ) return 0; + xHash = hashFunction(pH->keyClass); + assert( xHash!=0 ); + h = (*xHash)(pKey,nKey); + assert( (pH->htsize & (pH->htsize-1))==0 ); + elem = findElementGivenHash(pH,pKey,nKey, h & (pH->htsize-1)); + return elem ? elem->data : 0; +} + +/* Insert an element into the hash table pH. The key is pKey,nKey +** and the data is "data". +** +** If no element exists with a matching key, then a new +** element is created. A copy of the key is made if the copyKey +** flag is set. NULL is returned. +** +** If another element already exists with the same key, then the +** new data replaces the old data and the old data is returned. +** The key is not copied in this instance. If a malloc fails, then +** the new data is returned and the hash table is unchanged. +** +** If the "data" parameter to this function is NULL, then the +** element corresponding to "key" is removed from the hash table. +*/ +void *sqliteHashInsert(Hash *pH, const void *pKey, int nKey, void *data){ + int hraw; /* Raw hash value of the key */ + int h; /* the hash of the key modulo hash table size */ + HashElem *elem; /* Used to loop thru the element list */ + HashElem *new_elem; /* New element added to the pH */ + int (*xHash)(const void*,int); /* The hash function */ + + assert( pH!=0 ); + xHash = hashFunction(pH->keyClass); + assert( xHash!=0 ); + hraw = (*xHash)(pKey, nKey); + assert( (pH->htsize & (pH->htsize-1))==0 ); + h = hraw & (pH->htsize-1); + elem = findElementGivenHash(pH,pKey,nKey,h); + if( elem ){ + void *old_data = elem->data; + if( data==0 ){ + removeElementGivenHash(pH,elem,h); + }else{ + elem->data = data; + } + return old_data; + } + if( data==0 ) return 0; + new_elem = (HashElem*)sqliteMalloc( sizeof(HashElem) ); + if( new_elem==0 ) return data; + if( pH->copyKey && pKey!=0 ){ + new_elem->pKey = sqliteMallocRaw( nKey ); + if( new_elem->pKey==0 ){ + sqliteFree(new_elem); + return data; + } + memcpy((void*)new_elem->pKey, pKey, nKey); + }else{ + new_elem->pKey = (void*)pKey; + } + new_elem->nKey = nKey; + pH->count++; + if( pH->htsize==0 ) rehash(pH,8); + if( pH->htsize==0 ){ + pH->count = 0; + sqliteFree(new_elem); + return data; + } + if( pH->count > pH->htsize ){ + rehash(pH,pH->htsize*2); + } + assert( (pH->htsize & (pH->htsize-1))==0 ); + h = hraw & (pH->htsize-1); + elem = pH->ht[h].chain; + if( elem ){ + new_elem->next = elem; + new_elem->prev = elem->prev; + if( elem->prev ){ elem->prev->next = new_elem; } + else { pH->first = new_elem; } + elem->prev = new_elem; + }else{ + new_elem->next = pH->first; + new_elem->prev = 0; + if( pH->first ){ pH->first->prev = new_elem; } + pH->first = new_elem; + } + pH->ht[h].count++; + pH->ht[h].chain = new_elem; + new_elem->data = data; + return 0; +} diff --git a/src/libs/sqlite2/hash.h b/src/libs/sqlite2/hash.h new file mode 100644 index 00000000..27dd30dc --- /dev/null +++ b/src/libs/sqlite2/hash.h @@ -0,0 +1,109 @@ +/* +** 2001 September 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the header file for the generic hash-table implemenation +** used in SQLite. +** +** $Id: hash.h 875429 2008-10-24 12:20:41Z cgilles $ +*/ +#ifndef _SQLITE_HASH_H_ +#define _SQLITE_HASH_H_ + +/* Forward declarations of structures. */ +typedef struct Hash Hash; +typedef struct HashElem HashElem; + +/* A complete hash table is an instance of the following structure. +** The internals of this structure are intended to be opaque -- client +** code should not attempt to access or modify the fields of this structure +** directly. Change this structure only by using the routines below. +** However, many of the "procedures" and "functions" for modifying and +** accessing this structure are really macros, so we can't really make +** this structure opaque. +*/ +struct Hash { + char keyClass; /* SQLITE_HASH_INT, _POINTER, _STRING, _BINARY */ + char copyKey; /* True if copy of key made on insert */ + int count; /* Number of entries in this table */ + HashElem *first; /* The first element of the array */ + int htsize; /* Number of buckets in the hash table */ + struct _ht { /* the hash table */ + int count; /* Number of entries with this hash */ + HashElem *chain; /* Pointer to first entry with this hash */ + } *ht; +}; + +/* Each element in the hash table is an instance of the following +** structure. All elements are stored on a single doubly-linked list. +** +** Again, this structure is intended to be opaque, but it can't really +** be opaque because it is used by macros. +*/ +struct HashElem { + HashElem *next, *prev; /* Next and previous elements in the table */ + void *data; /* Data associated with this element */ + void *pKey; int nKey; /* Key associated with this element */ +}; + +/* +** There are 4 different modes of operation for a hash table: +** +** SQLITE_HASH_INT nKey is used as the key and pKey is ignored. +** +** SQLITE_HASH_POINTER pKey is used as the key and nKey is ignored. +** +** SQLITE_HASH_STRING pKey points to a string that is nKey bytes long +** (including the null-terminator, if any). Case +** is ignored in comparisons. +** +** SQLITE_HASH_BINARY pKey points to binary data nKey bytes long. +** memcmp() is used to compare keys. +** +** A copy of the key is made for SQLITE_HASH_STRING and SQLITE_HASH_BINARY +** if the copyKey parameter to HashInit is 1. +*/ +#define SQLITE_HASH_INT 1 +/* #define SQLITE_HASH_POINTER 2 // NOT USED */ +#define SQLITE_HASH_STRING 3 +#define SQLITE_HASH_BINARY 4 + +/* +** Access routines. To delete, insert a NULL pointer. +*/ +void sqliteHashInit(Hash*, int keytype, int copyKey); +void *sqliteHashInsert(Hash*, const void *pKey, int nKey, void *pData); +void *sqliteHashFind(const Hash*, const void *pKey, int nKey); +void sqliteHashClear(Hash*); + +/* +** Macros for looping over all elements of a hash table. The idiom is +** like this: +** +** Hash h; +** HashElem *p; +** ... +** for(p=sqliteHashFirst(&h); p; p=sqliteHashNext(p)){ +** SomeStructure *pData = sqliteHashData(p); +** // do something with pData +** } +*/ +#define sqliteHashFirst(H) ((H)->first) +#define sqliteHashNext(E) ((E)->next) +#define sqliteHashData(E) ((E)->data) +#define sqliteHashKey(E) ((E)->pKey) +#define sqliteHashKeysize(E) ((E)->nKey) + +/* +** Number of entries in a hash table +*/ +#define sqliteHashCount(H) ((H)->count) + +#endif /* _SQLITE_HASH_H_ */ diff --git a/src/libs/sqlite2/insert.c b/src/libs/sqlite2/insert.c new file mode 100644 index 00000000..2f73db4a --- /dev/null +++ b/src/libs/sqlite2/insert.c @@ -0,0 +1,919 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C code routines that are called by the parser +** to handle INSERT statements in SQLite. +** +** $Id: insert.c 326789 2004-07-07 21:25:56Z pahlibar $ +*/ +#include "sqliteInt.h" + +/* +** This routine is call to handle SQL of the following forms: +** +** insert into TABLE (IDLIST) values(EXPRLIST) +** insert into TABLE (IDLIST) select +** +** The IDLIST following the table name is always optional. If omitted, +** then a list of all columns for the table is substituted. The IDLIST +** appears in the pColumn parameter. pColumn is NULL if IDLIST is omitted. +** +** The pList parameter holds EXPRLIST in the first form of the INSERT +** statement above, and pSelect is NULL. For the second form, pList is +** NULL and pSelect is a pointer to the select statement used to generate +** data for the insert. +** +** The code generated follows one of three templates. For a simple +** select with data coming from a VALUES clause, the code executes +** once straight down through. The template looks like this: +** +** open write cursor to and its indices +** puts VALUES clause expressions onto the stack +** write the resulting record into
    +** cleanup +** +** If the statement is of the form +** +** INSERT INTO
    SELECT ... +** +** And the SELECT clause does not read from
    at any time, then +** the generated code follows this template: +** +** goto B +** A: setup for the SELECT +** loop over the tables in the SELECT +** gosub C +** end loop +** cleanup after the SELECT +** goto D +** B: open write cursor to
    and its indices +** goto A +** C: insert the select result into
    +** return +** D: cleanup +** +** The third template is used if the insert statement takes its +** values from a SELECT but the data is being inserted into a table +** that is also read as part of the SELECT. In the third form, +** we have to use a intermediate table to store the results of +** the select. The template is like this: +** +** goto B +** A: setup for the SELECT +** loop over the tables in the SELECT +** gosub C +** end loop +** cleanup after the SELECT +** goto D +** C: insert the select result into the intermediate table +** return +** B: open a cursor to an intermediate table +** goto A +** D: open write cursor to
    and its indices +** loop over the intermediate table +** transfer values form intermediate table into
    +** end the loop +** cleanup +*/ +void sqliteInsert( + Parse *pParse, /* Parser context */ + SrcList *pTabList, /* Name of table into which we are inserting */ + ExprList *pList, /* List of values to be inserted */ + Select *pSelect, /* A SELECT statement to use as the data source */ + IdList *pColumn, /* Column names corresponding to IDLIST. */ + int onError /* How to handle constraint errors */ +){ + Table *pTab; /* The table to insert into */ + char *zTab; /* Name of the table into which we are inserting */ + const char *zDb; /* Name of the database holding this table */ + int i, j, idx; /* Loop counters */ + Vdbe *v; /* Generate code into this virtual machine */ + Index *pIdx; /* For looping over indices of the table */ + int nColumn; /* Number of columns in the data */ + int base; /* VDBE Cursor number for pTab */ + int iCont, iBreak; /* Beginning and end of the loop over srcTab */ + sqlite *db; /* The main database structure */ + int keyColumn = -1; /* Column that is the INTEGER PRIMARY KEY */ + int endOfLoop; /* Label for the end of the insertion loop */ + int useTempTable; /* Store SELECT results in intermediate table */ + int srcTab; /* Data comes from this temporary cursor if >=0 */ + int iSelectLoop; /* Address of code that implements the SELECT */ + int iCleanup; /* Address of the cleanup code */ + int iInsertBlock; /* Address of the subroutine used to insert data */ + int iCntMem; /* Memory cell used for the row counter */ + int isView; /* True if attempting to insert into a view */ + + int row_triggers_exist = 0; /* True if there are FOR EACH ROW triggers */ + int before_triggers; /* True if there are BEFORE triggers */ + int after_triggers; /* True if there are AFTER triggers */ + int newIdx = -1; /* Cursor for the NEW table */ + + if( pParse->nErr || sqlite_malloc_failed ) goto insert_cleanup; + db = pParse->db; + + /* Locate the table into which we will be inserting new information. + */ + assert( pTabList->nSrc==1 ); + zTab = pTabList->a[0].zName; + if( zTab==0 ) goto insert_cleanup; + pTab = sqliteSrcListLookup(pParse, pTabList); + if( pTab==0 ){ + goto insert_cleanup; + } + assert( pTab->iDbnDb ); + zDb = db->aDb[pTab->iDb].zName; + if( sqliteAuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, zDb) ){ + goto insert_cleanup; + } + + /* Ensure that: + * (a) the table is not read-only, + * (b) that if it is a view then ON INSERT triggers exist + */ + before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, TK_INSERT, + TK_BEFORE, TK_ROW, 0); + after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, TK_INSERT, + TK_AFTER, TK_ROW, 0); + row_triggers_exist = before_triggers || after_triggers; + isView = pTab->pSelect!=0; + if( sqliteIsReadOnly(pParse, pTab, before_triggers) ){ + goto insert_cleanup; + } + if( pTab==0 ) goto insert_cleanup; + + /* If pTab is really a view, make sure it has been initialized. + */ + if( isView && sqliteViewGetColumnNames(pParse, pTab) ){ + goto insert_cleanup; + } + + /* Allocate a VDBE + */ + v = sqliteGetVdbe(pParse); + if( v==0 ) goto insert_cleanup; + sqliteBeginWriteOperation(pParse, pSelect || row_triggers_exist, pTab->iDb); + + /* if there are row triggers, allocate a temp table for new.* references. */ + if( row_triggers_exist ){ + newIdx = pParse->nTab++; + } + + /* Figure out how many columns of data are supplied. If the data + ** is coming from a SELECT statement, then this step also generates + ** all the code to implement the SELECT statement and invoke a subroutine + ** to process each row of the result. (Template 2.) If the SELECT + ** statement uses the the table that is being inserted into, then the + ** subroutine is also coded here. That subroutine stores the SELECT + ** results in a temporary table. (Template 3.) + */ + if( pSelect ){ + /* Data is coming from a SELECT. Generate code to implement that SELECT + */ + int rc, iInitCode; + iInitCode = sqliteVdbeAddOp(v, OP_Goto, 0, 0); + iSelectLoop = sqliteVdbeCurrentAddr(v); + iInsertBlock = sqliteVdbeMakeLabel(v); + rc = sqliteSelect(pParse, pSelect, SRT_Subroutine, iInsertBlock, 0,0,0); + if( rc || pParse->nErr || sqlite_malloc_failed ) goto insert_cleanup; + iCleanup = sqliteVdbeMakeLabel(v); + sqliteVdbeAddOp(v, OP_Goto, 0, iCleanup); + assert( pSelect->pEList ); + nColumn = pSelect->pEList->nExpr; + + /* Set useTempTable to TRUE if the result of the SELECT statement + ** should be written into a temporary table. Set to FALSE if each + ** row of the SELECT can be written directly into the result table. + ** + ** A temp table must be used if the table being updated is also one + ** of the tables being read by the SELECT statement. Also use a + ** temp table in the case of row triggers. + */ + if( row_triggers_exist ){ + useTempTable = 1; + }else{ + int addr = sqliteVdbeFindOp(v, OP_OpenRead, pTab->tnum); + useTempTable = 0; + if( addr>0 ){ + VdbeOp *pOp = sqliteVdbeGetOp(v, addr-2); + if( pOp->opcode==OP_Integer && pOp->p1==pTab->iDb ){ + useTempTable = 1; + } + } + } + + if( useTempTable ){ + /* Generate the subroutine that SELECT calls to process each row of + ** the result. Store the result in a temporary table + */ + srcTab = pParse->nTab++; + sqliteVdbeResolveLabel(v, iInsertBlock); + sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 0); + sqliteVdbeAddOp(v, OP_NewRecno, srcTab, 0); + sqliteVdbeAddOp(v, OP_Pull, 1, 0); + sqliteVdbeAddOp(v, OP_PutIntKey, srcTab, 0); + sqliteVdbeAddOp(v, OP_Return, 0, 0); + + /* The following code runs first because the GOTO at the very top + ** of the program jumps to it. Create the temporary table, then jump + ** back up and execute the SELECT code above. + */ + sqliteVdbeChangeP2(v, iInitCode, sqliteVdbeCurrentAddr(v)); + sqliteVdbeAddOp(v, OP_OpenTemp, srcTab, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, iSelectLoop); + sqliteVdbeResolveLabel(v, iCleanup); + }else{ + sqliteVdbeChangeP2(v, iInitCode, sqliteVdbeCurrentAddr(v)); + } + }else{ + /* This is the case if the data for the INSERT is coming from a VALUES + ** clause + */ + SrcList dummy; + assert( pList!=0 ); + srcTab = -1; + useTempTable = 0; + assert( pList ); + nColumn = pList->nExpr; + dummy.nSrc = 0; + for(i=0; ia[i].pExpr) ){ + goto insert_cleanup; + } + if( sqliteExprCheck(pParse, pList->a[i].pExpr, 0, 0) ){ + goto insert_cleanup; + } + } + } + + /* Make sure the number of columns in the source data matches the number + ** of columns to be inserted into the table. + */ + if( pColumn==0 && nColumn!=pTab->nCol ){ + sqliteErrorMsg(pParse, + "table %S has %d columns but %d values were supplied", + pTabList, 0, pTab->nCol, nColumn); + goto insert_cleanup; + } + if( pColumn!=0 && nColumn!=pColumn->nId ){ + sqliteErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn->nId); + goto insert_cleanup; + } + + /* If the INSERT statement included an IDLIST term, then make sure + ** all elements of the IDLIST really are columns of the table and + ** remember the column indices. + ** + ** If the table has an INTEGER PRIMARY KEY column and that column + ** is named in the IDLIST, then record in the keyColumn variable + ** the index into IDLIST of the primary key column. keyColumn is + ** the index of the primary key as it appears in IDLIST, not as + ** is appears in the original table. (The index of the primary + ** key in the original table is pTab->iPKey.) + */ + if( pColumn ){ + for(i=0; inId; i++){ + pColumn->a[i].idx = -1; + } + for(i=0; inId; i++){ + for(j=0; jnCol; j++){ + if( sqliteStrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){ + pColumn->a[i].idx = j; + if( j==pTab->iPKey ){ + keyColumn = i; + } + break; + } + } + if( j>=pTab->nCol ){ + if( sqliteIsRowid(pColumn->a[i].zName) ){ + keyColumn = i; + }else{ + sqliteErrorMsg(pParse, "table %S has no column named %s", + pTabList, 0, pColumn->a[i].zName); + pParse->nErr++; + goto insert_cleanup; + } + } + } + } + + /* If there is no IDLIST term but the table has an integer primary + ** key, the set the keyColumn variable to the primary key column index + ** in the original table definition. + */ + if( pColumn==0 ){ + keyColumn = pTab->iPKey; + } + + /* Open the temp table for FOR EACH ROW triggers + */ + if( row_triggers_exist ){ + sqliteVdbeAddOp(v, OP_OpenPseudo, newIdx, 0); + } + + /* Initialize the count of rows to be inserted + */ + if( db->flags & SQLITE_CountRows ){ + iCntMem = pParse->nMem++; + sqliteVdbeAddOp(v, OP_Integer, 0, 0); + sqliteVdbeAddOp(v, OP_MemStore, iCntMem, 1); + } + + /* Open tables and indices if there are no row triggers */ + if( !row_triggers_exist ){ + base = pParse->nTab; + idx = sqliteOpenTableAndIndices(pParse, pTab, base); + pParse->nTab += idx; + } + + /* If the data source is a temporary table, then we have to create + ** a loop because there might be multiple rows of data. If the data + ** source is a subroutine call from the SELECT statement, then we need + ** to launch the SELECT statement processing. + */ + if( useTempTable ){ + iBreak = sqliteVdbeMakeLabel(v); + sqliteVdbeAddOp(v, OP_Rewind, srcTab, iBreak); + iCont = sqliteVdbeCurrentAddr(v); + }else if( pSelect ){ + sqliteVdbeAddOp(v, OP_Goto, 0, iSelectLoop); + sqliteVdbeResolveLabel(v, iInsertBlock); + } + + /* Run the BEFORE and INSTEAD OF triggers, if there are any + */ + endOfLoop = sqliteVdbeMakeLabel(v); + if( before_triggers ){ + + /* build the NEW.* reference row. Note that if there is an INTEGER + ** PRIMARY KEY into which a NULL is being inserted, that NULL will be + ** translated into a unique ID for the row. But on a BEFORE trigger, + ** we do not know what the unique ID will be (because the insert has + ** not happened yet) so we substitute a rowid of -1 + */ + if( keyColumn<0 ){ + sqliteVdbeAddOp(v, OP_Integer, -1, 0); + }else if( useTempTable ){ + sqliteVdbeAddOp(v, OP_Column, srcTab, keyColumn); + }else if( pSelect ){ + sqliteVdbeAddOp(v, OP_Dup, nColumn - keyColumn - 1, 1); + }else{ + sqliteExprCode(pParse, pList->a[keyColumn].pExpr); + sqliteVdbeAddOp(v, OP_NotNull, -1, sqliteVdbeCurrentAddr(v)+3); + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + sqliteVdbeAddOp(v, OP_Integer, -1, 0); + sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0); + } + + /* Create the new column data + */ + for(i=0; inCol; i++){ + if( pColumn==0 ){ + j = i; + }else{ + for(j=0; jnId; j++){ + if( pColumn->a[j].idx==i ) break; + } + } + if( pColumn && j>=pColumn->nId ){ + sqliteVdbeOp3(v, OP_String, 0, 0, pTab->aCol[i].zDflt, P3_STATIC); + }else if( useTempTable ){ + sqliteVdbeAddOp(v, OP_Column, srcTab, j); + }else if( pSelect ){ + sqliteVdbeAddOp(v, OP_Dup, nColumn-j-1, 1); + }else{ + sqliteExprCode(pParse, pList->a[j].pExpr); + } + } + sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); + sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0); + + /* Fire BEFORE or INSTEAD OF triggers */ + if( sqliteCodeRowTrigger(pParse, TK_INSERT, 0, TK_BEFORE, pTab, + newIdx, -1, onError, endOfLoop) ){ + goto insert_cleanup; + } + } + + /* If any triggers exists, the opening of tables and indices is deferred + ** until now. + */ + if( row_triggers_exist && !isView ){ + base = pParse->nTab; + idx = sqliteOpenTableAndIndices(pParse, pTab, base); + pParse->nTab += idx; + } + + /* Push the record number for the new entry onto the stack. The + ** record number is a randomly generate integer created by NewRecno + ** except when the table has an INTEGER PRIMARY KEY column, in which + ** case the record number is the same as that column. + */ + if( !isView ){ + if( keyColumn>=0 ){ + if( useTempTable ){ + sqliteVdbeAddOp(v, OP_Column, srcTab, keyColumn); + }else if( pSelect ){ + sqliteVdbeAddOp(v, OP_Dup, nColumn - keyColumn - 1, 1); + }else{ + sqliteExprCode(pParse, pList->a[keyColumn].pExpr); + } + /* If the PRIMARY KEY expression is NULL, then use OP_NewRecno + ** to generate a unique primary key value. + */ + sqliteVdbeAddOp(v, OP_NotNull, -1, sqliteVdbeCurrentAddr(v)+3); + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + sqliteVdbeAddOp(v, OP_NewRecno, base, 0); + sqliteVdbeAddOp(v, OP_MustBeInt, 0, 0); + }else{ + sqliteVdbeAddOp(v, OP_NewRecno, base, 0); + } + + /* Push onto the stack, data for all columns of the new entry, beginning + ** with the first column. + */ + for(i=0; inCol; i++){ + if( i==pTab->iPKey ){ + /* The value of the INTEGER PRIMARY KEY column is always a NULL. + ** Whenever this column is read, the record number will be substituted + ** in its place. So will fill this column with a NULL to avoid + ** taking up data space with information that will never be used. */ + sqliteVdbeAddOp(v, OP_String, 0, 0); + continue; + } + if( pColumn==0 ){ + j = i; + }else{ + for(j=0; jnId; j++){ + if( pColumn->a[j].idx==i ) break; + } + } + if( pColumn && j>=pColumn->nId ){ + sqliteVdbeOp3(v, OP_String, 0, 0, pTab->aCol[i].zDflt, P3_STATIC); + }else if( useTempTable ){ + sqliteVdbeAddOp(v, OP_Column, srcTab, j); + }else if( pSelect ){ + sqliteVdbeAddOp(v, OP_Dup, i+nColumn-j, 1); + }else{ + sqliteExprCode(pParse, pList->a[j].pExpr); + } + } + + /* Generate code to check constraints and generate index keys and + ** do the insertion. + */ + sqliteGenerateConstraintChecks(pParse, pTab, base, 0, keyColumn>=0, + 0, onError, endOfLoop); + sqliteCompleteInsertion(pParse, pTab, base, 0,0,0, + after_triggers ? newIdx : -1); + } + + /* Update the count of rows that are inserted + */ + if( (db->flags & SQLITE_CountRows)!=0 ){ + sqliteVdbeAddOp(v, OP_MemIncr, iCntMem, 0); + } + + if( row_triggers_exist ){ + /* Close all tables opened */ + if( !isView ){ + sqliteVdbeAddOp(v, OP_Close, base, 0); + for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ + sqliteVdbeAddOp(v, OP_Close, idx+base, 0); + } + } + + /* Code AFTER triggers */ + if( sqliteCodeRowTrigger(pParse, TK_INSERT, 0, TK_AFTER, pTab, newIdx, -1, + onError, endOfLoop) ){ + goto insert_cleanup; + } + } + + /* The bottom of the loop, if the data source is a SELECT statement + */ + sqliteVdbeResolveLabel(v, endOfLoop); + if( useTempTable ){ + sqliteVdbeAddOp(v, OP_Next, srcTab, iCont); + sqliteVdbeResolveLabel(v, iBreak); + sqliteVdbeAddOp(v, OP_Close, srcTab, 0); + }else if( pSelect ){ + sqliteVdbeAddOp(v, OP_Pop, nColumn, 0); + sqliteVdbeAddOp(v, OP_Return, 0, 0); + sqliteVdbeResolveLabel(v, iCleanup); + } + + if( !row_triggers_exist ){ + /* Close all tables opened */ + sqliteVdbeAddOp(v, OP_Close, base, 0); + for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ + sqliteVdbeAddOp(v, OP_Close, idx+base, 0); + } + } + + sqliteVdbeAddOp(v, OP_SetCounts, 0, 0); + sqliteEndWriteOperation(pParse); + + /* + ** Return the number of rows inserted. + */ + if( db->flags & SQLITE_CountRows ){ + sqliteVdbeOp3(v, OP_ColumnName, 0, 1, "rows inserted", P3_STATIC); + sqliteVdbeAddOp(v, OP_MemLoad, iCntMem, 0); + sqliteVdbeAddOp(v, OP_Callback, 1, 0); + } + +insert_cleanup: + sqliteSrcListDelete(pTabList); + if( pList ) sqliteExprListDelete(pList); + if( pSelect ) sqliteSelectDelete(pSelect); + sqliteIdListDelete(pColumn); +} + +/* +** Generate code to do a constraint check prior to an INSERT or an UPDATE. +** +** When this routine is called, the stack contains (from bottom to top) +** the following values: +** +** 1. The recno of the row to be updated before the update. This +** value is omitted unless we are doing an UPDATE that involves a +** change to the record number. +** +** 2. The recno of the row after the update. +** +** 3. The data in the first column of the entry after the update. +** +** i. Data from middle columns... +** +** N. The data in the last column of the entry after the update. +** +** The old recno shown as entry (1) above is omitted unless both isUpdate +** and recnoChng are 1. isUpdate is true for UPDATEs and false for +** INSERTs and recnoChng is true if the record number is being changed. +** +** The code generated by this routine pushes additional entries onto +** the stack which are the keys for new index entries for the new record. +** The order of index keys is the same as the order of the indices on +** the pTable->pIndex list. A key is only created for index i if +** aIdxUsed!=0 and aIdxUsed[i]!=0. +** +** This routine also generates code to check constraints. NOT NULL, +** CHECK, and UNIQUE constraints are all checked. If a constraint fails, +** then the appropriate action is performed. There are five possible +** actions: ROLLBACK, ABORT, FAIL, REPLACE, and IGNORE. +** +** Constraint type Action What Happens +** --------------- ---------- ---------------------------------------- +** any ROLLBACK The current transaction is rolled back and +** sqlite_exec() returns immediately with a +** return code of SQLITE_CONSTRAINT. +** +** any ABORT Back out changes from the current command +** only (do not do a complete rollback) then +** cause sqlite_exec() to return immediately +** with SQLITE_CONSTRAINT. +** +** any FAIL Sqlite_exec() returns immediately with a +** return code of SQLITE_CONSTRAINT. The +** transaction is not rolled back and any +** prior changes are retained. +** +** any IGNORE The record number and data is popped from +** the stack and there is an immediate jump +** to label ignoreDest. +** +** NOT NULL REPLACE The NULL value is replace by the default +** value for that column. If the default value +** is NULL, the action is the same as ABORT. +** +** UNIQUE REPLACE The other row that conflicts with the row +** being inserted is removed. +** +** CHECK REPLACE Illegal. The results in an exception. +** +** Which action to take is determined by the overrideError parameter. +** Or if overrideError==OE_Default, then the pParse->onError parameter +** is used. Or if pParse->onError==OE_Default then the onError value +** for the constraint is used. +** +** The calling routine must open a read/write cursor for pTab with +** cursor number "base". All indices of pTab must also have open +** read/write cursors with cursor number base+i for the i-th cursor. +** Except, if there is no possibility of a REPLACE action then +** cursors do not need to be open for indices where aIdxUsed[i]==0. +** +** If the isUpdate flag is true, it means that the "base" cursor is +** initially pointing to an entry that is being updated. The isUpdate +** flag causes extra code to be generated so that the "base" cursor +** is still pointing at the same entry after the routine returns. +** Without the isUpdate flag, the "base" cursor might be moved. +*/ +void sqliteGenerateConstraintChecks( + Parse *pParse, /* The parser context */ + Table *pTab, /* the table into which we are inserting */ + int base, /* Index of a read/write cursor pointing at pTab */ + char *aIdxUsed, /* Which indices are used. NULL means all are used */ + int recnoChng, /* True if the record number will change */ + int isUpdate, /* True for UPDATE, False for INSERT */ + int overrideError, /* Override onError to this if not OE_Default */ + int ignoreDest /* Jump to this label on an OE_Ignore resolution */ +){ + int i; + Vdbe *v; + int nCol; + int onError; + int addr; + int extra; + int iCur; + Index *pIdx; + int seenReplace = 0; + int jumpInst1, jumpInst2; + int contAddr; + int hasTwoRecnos = (isUpdate && recnoChng); + + v = sqliteGetVdbe(pParse); + assert( v!=0 ); + assert( pTab->pSelect==0 ); /* This table is not a VIEW */ + nCol = pTab->nCol; + + /* Test all NOT NULL constraints. + */ + for(i=0; iiPKey ){ + continue; + } + onError = pTab->aCol[i].notNull; + if( onError==OE_None ) continue; + if( overrideError!=OE_Default ){ + onError = overrideError; + }else if( pParse->db->onError!=OE_Default ){ + onError = pParse->db->onError; + }else if( onError==OE_Default ){ + onError = OE_Abort; + } + if( onError==OE_Replace && pTab->aCol[i].zDflt==0 ){ + onError = OE_Abort; + } + sqliteVdbeAddOp(v, OP_Dup, nCol-1-i, 1); + addr = sqliteVdbeAddOp(v, OP_NotNull, 1, 0); + switch( onError ){ + case OE_Rollback: + case OE_Abort: + case OE_Fail: { + char *zMsg = 0; + sqliteVdbeAddOp(v, OP_Halt, SQLITE_CONSTRAINT, onError); + sqliteSetString(&zMsg, pTab->zName, ".", pTab->aCol[i].zName, + " may not be NULL", (char*)0); + sqliteVdbeChangeP3(v, -1, zMsg, P3_DYNAMIC); + break; + } + case OE_Ignore: { + sqliteVdbeAddOp(v, OP_Pop, nCol+1+hasTwoRecnos, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, ignoreDest); + break; + } + case OE_Replace: { + sqliteVdbeOp3(v, OP_String, 0, 0, pTab->aCol[i].zDflt, P3_STATIC); + sqliteVdbeAddOp(v, OP_Push, nCol-i, 0); + break; + } + default: assert(0); + } + sqliteVdbeChangeP2(v, addr, sqliteVdbeCurrentAddr(v)); + } + + /* Test all CHECK constraints + */ + /**** TBD ****/ + + /* If we have an INTEGER PRIMARY KEY, make sure the primary key + ** of the new record does not previously exist. Except, if this + ** is an UPDATE and the primary key is not changing, that is OK. + */ + if( recnoChng ){ + onError = pTab->keyConf; + if( overrideError!=OE_Default ){ + onError = overrideError; + }else if( pParse->db->onError!=OE_Default ){ + onError = pParse->db->onError; + }else if( onError==OE_Default ){ + onError = OE_Abort; + } + + if( isUpdate ){ + sqliteVdbeAddOp(v, OP_Dup, nCol+1, 1); + sqliteVdbeAddOp(v, OP_Dup, nCol+1, 1); + jumpInst1 = sqliteVdbeAddOp(v, OP_Eq, 0, 0); + } + sqliteVdbeAddOp(v, OP_Dup, nCol, 1); + jumpInst2 = sqliteVdbeAddOp(v, OP_NotExists, base, 0); + switch( onError ){ + default: { + onError = OE_Abort; + /* Fall thru into the next case */ + } + case OE_Rollback: + case OE_Abort: + case OE_Fail: { + sqliteVdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, onError, + "PRIMARY KEY must be unique", P3_STATIC); + break; + } + case OE_Replace: { + sqliteGenerateRowIndexDelete(pParse->db, v, pTab, base, 0); + if( isUpdate ){ + sqliteVdbeAddOp(v, OP_Dup, nCol+hasTwoRecnos, 1); + sqliteVdbeAddOp(v, OP_MoveTo, base, 0); + } + seenReplace = 1; + break; + } + case OE_Ignore: { + assert( seenReplace==0 ); + sqliteVdbeAddOp(v, OP_Pop, nCol+1+hasTwoRecnos, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, ignoreDest); + break; + } + } + contAddr = sqliteVdbeCurrentAddr(v); + sqliteVdbeChangeP2(v, jumpInst2, contAddr); + if( isUpdate ){ + sqliteVdbeChangeP2(v, jumpInst1, contAddr); + sqliteVdbeAddOp(v, OP_Dup, nCol+1, 1); + sqliteVdbeAddOp(v, OP_MoveTo, base, 0); + } + } + + /* Test all UNIQUE constraints by creating entries for each UNIQUE + ** index and making sure that duplicate entries do not already exist. + ** Add the new records to the indices as we go. + */ + extra = -1; + for(iCur=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, iCur++){ + if( aIdxUsed && aIdxUsed[iCur]==0 ) continue; /* Skip unused indices */ + extra++; + + /* Create a key for accessing the index entry */ + sqliteVdbeAddOp(v, OP_Dup, nCol+extra, 1); + for(i=0; inColumn; i++){ + int idx = pIdx->aiColumn[i]; + if( idx==pTab->iPKey ){ + sqliteVdbeAddOp(v, OP_Dup, i+extra+nCol+1, 1); + }else{ + sqliteVdbeAddOp(v, OP_Dup, i+extra+nCol-idx, 1); + } + } + jumpInst1 = sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0); + if( pParse->db->file_format>=4 ) sqliteAddIdxKeyType(v, pIdx); + + /* Find out what action to take in case there is an indexing conflict */ + onError = pIdx->onError; + if( onError==OE_None ) continue; /* pIdx is not a UNIQUE index */ + if( overrideError!=OE_Default ){ + onError = overrideError; + }else if( pParse->db->onError!=OE_Default ){ + onError = pParse->db->onError; + }else if( onError==OE_Default ){ + onError = OE_Abort; + } + if( seenReplace ){ + if( onError==OE_Ignore ) onError = OE_Replace; + else if( onError==OE_Fail ) onError = OE_Abort; + } + + + /* Check to see if the new index entry will be unique */ + sqliteVdbeAddOp(v, OP_Dup, extra+nCol+1+hasTwoRecnos, 1); + jumpInst2 = sqliteVdbeAddOp(v, OP_IsUnique, base+iCur+1, 0); + + /* Generate code that executes if the new index entry is not unique */ + switch( onError ){ + case OE_Rollback: + case OE_Abort: + case OE_Fail: { + int j, n1, n2; + char zErrMsg[200]; + strcpy(zErrMsg, pIdx->nColumn>1 ? "columns " : "column "); + n1 = strlen(zErrMsg); + for(j=0; jnColumn && n1aCol[pIdx->aiColumn[j]].zName; + n2 = strlen(zCol); + if( j>0 ){ + strcpy(&zErrMsg[n1], ", "); + n1 += 2; + } + if( n1+n2>sizeof(zErrMsg)-30 ){ + strcpy(&zErrMsg[n1], "..."); + n1 += 3; + break; + }else{ + strcpy(&zErrMsg[n1], zCol); + n1 += n2; + } + } + strcpy(&zErrMsg[n1], + pIdx->nColumn>1 ? " are not unique" : " is not unique"); + sqliteVdbeOp3(v, OP_Halt, SQLITE_CONSTRAINT, onError, zErrMsg, 0); + break; + } + case OE_Ignore: { + assert( seenReplace==0 ); + sqliteVdbeAddOp(v, OP_Pop, nCol+extra+3+hasTwoRecnos, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, ignoreDest); + break; + } + case OE_Replace: { + sqliteGenerateRowDelete(pParse->db, v, pTab, base, 0); + if( isUpdate ){ + sqliteVdbeAddOp(v, OP_Dup, nCol+extra+1+hasTwoRecnos, 1); + sqliteVdbeAddOp(v, OP_MoveTo, base, 0); + } + seenReplace = 1; + break; + } + default: assert(0); + } + contAddr = sqliteVdbeCurrentAddr(v); +#if NULL_DISTINCT_FOR_UNIQUE + sqliteVdbeChangeP2(v, jumpInst1, contAddr); +#endif + sqliteVdbeChangeP2(v, jumpInst2, contAddr); + } +} + +/* +** This routine generates code to finish the INSERT or UPDATE operation +** that was started by a prior call to sqliteGenerateConstraintChecks. +** The stack must contain keys for all active indices followed by data +** and the recno for the new entry. This routine creates the new +** entries in all indices and in the main table. +** +** The arguments to this routine should be the same as the first six +** arguments to sqliteGenerateConstraintChecks. +*/ +void sqliteCompleteInsertion( + Parse *pParse, /* The parser context */ + Table *pTab, /* the table into which we are inserting */ + int base, /* Index of a read/write cursor pointing at pTab */ + char *aIdxUsed, /* Which indices are used. NULL means all are used */ + int recnoChng, /* True if the record number will change */ + int isUpdate, /* True for UPDATE, False for INSERT */ + int newIdx /* Index of NEW table for triggers. -1 if none */ +){ + int i; + Vdbe *v; + int nIdx; + Index *pIdx; + + v = sqliteGetVdbe(pParse); + assert( v!=0 ); + assert( pTab->pSelect==0 ); /* This table is not a VIEW */ + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){} + for(i=nIdx-1; i>=0; i--){ + if( aIdxUsed && aIdxUsed[i]==0 ) continue; + sqliteVdbeAddOp(v, OP_IdxPut, base+i+1, 0); + } + sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0); + if( newIdx>=0 ){ + sqliteVdbeAddOp(v, OP_Dup, 1, 0); + sqliteVdbeAddOp(v, OP_Dup, 1, 0); + sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0); + } + sqliteVdbeAddOp(v, OP_PutIntKey, base, + (pParse->trigStack?0:OPFLAG_NCHANGE) | + (isUpdate?0:OPFLAG_LASTROWID) | OPFLAG_CSCHANGE); + if( isUpdate && recnoChng ){ + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + } +} + +/* +** Generate code that will open write cursors for a table and for all +** indices of that table. The "base" parameter is the cursor number used +** for the table. Indices are opened on subsequent cursors. +** +** Return the total number of cursors opened. This is always at least +** 1 (for the main table) plus more for each cursor. +*/ +int sqliteOpenTableAndIndices(Parse *pParse, Table *pTab, int base){ + int i; + Index *pIdx; + Vdbe *v = sqliteGetVdbe(pParse); + assert( v!=0 ); + sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqliteVdbeOp3(v, OP_OpenWrite, base, pTab->tnum, pTab->zName, P3_STATIC); + for(i=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0); + sqliteVdbeOp3(v, OP_OpenWrite, i+base, pIdx->tnum, pIdx->zName, P3_STATIC); + } + return i; +} diff --git a/src/libs/sqlite2/main.c b/src/libs/sqlite2/main.c new file mode 100644 index 00000000..7541b171 --- /dev/null +++ b/src/libs/sqlite2/main.c @@ -0,0 +1,1143 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Main file for the SQLite library. The routines in this file +** implement the programmer interface to the library. Routines in +** other files are for internal use by SQLite and should not be +** accessed by users of the library. +** +** $Id: main.c 875429 2008-10-24 12:20:41Z cgilles $ +*/ +#include "sqliteInt.h" +#include "os.h" +#include + +/* +** A pointer to this structure is used to communicate information +** from sqliteInit into the sqliteInitCallback. +*/ +typedef struct { + sqlite *db; /* The database being initialized */ + char **pzErrMsg; /* Error message stored here */ +} InitData; + +/* +** Fill the InitData structure with an error message that indicates +** that the database is corrupt. +*/ +static void corruptSchema(InitData *pData, const char *zExtra){ + sqliteSetString(pData->pzErrMsg, "malformed database schema", + zExtra!=0 && zExtra[0]!=0 ? " - " : (char*)0, zExtra, (char*)0); +} + +/* +** This is the callback routine for the code that initializes the +** database. See sqliteInit() below for additional information. +** +** Each callback contains the following information: +** +** argv[0] = "file-format" or "schema-cookie" or "table" or "index" +** argv[1] = table or index name or meta statement type. +** argv[2] = root page number for table or index. NULL for meta. +** argv[3] = SQL text for a CREATE TABLE or CREATE INDEX statement. +** argv[4] = "1" for temporary files, "0" for main database, "2" or more +** for auxiliary database files. +** +*/ +static +int sqliteInitCallback(void *pInit, int argc, char **argv, char **azColName){ + InitData *pData = (InitData*)pInit; + int nErr = 0; + + assert( argc==5 ); + if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */ + if( argv[0]==0 ){ + corruptSchema(pData, 0); + return 1; + } + switch( argv[0][0] ){ + case 'v': + case 'i': + case 't': { /* CREATE TABLE, CREATE INDEX, or CREATE VIEW statements */ + sqlite *db = pData->db; + if( argv[2]==0 || argv[4]==0 ){ + corruptSchema(pData, 0); + return 1; + } + if( argv[3] && argv[3][0] ){ + /* Call the parser to process a CREATE TABLE, INDEX or VIEW. + ** But because db->init.busy is set to 1, no VDBE code is generated + ** or executed. All the parser does is build the internal data + ** structures that describe the table, index, or view. + */ + char *zErr; + assert( db->init.busy ); + db->init.iDb = atoi(argv[4]); + assert( db->init.iDb>=0 && db->init.iDbnDb ); + db->init.newTnum = atoi(argv[2]); + if( sqlite_exec(db, argv[3], 0, 0, &zErr) ){ + corruptSchema(pData, zErr); + sqlite_freemem(zErr); + } + db->init.iDb = 0; + }else{ + /* If the SQL column is blank it means this is an index that + ** was created to be the PRIMARY KEY or to fulfill a UNIQUE + ** constraint for a CREATE TABLE. The index should have already + ** been created when we processed the CREATE TABLE. All we have + ** to do here is record the root page number for that index. + */ + int iDb; + Index *pIndex; + + iDb = atoi(argv[4]); + assert( iDb>=0 && iDbnDb ); + pIndex = sqliteFindIndex(db, argv[1], db->aDb[iDb].zName); + if( pIndex==0 || pIndex->tnum!=0 ){ + /* This can occur if there exists an index on a TEMP table which + ** has the same name as another index on a permanent index. Since + ** the permanent table is hidden by the TEMP table, we can also + ** safely ignore the index on the permanent table. + */ + /* Do Nothing */; + }else{ + pIndex->tnum = atoi(argv[2]); + } + } + break; + } + default: { + /* This can not happen! */ + nErr = 1; + assert( nErr==0 ); + } + } + return nErr; +} + +/* +** This is a callback procedure used to reconstruct a table. The +** name of the table to be reconstructed is passed in as argv[0]. +** +** This routine is used to automatically upgrade a database from +** format version 1 or 2 to version 3. The correct operation of +** this routine relys on the fact that no indices are used when +** copying a table out to a temporary file. +** +** The change from version 2 to version 3 occurred between SQLite +** version 2.5.6 and 2.6.0 on 2002-July-18. +*/ +static +int upgrade_3_callback(void *pInit, int argc, char **argv, char **NotUsed){ + InitData *pData = (InitData*)pInit; + int rc; + Table *pTab; + Trigger *pTrig; + char *zErr = 0; + + pTab = sqliteFindTable(pData->db, argv[0], 0); + assert( pTab!=0 ); + assert( sqliteStrICmp(pTab->zName, argv[0])==0 ); + if( pTab ){ + pTrig = pTab->pTrigger; + pTab->pTrigger = 0; /* Disable all triggers before rebuilding the table */ + } + rc = sqlite_exec_printf(pData->db, + "CREATE TEMP TABLE sqlite_x AS SELECT * FROM '%q'; " + "DELETE FROM '%q'; " + "INSERT INTO '%q' SELECT * FROM sqlite_x; " + "DROP TABLE sqlite_x;", + 0, 0, &zErr, argv[0], argv[0], argv[0]); + if( zErr ){ + if( *pData->pzErrMsg ) sqlite_freemem(*pData->pzErrMsg); + *pData->pzErrMsg = zErr; + } + + /* If an error occurred in the SQL above, then the transaction will + ** rollback which will delete the internal symbol tables. This will + ** cause the structure that pTab points to be deleted. In case that + ** happened, we need to refetch pTab. + */ + pTab = sqliteFindTable(pData->db, argv[0], 0); + if( pTab ){ + assert( sqliteStrICmp(pTab->zName, argv[0])==0 ); + pTab->pTrigger = pTrig; /* Re-enable triggers */ + } + return rc!=SQLITE_OK; +} + + + +/* +** Attempt to read the database schema and initialize internal +** data structures for a single database file. The index of the +** database file is given by iDb. iDb==0 is used for the main +** database. iDb==1 should never be used. iDb>=2 is used for +** auxiliary databases. Return one of the SQLITE_ error codes to +** indicate success or failure. +*/ +static int sqliteInitOne(sqlite *db, int iDb, char **pzErrMsg){ + int rc; + BtCursor *curMain; + int size; + Table *pTab; + char const *azArg[6]; + char zDbNum[30]; + int meta[SQLITE_N_BTREE_META]; + InitData initData; + char const *zMasterSchema; + char const *zMasterName; + char *zSql = 0; + + /* + ** The master database table has a structure like this + */ + static char master_schema[] = + "CREATE TABLE sqlite_master(\n" + " type text,\n" + " name text,\n" + " tbl_name text,\n" + " rootpage integer,\n" + " sql text\n" + ")" + ; + static char temp_master_schema[] = + "CREATE TEMP TABLE sqlite_temp_master(\n" + " type text,\n" + " name text,\n" + " tbl_name text,\n" + " rootpage integer,\n" + " sql text\n" + ")" + ; + + assert( iDb>=0 && iDbnDb ); + + /* zMasterSchema and zInitScript are set to point at the master schema + ** and initialisation script appropriate for the database being + ** initialised. zMasterName is the name of the master table. + */ + if( iDb==1 ){ + zMasterSchema = temp_master_schema; + zMasterName = TEMP_MASTER_NAME; + }else{ + zMasterSchema = master_schema; + zMasterName = MASTER_NAME; + } + + /* Construct the schema table. + */ + sqliteSafetyOff(db); + azArg[0] = "table"; + azArg[1] = zMasterName; + azArg[2] = "2"; + azArg[3] = zMasterSchema; + sprintf(zDbNum, "%d", iDb); + azArg[4] = zDbNum; + azArg[5] = 0; + initData.db = db; + initData.pzErrMsg = pzErrMsg; + sqliteInitCallback(&initData, 5, (char **)azArg, 0); + pTab = sqliteFindTable(db, zMasterName, db->aDb[iDb].zName); + if( pTab ){ + pTab->readOnly = 1; + }else{ + return SQLITE_NOMEM; + } + sqliteSafetyOn(db); + + /* Create a cursor to hold the database open + */ + if( db->aDb[iDb].pBt==0 ) return SQLITE_OK; + rc = sqliteBtreeCursor(db->aDb[iDb].pBt, 2, 0, &curMain); + if( rc ){ + sqliteSetString(pzErrMsg, sqlite_error_string(rc), (char*)0); + return rc; + } + + /* Get the database meta information + */ + rc = sqliteBtreeGetMeta(db->aDb[iDb].pBt, meta); + if( rc ){ + sqliteSetString(pzErrMsg, sqlite_error_string(rc), (char*)0); + sqliteBtreeCloseCursor(curMain); + return rc; + } + db->aDb[iDb].schema_cookie = meta[1]; + if( iDb==0 ){ + db->next_cookie = meta[1]; + db->file_format = meta[2]; + size = meta[3]; + if( size==0 ){ size = MAX_PAGES; } + db->cache_size = size; + db->safety_level = meta[4]; + if( meta[6]>0 && meta[6]<=2 && db->temp_store==0 ){ + db->temp_store = meta[6]; + } + if( db->safety_level==0 ) db->safety_level = 2; + + /* + ** file_format==1 Version 2.1.0. + ** file_format==2 Version 2.2.0. Add support for INTEGER PRIMARY KEY. + ** file_format==3 Version 2.6.0. Fix empty-string index bug. + ** file_format==4 Version 2.7.0. Add support for separate numeric and + ** text datatypes. + */ + if( db->file_format==0 ){ + /* This happens if the database was initially empty */ + db->file_format = 4; + }else if( db->file_format>4 ){ + sqliteBtreeCloseCursor(curMain); + sqliteSetString(pzErrMsg, "unsupported file format", (char*)0); + return SQLITE_ERROR; + } + }else if( iDb!=1 && (db->file_format!=meta[2] || db->file_format<4) ){ + assert( db->file_format>=4 ); + if( meta[2]==0 ){ + sqliteSetString(pzErrMsg, "cannot attach empty database: ", + db->aDb[iDb].zName, (char*)0); + }else{ + sqliteSetString(pzErrMsg, "incompatible file format in auxiliary " + "database: ", db->aDb[iDb].zName, (char*)0); + } + sqliteBtreeClose(db->aDb[iDb].pBt); + db->aDb[iDb].pBt = 0; + return SQLITE_FORMAT; + } + sqliteBtreeSetCacheSize(db->aDb[iDb].pBt, db->cache_size); + sqliteBtreeSetSafetyLevel(db->aDb[iDb].pBt, meta[4]==0 ? 2 : meta[4]); + + /* Read the schema information out of the schema tables + */ + assert( db->init.busy ); + sqliteSafetyOff(db); + + /* The following SQL will read the schema from the master tables. + ** The first version works with SQLite file formats 2 or greater. + ** The second version is for format 1 files. + ** + ** Beginning with file format 2, the rowid for new table entries + ** (including entries in sqlite_master) is an increasing integer. + ** So for file format 2 and later, we can play back sqlite_master + ** and all the CREATE statements will appear in the right order. + ** But with file format 1, table entries were random and so we + ** have to make sure the CREATE TABLEs occur before their corresponding + ** CREATE INDEXs. (We don't have to deal with CREATE VIEW or + ** CREATE TRIGGER in file format 1 because those constructs did + ** not exist then.) + */ + if( db->file_format>=2 ){ + sqliteSetString(&zSql, + "SELECT type, name, rootpage, sql, ", zDbNum, " FROM \"", + db->aDb[iDb].zName, "\".", zMasterName, (char*)0); + }else{ + sqliteSetString(&zSql, + "SELECT type, name, rootpage, sql, ", zDbNum, " FROM \"", + db->aDb[iDb].zName, "\".", zMasterName, + " WHERE type IN ('table', 'index')" + " ORDER BY CASE type WHEN 'table' THEN 0 ELSE 1 END", (char*)0); + } + rc = sqlite_exec(db, zSql, sqliteInitCallback, &initData, 0); + + sqliteFree(zSql); + sqliteSafetyOn(db); + sqliteBtreeCloseCursor(curMain); + if( sqlite_malloc_failed ){ + sqliteSetString(pzErrMsg, "out of memory", (char*)0); + rc = SQLITE_NOMEM; + sqliteResetInternalSchema(db, 0); + } + if( rc==SQLITE_OK ){ + DbSetProperty(db, iDb, DB_SchemaLoaded); + }else{ + sqliteResetInternalSchema(db, iDb); + } + return rc; +} + +/* +** Initialize all database files - the main database file, the file +** used to store temporary tables, and any additional database files +** created using ATTACH statements. Return a success code. If an +** error occurs, write an error message into *pzErrMsg. +** +** After the database is initialized, the SQLITE_Initialized +** bit is set in the flags field of the sqlite structure. An +** attempt is made to initialize the database as soon as it +** is opened. If that fails (perhaps because another process +** has the sqlite_master table locked) than another attempt +** is made the first time the database is accessed. +*/ +int sqliteInit(sqlite *db, char **pzErrMsg){ + int i, rc; + + if( db->init.busy ) return SQLITE_OK; + assert( (db->flags & SQLITE_Initialized)==0 ); + rc = SQLITE_OK; + db->init.busy = 1; + for(i=0; rc==SQLITE_OK && inDb; i++){ + if( DbHasProperty(db, i, DB_SchemaLoaded) || i==1 ) continue; + rc = sqliteInitOne(db, i, pzErrMsg); + if( rc ){ + sqliteResetInternalSchema(db, i); + } + } + + /* Once all the other databases have been initialised, load the schema + ** for the TEMP database. This is loaded last, as the TEMP database + ** schema may contain references to objects in other databases. + */ + if( rc==SQLITE_OK && db->nDb>1 && !DbHasProperty(db, 1, DB_SchemaLoaded) ){ + rc = sqliteInitOne(db, 1, pzErrMsg); + if( rc ){ + sqliteResetInternalSchema(db, 1); + } + } + + db->init.busy = 0; + if( rc==SQLITE_OK ){ + db->flags |= SQLITE_Initialized; + sqliteCommitInternalChanges(db); + } + + /* If the database is in formats 1 or 2, then upgrade it to + ** version 3. This will reconstruct all indices. If the + ** upgrade fails for any reason (ex: out of disk space, database + ** is read only, interrupt received, etc.) then fail the init. + */ + if( rc==SQLITE_OK && db->file_format<3 ){ + char *zErr = 0; + InitData initData; + int meta[SQLITE_N_BTREE_META]; + + db->magic = SQLITE_MAGIC_OPEN; + initData.db = db; + initData.pzErrMsg = &zErr; + db->file_format = 3; + rc = sqlite_exec(db, + "BEGIN; SELECT name FROM sqlite_master WHERE type='table';", + upgrade_3_callback, + &initData, + &zErr); + if( rc==SQLITE_OK ){ + sqliteBtreeGetMeta(db->aDb[0].pBt, meta); + meta[2] = 4; + sqliteBtreeUpdateMeta(db->aDb[0].pBt, meta); + sqlite_exec(db, "COMMIT", 0, 0, 0); + } + if( rc!=SQLITE_OK ){ + sqliteSetString(pzErrMsg, + "unable to upgrade database to the version 2.6 format", + zErr ? ": " : 0, zErr, (char*)0); + } + sqlite_freemem(zErr); + } + + if( rc!=SQLITE_OK ){ + db->flags &= ~SQLITE_Initialized; + } + return rc; +} + +/* +** The version of the library +*/ +const char rcsid[] = "@(#) \044Id: SQLite version " SQLITE_VERSION " $"; +const char sqlite_version[] = SQLITE_VERSION; + +/* +** Does the library expect data to be encoded as UTF-8 or iso8859? The +** following global constant always lets us know. +*/ +#ifdef SQLITE_UTF8 +const char sqlite_encoding[] = "UTF-8"; +#else +const char sqlite_encoding[] = "iso8859"; +#endif + +/* +** Open a new SQLite database. Construct an "sqlite" structure to define +** the state of this database and return a pointer to that structure. +** +** An attempt is made to initialize the in-memory data structures that +** hold the database schema. But if this fails (because the schema file +** is locked) then that step is deferred until the first call to +** sqlite_exec(). +*/ +sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){ + sqlite *db; + int rc, i; + + /* Allocate the sqlite data structure */ + db = sqliteMalloc( sizeof(sqlite) ); + if( pzErrMsg ) *pzErrMsg = 0; + if( db==0 ) goto no_mem_on_open; + db->onError = OE_Default; + db->priorNewRowid = 0; + db->magic = SQLITE_MAGIC_BUSY; + db->nDb = 2; + db->aDb = db->aDbStatic; + /* db->flags |= SQLITE_ShortColNames; */ + sqliteHashInit(&db->aFunc, SQLITE_HASH_STRING, 1); + for(i=0; inDb; i++){ + sqliteHashInit(&db->aDb[i].tblHash, SQLITE_HASH_STRING, 0); + sqliteHashInit(&db->aDb[i].idxHash, SQLITE_HASH_STRING, 0); + sqliteHashInit(&db->aDb[i].trigHash, SQLITE_HASH_STRING, 0); + sqliteHashInit(&db->aDb[i].aFKey, SQLITE_HASH_STRING, 1); + } + + /* Open the backend database driver */ + if( zFilename[0]==':' && strcmp(zFilename,":memory:")==0 ){ + db->temp_store = 2; + } + rc = sqliteBtreeFactory(db, zFilename, 0, MAX_PAGES, &db->aDb[0].pBt); + if( rc!=SQLITE_OK ){ + switch( rc ){ + default: { + sqliteSetString(pzErrMsg, "unable to open database: ", + zFilename, (char*)0); + } + } + sqliteFree(db); + sqliteStrRealloc(pzErrMsg); + return 0; + } + db->aDb[0].zName = "main"; + db->aDb[1].zName = "temp"; + + /* Attempt to read the schema */ + sqliteRegisterBuiltinFunctions(db); + rc = sqliteInit(db, pzErrMsg); + db->magic = SQLITE_MAGIC_OPEN; + if( sqlite_malloc_failed ){ + sqlite_close(db); + goto no_mem_on_open; + }else if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){ + sqlite_close(db); + sqliteStrRealloc(pzErrMsg); + return 0; + }else if( pzErrMsg ){ + sqliteFree(*pzErrMsg); + *pzErrMsg = 0; + } + + /* Return a pointer to the newly opened database structure */ + return db; + +no_mem_on_open: + sqliteSetString(pzErrMsg, "out of memory", (char*)0); + sqliteStrRealloc(pzErrMsg); + return 0; +} + +/* +** Return the ROWID of the most recent insert +*/ +int sqlite_last_insert_rowid(sqlite *db){ + return db->lastRowid; +} + +/* +** Return the number of changes in the most recent call to sqlite_exec(). +*/ +int sqlite_changes(sqlite *db){ + return db->nChange; +} + +/* +** Return the number of changes produced by the last INSERT, UPDATE, or +** DELETE statement to complete execution. The count does not include +** changes due to SQL statements executed in trigger programs that were +** triggered by that statement +*/ +int sqlite_last_statement_changes(sqlite *db){ + return db->lsChange; +} + +/* +** Close an existing SQLite database +*/ +void sqlite_close(sqlite *db){ + HashElem *i; + int j; + db->want_to_close = 1; + if( sqliteSafetyCheck(db) || sqliteSafetyOn(db) ){ + /* printf("DID NOT CLOSE\n"); fflush(stdout); */ + return; + } + db->magic = SQLITE_MAGIC_CLOSED; + for(j=0; jnDb; j++){ + struct Db *pDb = &db->aDb[j]; + if( pDb->pBt ){ + sqliteBtreeClose(pDb->pBt); + pDb->pBt = 0; + } + } + sqliteResetInternalSchema(db, 0); + assert( db->nDb<=2 ); + assert( db->aDb==db->aDbStatic ); + for(i=sqliteHashFirst(&db->aFunc); i; i=sqliteHashNext(i)){ + FuncDef *pFunc, *pNext; + for(pFunc = (FuncDef*)sqliteHashData(i); pFunc; pFunc=pNext){ + pNext = pFunc->pNext; + sqliteFree(pFunc); + } + } + sqliteHashClear(&db->aFunc); + sqliteFree(db); +} + +/* +** Rollback all database files. +*/ +void sqliteRollbackAll(sqlite *db){ + int i; + for(i=0; inDb; i++){ + if( db->aDb[i].pBt ){ + sqliteBtreeRollback(db->aDb[i].pBt); + db->aDb[i].inTrans = 0; + } + } + sqliteResetInternalSchema(db, 0); + /* sqliteRollbackInternalChanges(db); */ +} + +/* +** Execute SQL code. Return one of the SQLITE_ success/failure +** codes. Also write an error message into memory obtained from +** malloc() and make *pzErrMsg point to that message. +** +** If the SQL is a query, then for each row in the query result +** the xCallback() function is called. pArg becomes the first +** argument to xCallback(). If xCallback=NULL then no callback +** is invoked, even for queries. +*/ +int sqlite_exec( + sqlite *db, /* The database on which the SQL executes */ + const char *zSql, /* The SQL to be executed */ + sqlite_callback xCallback, /* Invoke this callback routine */ + void *pArg, /* First argument to xCallback() */ + char **pzErrMsg /* Write error messages here */ +){ + int rc = SQLITE_OK; + const char *zLeftover; + sqlite_vm *pVm; + int nRetry = 0; + int nChange = 0; + int nCallback; + + if( zSql==0 ) return SQLITE_OK; + while( rc==SQLITE_OK && zSql[0] ){ + pVm = 0; + rc = sqlite_compile(db, zSql, &zLeftover, &pVm, pzErrMsg); + if( rc!=SQLITE_OK ){ + assert( pVm==0 || sqlite_malloc_failed ); + return rc; + } + if( pVm==0 ){ + /* This happens if the zSql input contained only whitespace */ + break; + } + db->nChange += nChange; + nCallback = 0; + while(1){ + int nArg; + char **azArg, **azCol; + rc = sqlite_step(pVm, &nArg, (const char***)&azArg,(const char***)&azCol); + if( rc==SQLITE_ROW ){ + if( xCallback!=0 && xCallback(pArg, nArg, azArg, azCol) ){ + sqlite_finalize(pVm, 0); + return SQLITE_ABORT; + } + nCallback++; + }else{ + if( rc==SQLITE_DONE && nCallback==0 + && (db->flags & SQLITE_NullCallback)!=0 && xCallback!=0 ){ + xCallback(pArg, nArg, azArg, azCol); + } + rc = sqlite_finalize(pVm, pzErrMsg); + if( rc==SQLITE_SCHEMA && nRetry<2 ){ + nRetry++; + rc = SQLITE_OK; + break; + } + if( db->pVdbe==0 ){ + nChange = db->nChange; + } + nRetry = 0; + zSql = zLeftover; + while( isspace(zSql[0]) ) zSql++; + break; + } + } + } + return rc; +} + + +/* +** Compile a single statement of SQL into a virtual machine. Return one +** of the SQLITE_ success/failure codes. Also write an error message into +** memory obtained from malloc() and make *pzErrMsg point to that message. +*/ +int sqlite_compile( + sqlite *db, /* The database on which the SQL executes */ + const char *zSql, /* The SQL to be executed */ + const char **pzTail, /* OUT: Next statement after the first */ + sqlite_vm **ppVm, /* OUT: The virtual machine */ + char **pzErrMsg /* OUT: Write error messages here */ +){ + Parse sParse; + + if( pzErrMsg ) *pzErrMsg = 0; + if( sqliteSafetyOn(db) ) goto exec_misuse; + if( !db->init.busy ){ + if( (db->flags & SQLITE_Initialized)==0 ){ + int rc, cnt = 1; + while( (rc = sqliteInit(db, pzErrMsg))==SQLITE_BUSY + && db->xBusyCallback + && db->xBusyCallback(db->pBusyArg, "", cnt++)!=0 ){} + if( rc!=SQLITE_OK ){ + sqliteStrRealloc(pzErrMsg); + sqliteSafetyOff(db); + return rc; + } + if( pzErrMsg ){ + sqliteFree(*pzErrMsg); + *pzErrMsg = 0; + } + } + if( db->file_format<3 ){ + sqliteSafetyOff(db); + sqliteSetString(pzErrMsg, "obsolete database file format", (char*)0); + return SQLITE_ERROR; + } + } + assert( (db->flags & SQLITE_Initialized)!=0 || db->init.busy ); + if( db->pVdbe==0 ){ db->nChange = 0; } + memset(&sParse, 0, sizeof(sParse)); + sParse.db = db; + sqliteRunParser(&sParse, zSql, pzErrMsg); + if( db->xTrace && !db->init.busy ){ + /* Trace only the statment that was compiled. + ** Make a copy of that part of the SQL string since zSQL is const + ** and we must pass a zero terminated string to the trace function + ** The copy is unnecessary if the tail pointer is pointing at the + ** beginnig or end of the SQL string. + */ + if( sParse.zTail && sParse.zTail!=zSql && *sParse.zTail ){ + char *tmpSql = sqliteStrNDup(zSql, sParse.zTail - zSql); + if( tmpSql ){ + db->xTrace(db->pTraceArg, tmpSql); + free(tmpSql); + }else{ + /* If a memory error occurred during the copy, + ** trace entire SQL string and fall through to the + ** sqlite_malloc_failed test to report the error. + */ + db->xTrace(db->pTraceArg, zSql); + } + }else{ + db->xTrace(db->pTraceArg, zSql); + } + } + if( sqlite_malloc_failed ){ + sqliteSetString(pzErrMsg, "out of memory", (char*)0); + sParse.rc = SQLITE_NOMEM; + sqliteRollbackAll(db); + sqliteResetInternalSchema(db, 0); + db->flags &= ~SQLITE_InTrans; + } + if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK; + if( sParse.rc!=SQLITE_OK && pzErrMsg && *pzErrMsg==0 ){ + sqliteSetString(pzErrMsg, sqlite_error_string(sParse.rc), (char*)0); + } + sqliteStrRealloc(pzErrMsg); + if( sParse.rc==SQLITE_SCHEMA ){ + sqliteResetInternalSchema(db, 0); + } + assert( ppVm ); + *ppVm = (sqlite_vm*)sParse.pVdbe; + if( pzTail ) *pzTail = sParse.zTail; + if( sqliteSafetyOff(db) ) goto exec_misuse; + return sParse.rc; + +exec_misuse: + if( pzErrMsg ){ + *pzErrMsg = 0; + sqliteSetString(pzErrMsg, sqlite_error_string(SQLITE_MISUSE), (char*)0); + sqliteStrRealloc(pzErrMsg); + } + return SQLITE_MISUSE; +} + + +/* +** The following routine destroys a virtual machine that is created by +** the sqlite_compile() routine. +** +** The integer returned is an SQLITE_ success/failure code that describes +** the result of executing the virtual machine. An error message is +** written into memory obtained from malloc and *pzErrMsg is made to +** point to that error if pzErrMsg is not NULL. The calling routine +** should use sqlite_freemem() to delete the message when it has finished +** with it. +*/ +int sqlite_finalize( + sqlite_vm *pVm, /* The virtual machine to be destroyed */ + char **pzErrMsg /* OUT: Write error messages here */ +){ + int rc = sqliteVdbeFinalize((Vdbe*)pVm, pzErrMsg); + sqliteStrRealloc(pzErrMsg); + return rc; +} + +/* +** Terminate the current execution of a virtual machine then +** reset the virtual machine back to its starting state so that it +** can be reused. Any error message resulting from the prior execution +** is written into *pzErrMsg. A success code from the prior execution +** is returned. +*/ +int sqlite_reset( + sqlite_vm *pVm, /* The virtual machine to be destroyed */ + char **pzErrMsg /* OUT: Write error messages here */ +){ + int rc = sqliteVdbeReset((Vdbe*)pVm, pzErrMsg); + sqliteVdbeMakeReady((Vdbe*)pVm, -1, 0); + sqliteStrRealloc(pzErrMsg); + return rc; +} + +/* +** Return a static string that describes the kind of error specified in the +** argument. +*/ +const char *sqlite_error_string(int rc){ + const char *z; + switch( rc ){ + case SQLITE_OK: z = "not an error"; break; + case SQLITE_ERROR: z = "SQL logic error or missing database"; break; + case SQLITE_INTERNAL: z = "internal SQLite implementation flaw"; break; + case SQLITE_PERM: z = "access permission denied"; break; + case SQLITE_ABORT: z = "callback requested query abort"; break; + case SQLITE_BUSY: z = "database is locked"; break; + case SQLITE_LOCKED: z = "database table is locked"; break; + case SQLITE_NOMEM: z = "out of memory"; break; + case SQLITE_READONLY: z = "attempt to write a readonly database"; break; + case SQLITE_INTERRUPT: z = "interrupted"; break; + case SQLITE_IOERR: z = "disk I/O error"; break; + case SQLITE_CORRUPT: z = "database disk image is malformed"; break; + case SQLITE_NOTFOUND: z = "table or record not found"; break; + case SQLITE_FULL: z = "database is full"; break; + case SQLITE_CANTOPEN: z = "unable to open database file"; break; + case SQLITE_PROTOCOL: z = "database locking protocol failure"; break; + case SQLITE_EMPTY: z = "table contains no data"; break; + case SQLITE_SCHEMA: z = "database schema has changed"; break; + case SQLITE_TOOBIG: z = "too much data for one table row"; break; + case SQLITE_CONSTRAINT: z = "constraint failed"; break; + case SQLITE_MISMATCH: z = "datatype mismatch"; break; + case SQLITE_MISUSE: z = "library routine called out of sequence";break; + case SQLITE_NOLFS: z = "kernel lacks large file support"; break; + case SQLITE_AUTH: z = "authorization denied"; break; + case SQLITE_FORMAT: z = "auxiliary database format error"; break; + case SQLITE_RANGE: z = "bind index out of range"; break; + case SQLITE_NOTADB: z = "file is encrypted or is not a database";break; + default: z = "unknown error"; break; + } + return z; +} + +/* +** This routine implements a busy callback that sleeps and tries +** again until a timeout value is reached. The timeout value is +** an integer number of milliseconds passed in as the first +** argument. +*/ +static int sqliteDefaultBusyCallback( + void *Timeout, /* Maximum amount of time to wait */ + const char *NotUsed, /* The name of the table that is busy */ + int count /* Number of times table has been busy */ +){ +#if SQLITE_MIN_SLEEP_MS==1 + static const char delays[] = + { 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 50, 100}; + static const short int totals[] = + { 0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228, 287}; +# define NDELAY (sizeof(delays)/sizeof(delays[0])) + int timeout = (int)(long)Timeout; + int delay, prior; + + if( count <= NDELAY ){ + delay = delays[count-1]; + prior = totals[count-1]; + }else{ + delay = delays[NDELAY-1]; + prior = totals[NDELAY-1] + delay*(count-NDELAY-1); + } + if( prior + delay > timeout ){ + delay = timeout - prior; + if( delay<=0 ) return 0; + } + sqliteOsSleep(delay); + return 1; +#else + int timeout = (int)(long)Timeout; + if( (count+1)*1000 > timeout ){ + return 0; + } + sqliteOsSleep(1000); + return 1; +#endif +} + +/* +** This routine sets the busy callback for an Sqlite database to the +** given callback function with the given argument. +*/ +void sqlite_busy_handler( + sqlite *db, + int (*xBusy)(void*,const char*,int), + void *pArg +){ + db->xBusyCallback = xBusy; + db->pBusyArg = pArg; +} + +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK +/* +** This routine sets the progress callback for an Sqlite database to the +** given callback function with the given argument. The progress callback will +** be invoked every nOps opcodes. +*/ +void sqlite_progress_handler( + sqlite *db, + int nOps, + int (*xProgress)(void*), + void *pArg +){ + if( nOps>0 ){ + db->xProgress = xProgress; + db->nProgressOps = nOps; + db->pProgressArg = pArg; + }else{ + db->xProgress = 0; + db->nProgressOps = 0; + db->pProgressArg = 0; + } +} +#endif + + +/* +** This routine installs a default busy handler that waits for the +** specified number of milliseconds before returning 0. +*/ +void sqlite_busy_timeout(sqlite *db, int ms){ + if( ms>0 ){ + sqlite_busy_handler(db, sqliteDefaultBusyCallback, (void*)(long)ms); + }else{ + sqlite_busy_handler(db, 0, 0); + } +} + +/* +** Cause any pending operation to stop at its earliest opportunity. +*/ +void sqlite_interrupt(sqlite *db){ + db->flags |= SQLITE_Interrupt; +} + +/* +** Windows systems should call this routine to free memory that +** is returned in the in the errmsg parameter of sqlite_open() when +** SQLite is a DLL. For some reason, it does not work to call free() +** directly. +** +** Note that we need to call free() not sqliteFree() here, since every +** string that is exported from SQLite should have already passed through +** sqliteStrRealloc(). +*/ +void sqlite_freemem(void *p){ free(p); } + +/* +** Windows systems need functions to call to return the sqlite_version +** and sqlite_encoding strings since they are unable to access constants +** within DLLs. +*/ +const char *sqlite_libversion(void){ return sqlite_version; } +const char *sqlite_libencoding(void){ return sqlite_encoding; } + +/* +** Create new user-defined functions. The sqlite_create_function() +** routine creates a regular function and sqlite_create_aggregate() +** creates an aggregate function. +** +** Passing a NULL xFunc argument or NULL xStep and xFinalize arguments +** disables the function. Calling sqlite_create_function() with the +** same name and number of arguments as a prior call to +** sqlite_create_aggregate() disables the prior call to +** sqlite_create_aggregate(), and vice versa. +** +** If nArg is -1 it means that this function will accept any number +** of arguments, including 0. The maximum allowed value of nArg is 127. +*/ +int sqlite_create_function( + sqlite *db, /* Add the function to this database connection */ + const char *zName, /* Name of the function to add */ + int nArg, /* Number of arguments */ + void (*xFunc)(sqlite_func*,int,const char**), /* The implementation */ + void *pUserData /* User data */ +){ + FuncDef *p; + int nName; + if( db==0 || zName==0 || sqliteSafetyCheck(db) ) return 1; + if( nArg<-1 || nArg>127 ) return 1; + nName = strlen(zName); + if( nName>255 ) return 1; + p = sqliteFindFunction(db, zName, nName, nArg, 1); + if( p==0 ) return 1; + p->xFunc = xFunc; + p->xStep = 0; + p->xFinalize = 0; + p->pUserData = pUserData; + return 0; +} +int sqlite_create_aggregate( + sqlite *db, /* Add the function to this database connection */ + const char *zName, /* Name of the function to add */ + int nArg, /* Number of arguments */ + void (*xStep)(sqlite_func*,int,const char**), /* The step function */ + void (*xFinalize)(sqlite_func*), /* The finalizer */ + void *pUserData /* User data */ +){ + FuncDef *p; + int nName; + if( db==0 || zName==0 || sqliteSafetyCheck(db) ) return 1; + if( nArg<-1 || nArg>127 ) return 1; + nName = strlen(zName); + if( nName>255 ) return 1; + p = sqliteFindFunction(db, zName, nName, nArg, 1); + if( p==0 ) return 1; + p->xFunc = 0; + p->xStep = xStep; + p->xFinalize = xFinalize; + p->pUserData = pUserData; + return 0; +} + +/* +** Change the datatype for all functions with a given name. See the +** header comment for the prototype of this function in sqlite.h for +** additional information. +*/ +int sqlite_function_type(sqlite *db, const char *zName, int dataType){ + FuncDef *p = (FuncDef*)sqliteHashFind(&db->aFunc, zName, strlen(zName)); + while( p ){ + p->dataType = dataType; + p = p->pNext; + } + return SQLITE_OK; +} + +/* +** Register a trace function. The pArg from the previously registered trace +** is returned. +** +** A NULL trace function means that no tracing is executes. A non-NULL +** trace is a pointer to a function that is invoked at the start of each +** sqlite_exec(). +*/ +void *sqlite_trace(sqlite *db, void (*xTrace)(void*,const char*), void *pArg){ + void *pOld = db->pTraceArg; + db->xTrace = xTrace; + db->pTraceArg = pArg; + return pOld; +} + +/*** EXPERIMENTAL *** +** +** Register a function to be invoked when a transaction comments. +** If either function returns non-zero, then the commit becomes a +** rollback. +*/ +void *sqlite_commit_hook( + sqlite *db, /* Attach the hook to this database */ + int (*xCallback)(void*), /* Function to invoke on each commit */ + void *pArg /* Argument to the function */ +){ + void *pOld = db->pCommitArg; + db->xCommitCallback = xCallback; + db->pCommitArg = pArg; + return pOld; +} + + +/* +** This routine is called to create a connection to a database BTree +** driver. If zFilename is the name of a file, then that file is +** opened and used. If zFilename is the magic name ":memory:" then +** the database is stored in memory (and is thus forgotten as soon as +** the connection is closed.) If zFilename is NULL then the database +** is for temporary use only and is deleted as soon as the connection +** is closed. +** +** A temporary database can be either a disk file (that is automatically +** deleted when the file is closed) or a set of red-black trees held in memory, +** depending on the values of the TEMP_STORE compile-time macro and the +** db->temp_store variable, according to the following chart: +** +** TEMP_STORE db->temp_store Location of temporary database +** ---------- -------------- ------------------------------ +** 0 any file +** 1 1 file +** 1 2 memory +** 1 0 file +** 2 1 file +** 2 2 memory +** 2 0 memory +** 3 any memory +*/ +int sqliteBtreeFactory( + const sqlite *db, /* Main database when opening aux otherwise 0 */ + const char *zFilename, /* Name of the file containing the BTree database */ + int omitJournal, /* if TRUE then do not journal this file */ + int nCache, /* How many pages in the page cache */ + Btree **ppBtree){ /* Pointer to new Btree object written here */ + + assert( ppBtree != 0); + +#ifndef SQLITE_OMIT_INMEMORYDB + if( zFilename==0 ){ + if (TEMP_STORE == 0) { + /* Always use file based temporary DB */ + return sqliteBtreeOpen(0, omitJournal, nCache, ppBtree); + } else if (TEMP_STORE == 1 || TEMP_STORE == 2) { + /* Switch depending on compile-time and/or runtime settings. */ + int location = db->temp_store==0 ? TEMP_STORE : db->temp_store; + + if (location == 1) { + return sqliteBtreeOpen(zFilename, omitJournal, nCache, ppBtree); + } else { + return sqliteRbtreeOpen(0, 0, 0, ppBtree); + } + } else { + /* Always use in-core DB */ + return sqliteRbtreeOpen(0, 0, 0, ppBtree); + } + }else if( zFilename[0]==':' && strcmp(zFilename,":memory:")==0 ){ + return sqliteRbtreeOpen(0, 0, 0, ppBtree); + }else +#endif + { + return sqliteBtreeOpen(zFilename, omitJournal, nCache, ppBtree); + } +} diff --git a/src/libs/sqlite2/opcodes.c b/src/libs/sqlite2/opcodes.c new file mode 100644 index 00000000..0907e0e7 --- /dev/null +++ b/src/libs/sqlite2/opcodes.c @@ -0,0 +1,140 @@ +/* Automatically generated file. Do not edit */ +char *sqliteOpcodeNames[] = { "???", + "Goto", + "Gosub", + "Return", + "Halt", + "Integer", + "String", + "Variable", + "Pop", + "Dup", + "Pull", + "Push", + "ColumnName", + "Callback", + "Concat", + "Add", + "Subtract", + "Multiply", + "Divide", + "Remainder", + "Function", + "BitAnd", + "BitOr", + "ShiftLeft", + "ShiftRight", + "AddImm", + "ForceInt", + "MustBeInt", + "Eq", + "Ne", + "Lt", + "Le", + "Gt", + "Ge", + "StrEq", + "StrNe", + "StrLt", + "StrLe", + "StrGt", + "StrGe", + "And", + "Or", + "Negative", + "AbsValue", + "Not", + "BitNot", + "Noop", + "If", + "IfNot", + "IsNull", + "NotNull", + "MakeRecord", + "MakeIdxKey", + "MakeKey", + "IncrKey", + "Checkpoint", + "Transaction", + "Commit", + "Rollback", + "ReadCookie", + "SetCookie", + "VerifyCookie", + "OpenRead", + "OpenWrite", + "OpenTemp", + "OpenPseudo", + "Close", + "MoveLt", + "MoveTo", + "Distinct", + "NotFound", + "Found", + "IsUnique", + "NotExists", + "NewRecno", + "PutIntKey", + "PutStrKey", + "Delete", + "SetCounts", + "KeyAsData", + "RowKey", + "RowData", + "Column", + "Recno", + "FullKey", + "NullRow", + "Last", + "Rewind", + "Prev", + "Next", + "IdxPut", + "IdxDelete", + "IdxRecno", + "IdxLT", + "IdxGT", + "IdxGE", + "IdxIsNull", + "Destroy", + "Clear", + "CreateIndex", + "CreateTable", + "IntegrityCk", + "ListWrite", + "ListRewind", + "ListRead", + "ListReset", + "ListPush", + "ListPop", + "ContextPush", + "ContextPop", + "SortPut", + "SortMakeRec", + "SortMakeKey", + "Sort", + "SortNext", + "SortCallback", + "SortReset", + "FileOpen", + "FileRead", + "FileColumn", + "MemStore", + "MemLoad", + "MemIncr", + "AggReset", + "AggInit", + "AggFunc", + "AggFocus", + "AggSet", + "AggGet", + "AggNext", + "SetInsert", + "SetFound", + "SetNotFound", + "SetFirst", + "SetNext", + "Vacuum", + "StackDepth", + "StackReset", +}; diff --git a/src/libs/sqlite2/opcodes.h b/src/libs/sqlite2/opcodes.h new file mode 100644 index 00000000..35e05069 --- /dev/null +++ b/src/libs/sqlite2/opcodes.h @@ -0,0 +1,138 @@ +/* Automatically generated file. Do not edit */ +#define OP_Goto 1 +#define OP_Gosub 2 +#define OP_Return 3 +#define OP_Halt 4 +#define OP_Integer 5 +#define OP_String 6 +#define OP_Variable 7 +#define OP_Pop 8 +#define OP_Dup 9 +#define OP_Pull 10 +#define OP_Push 11 +#define OP_ColumnName 12 +#define OP_Callback 13 +#define OP_Concat 14 +#define OP_Add 15 +#define OP_Subtract 16 +#define OP_Multiply 17 +#define OP_Divide 18 +#define OP_Remainder 19 +#define OP_Function 20 +#define OP_BitAnd 21 +#define OP_BitOr 22 +#define OP_ShiftLeft 23 +#define OP_ShiftRight 24 +#define OP_AddImm 25 +#define OP_ForceInt 26 +#define OP_MustBeInt 27 +#define OP_Eq 28 +#define OP_Ne 29 +#define OP_Lt 30 +#define OP_Le 31 +#define OP_Gt 32 +#define OP_Ge 33 +#define OP_StrEq 34 +#define OP_StrNe 35 +#define OP_StrLt 36 +#define OP_StrLe 37 +#define OP_StrGt 38 +#define OP_StrGe 39 +#define OP_And 40 +#define OP_Or 41 +#define OP_Negative 42 +#define OP_AbsValue 43 +#define OP_Not 44 +#define OP_BitNot 45 +#define OP_Noop 46 +#define OP_If 47 +#define OP_IfNot 48 +#define OP_IsNull 49 +#define OP_NotNull 50 +#define OP_MakeRecord 51 +#define OP_MakeIdxKey 52 +#define OP_MakeKey 53 +#define OP_IncrKey 54 +#define OP_Checkpoint 55 +#define OP_Transaction 56 +#define OP_Commit 57 +#define OP_Rollback 58 +#define OP_ReadCookie 59 +#define OP_SetCookie 60 +#define OP_VerifyCookie 61 +#define OP_OpenRead 62 +#define OP_OpenWrite 63 +#define OP_OpenTemp 64 +#define OP_OpenPseudo 65 +#define OP_Close 66 +#define OP_MoveLt 67 +#define OP_MoveTo 68 +#define OP_Distinct 69 +#define OP_NotFound 70 +#define OP_Found 71 +#define OP_IsUnique 72 +#define OP_NotExists 73 +#define OP_NewRecno 74 +#define OP_PutIntKey 75 +#define OP_PutStrKey 76 +#define OP_Delete 77 +#define OP_SetCounts 78 +#define OP_KeyAsData 79 +#define OP_RowKey 80 +#define OP_RowData 81 +#define OP_Column 82 +#define OP_Recno 83 +#define OP_FullKey 84 +#define OP_NullRow 85 +#define OP_Last 86 +#define OP_Rewind 87 +#define OP_Prev 88 +#define OP_Next 89 +#define OP_IdxPut 90 +#define OP_IdxDelete 91 +#define OP_IdxRecno 92 +#define OP_IdxLT 93 +#define OP_IdxGT 94 +#define OP_IdxGE 95 +#define OP_IdxIsNull 96 +#define OP_Destroy 97 +#define OP_Clear 98 +#define OP_CreateIndex 99 +#define OP_CreateTable 100 +#define OP_IntegrityCk 101 +#define OP_ListWrite 102 +#define OP_ListRewind 103 +#define OP_ListRead 104 +#define OP_ListReset 105 +#define OP_ListPush 106 +#define OP_ListPop 107 +#define OP_ContextPush 108 +#define OP_ContextPop 109 +#define OP_SortPut 110 +#define OP_SortMakeRec 111 +#define OP_SortMakeKey 112 +#define OP_Sort 113 +#define OP_SortNext 114 +#define OP_SortCallback 115 +#define OP_SortReset 116 +#define OP_FileOpen 117 +#define OP_FileRead 118 +#define OP_FileColumn 119 +#define OP_MemStore 120 +#define OP_MemLoad 121 +#define OP_MemIncr 122 +#define OP_AggReset 123 +#define OP_AggInit 124 +#define OP_AggFunc 125 +#define OP_AggFocus 126 +#define OP_AggSet 127 +#define OP_AggGet 128 +#define OP_AggNext 129 +#define OP_SetInsert 130 +#define OP_SetFound 131 +#define OP_SetNotFound 132 +#define OP_SetFirst 133 +#define OP_SetNext 134 +#define OP_Vacuum 135 +#define OP_StackDepth 136 +#define OP_StackReset 137 diff --git a/src/libs/sqlite2/os.c b/src/libs/sqlite2/os.c new file mode 100644 index 00000000..dccd65f1 --- /dev/null +++ b/src/libs/sqlite2/os.c @@ -0,0 +1,1850 @@ +/* +** 2001 September 16 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code that is specific to particular operating +** systems. The purpose of this file is to provide a uniform abstraction +** on which the rest of SQLite can operate. +*/ +#include "os.h" /* Must be first to enable large file support */ +#include "sqliteInt.h" + +#if OS_UNIX +# include +# include +# include +# ifndef O_LARGEFILE +# define O_LARGEFILE 0 +# endif +# ifdef SQLITE_DISABLE_LFS +# undef O_LARGEFILE +# define O_LARGEFILE 0 +# endif +# ifndef O_NOFOLLOW +# define O_NOFOLLOW 0 +# endif +# ifndef O_BINARY +# define O_BINARY 0 +# endif +#endif + + +#if OS_WIN +# include +#endif + +#if OS_MAC +# include +# include +# include +# include +# include +# include +# include +#endif + +/* +** The DJGPP compiler environment looks mostly like Unix, but it +** lacks the fcntl() system call. So redefine fcntl() to be something +** that always succeeds. This means that locking does not occur under +** DJGPP. But its DOS - what did you expect? +*/ +#ifdef __DJGPP__ +# define fcntl(A,B,C) 0 +#endif + +/* +** Macros used to determine whether or not to use threads. The +** SQLITE_UNIX_THREADS macro is defined if we are synchronizing for +** Posix threads and SQLITE_W32_THREADS is defined if we are +** synchronizing using Win32 threads. +*/ +#if OS_UNIX && defined(THREADSAFE) && THREADSAFE +# include +# define SQLITE_UNIX_THREADS 1 +#endif +#if OS_WIN && defined(THREADSAFE) && THREADSAFE +# define SQLITE_W32_THREADS 1 +#endif +#if OS_MAC && defined(THREADSAFE) && THREADSAFE +# include +# define SQLITE_MACOS_MULTITASKING 1 +#endif + +/* +** Macros for performance tracing. Normally turned off +*/ +#if 0 +static int last_page = 0; +__inline__ unsigned long long int hwtime(void){ + unsigned long long int x; + __asm__("rdtsc\n\t" + "mov %%edx, %%ecx\n\t" + :"=A" (x)); + return x; +} +static unsigned long long int g_start; +static unsigned int elapse; +#define TIMER_START g_start=hwtime() +#define TIMER_END elapse=hwtime()-g_start +#define SEEK(X) last_page=(X) +#define TRACE1(X) fprintf(stderr,X) +#define TRACE2(X,Y) fprintf(stderr,X,Y) +#define TRACE3(X,Y,Z) fprintf(stderr,X,Y,Z) +#define TRACE4(X,Y,Z,A) fprintf(stderr,X,Y,Z,A) +#define TRACE5(X,Y,Z,A,B) fprintf(stderr,X,Y,Z,A,B) +#else +#define TIMER_START +#define TIMER_END +#define SEEK(X) +#define TRACE1(X) +#define TRACE2(X,Y) +#define TRACE3(X,Y,Z) +#define TRACE4(X,Y,Z,A) +#define TRACE5(X,Y,Z,A,B) +#endif + + +#if OS_UNIX +/* +** Here is the dirt on POSIX advisory locks: ANSI STD 1003.1 (1996) +** section 6.5.2.2 lines 483 through 490 specify that when a process +** sets or clears a lock, that operation overrides any prior locks set +** by the same process. It does not explicitly say so, but this implies +** that it overrides locks set by the same process using a different +** file descriptor. Consider this test case: +** +** int fd1 = open("./file1", O_RDWR|O_CREAT, 0644); +** int fd2 = open("./file2", O_RDWR|O_CREAT, 0644); +** +** Suppose ./file1 and ./file2 are really the same file (because +** one is a hard or symbolic link to the other) then if you set +** an exclusive lock on fd1, then try to get an exclusive lock +** on fd2, it works. I would have expected the second lock to +** fail since there was already a lock on the file due to fd1. +** But not so. Since both locks came from the same process, the +** second overrides the first, even though they were on different +** file descriptors opened on different file names. +** +** Bummer. If you ask me, this is broken. Badly broken. It means +** that we cannot use POSIX locks to synchronize file access among +** competing threads of the same process. POSIX locks will work fine +** to synchronize access for threads in separate processes, but not +** threads within the same process. +** +** To work around the problem, SQLite has to manage file locks internally +** on its own. Whenever a new database is opened, we have to find the +** specific inode of the database file (the inode is determined by the +** st_dev and st_ino fields of the stat structure that fstat() fills in) +** and check for locks already existing on that inode. When locks are +** created or removed, we have to look at our own internal record of the +** locks to see if another thread has previously set a lock on that same +** inode. +** +** The OsFile structure for POSIX is no longer just an integer file +** descriptor. It is now a structure that holds the integer file +** descriptor and a pointer to a structure that describes the internal +** locks on the corresponding inode. There is one locking structure +** per inode, so if the same inode is opened twice, both OsFile structures +** point to the same locking structure. The locking structure keeps +** a reference count (so we will know when to delete it) and a "cnt" +** field that tells us its internal lock status. cnt==0 means the +** file is unlocked. cnt==-1 means the file has an exclusive lock. +** cnt>0 means there are cnt shared locks on the file. +** +** Any attempt to lock or unlock a file first checks the locking +** structure. The fcntl() system call is only invoked to set a +** POSIX lock if the internal lock structure transitions between +** a locked and an unlocked state. +** +** 2004-Jan-11: +** More recent discoveries about POSIX advisory locks. (The more +** I discover, the more I realize the a POSIX advisory locks are +** an abomination.) +** +** If you close a file descriptor that points to a file that has locks, +** all locks on that file that are owned by the current process are +** released. To work around this problem, each OsFile structure contains +** a pointer to an openCnt structure. There is one openCnt structure +** per open inode, which means that multiple OsFiles can point to a single +** openCnt. When an attempt is made to close an OsFile, if there are +** other OsFiles open on the same inode that are holding locks, the call +** to close() the file descriptor is deferred until all of the locks clear. +** The openCnt structure keeps a list of file descriptors that need to +** be closed and that list is walked (and cleared) when the last lock +** clears. +** +** First, under Linux threads, because each thread has a separate +** process ID, lock operations in one thread do not override locks +** to the same file in other threads. Linux threads behave like +** separate processes in this respect. But, if you close a file +** descriptor in linux threads, all locks are cleared, even locks +** on other threads and even though the other threads have different +** process IDs. Linux threads is inconsistent in this respect. +** (I'm beginning to think that linux threads is an abomination too.) +** The consequence of this all is that the hash table for the lockInfo +** structure has to include the process id as part of its key because +** locks in different threads are treated as distinct. But the +** openCnt structure should not include the process id in its +** key because close() clears lock on all threads, not just the current +** thread. Were it not for this goofiness in linux threads, we could +** combine the lockInfo and openCnt structures into a single structure. +*/ + +/* +** An instance of the following structure serves as the key used +** to locate a particular lockInfo structure given its inode. Note +** that we have to include the process ID as part of the key. On some +** threading implementations (ex: linux), each thread has a separate +** process ID. +*/ +struct lockKey { + dev_t dev; /* Device number */ + ino_t ino; /* Inode number */ + pid_t pid; /* Process ID */ +}; + +/* +** An instance of the following structure is allocated for each open +** inode on each thread with a different process ID. (Threads have +** different process IDs on linux, but not on most other unixes.) +** +** A single inode can have multiple file descriptors, so each OsFile +** structure contains a pointer to an instance of this object and this +** object keeps a count of the number of OsFiles pointing to it. +*/ +struct lockInfo { + struct lockKey key; /* The lookup key */ + int cnt; /* 0: unlocked. -1: write lock. 1...: read lock. */ + int nRef; /* Number of pointers to this structure */ +}; + +/* +** An instance of the following structure serves as the key used +** to locate a particular openCnt structure given its inode. This +** is the same as the lockKey except that the process ID is omitted. +*/ +struct openKey { + dev_t dev; /* Device number */ + ino_t ino; /* Inode number */ +}; + +/* +** An instance of the following structure is allocated for each open +** inode. This structure keeps track of the number of locks on that +** inode. If a close is attempted against an inode that is holding +** locks, the close is deferred until all locks clear by adding the +** file descriptor to be closed to the pending list. +*/ +struct openCnt { + struct openKey key; /* The lookup key */ + int nRef; /* Number of pointers to this structure */ + int nLock; /* Number of outstanding locks */ + int nPending; /* Number of pending close() operations */ + int *aPending; /* Malloced space holding fd's awaiting a close() */ +}; + +/* +** These hash table maps inodes and process IDs into lockInfo and openCnt +** structures. Access to these hash tables must be protected by a mutex. +*/ +static Hash lockHash = { SQLITE_HASH_BINARY, 0, 0, 0, 0, 0 }; +static Hash openHash = { SQLITE_HASH_BINARY, 0, 0, 0, 0, 0 }; + +/* +** Release a lockInfo structure previously allocated by findLockInfo(). +*/ +static void releaseLockInfo(struct lockInfo *pLock){ + pLock->nRef--; + if( pLock->nRef==0 ){ + sqliteHashInsert(&lockHash, &pLock->key, sizeof(pLock->key), 0); + sqliteFree(pLock); + } +} + +/* +** Release a openCnt structure previously allocated by findLockInfo(). +*/ +static void releaseOpenCnt(struct openCnt *pOpen){ + pOpen->nRef--; + if( pOpen->nRef==0 ){ + sqliteHashInsert(&openHash, &pOpen->key, sizeof(pOpen->key), 0); + sqliteFree(pOpen->aPending); + sqliteFree(pOpen); + } +} + +/* +** Given a file descriptor, locate lockInfo and openCnt structures that +** describes that file descriptor. Create a new ones if necessary. The +** return values might be unset if an error occurs. +** +** Return the number of errors. +*/ +int findLockInfo( + int fd, /* The file descriptor used in the key */ + struct lockInfo **ppLock, /* Return the lockInfo structure here */ + struct openCnt **ppOpen /* Return the openCnt structure here */ +){ + int rc; + struct lockKey key1; + struct openKey key2; + struct stat statbuf; + struct lockInfo *pLock; + struct openCnt *pOpen; + rc = fstat(fd, &statbuf); + if( rc!=0 ) return 1; + memset(&key1, 0, sizeof(key1)); + key1.dev = statbuf.st_dev; + key1.ino = statbuf.st_ino; + key1.pid = getpid(); + memset(&key2, 0, sizeof(key2)); + key2.dev = statbuf.st_dev; + key2.ino = statbuf.st_ino; + pLock = (struct lockInfo*)sqliteHashFind(&lockHash, &key1, sizeof(key1)); + if( pLock==0 ){ + struct lockInfo *pOld; + pLock = sqliteMallocRaw( sizeof(*pLock) ); + if( pLock==0 ) return 1; + pLock->key = key1; + pLock->nRef = 1; + pLock->cnt = 0; + pOld = sqliteHashInsert(&lockHash, &pLock->key, sizeof(key1), pLock); + if( pOld!=0 ){ + assert( pOld==pLock ); + sqliteFree(pLock); + return 1; + } + }else{ + pLock->nRef++; + } + *ppLock = pLock; + pOpen = (struct openCnt*)sqliteHashFind(&openHash, &key2, sizeof(key2)); + if( pOpen==0 ){ + struct openCnt *pOld; + pOpen = sqliteMallocRaw( sizeof(*pOpen) ); + if( pOpen==0 ){ + releaseLockInfo(pLock); + return 1; + } + pOpen->key = key2; + pOpen->nRef = 1; + pOpen->nLock = 0; + pOpen->nPending = 0; + pOpen->aPending = 0; + pOld = sqliteHashInsert(&openHash, &pOpen->key, sizeof(key2), pOpen); + if( pOld!=0 ){ + assert( pOld==pOpen ); + sqliteFree(pOpen); + releaseLockInfo(pLock); + return 1; + } + }else{ + pOpen->nRef++; + } + *ppOpen = pOpen; + return 0; +} + +#endif /** POSIX advisory lock work-around **/ + +/* +** If we compile with the SQLITE_TEST macro set, then the following block +** of code will give us the ability to simulate a disk I/O error. This +** is used for testing the I/O recovery logic. +*/ +#ifdef SQLITE_TEST +int sqlite_io_error_pending = 0; +#define SimulateIOError(A) \ + if( sqlite_io_error_pending ) \ + if( sqlite_io_error_pending-- == 1 ){ local_ioerr(); return A; } +static void local_ioerr(){ + sqlite_io_error_pending = 0; /* Really just a place to set a breakpoint */ +} +#else +#define SimulateIOError(A) +#endif + +/* +** When testing, keep a count of the number of open files. +*/ +#ifdef SQLITE_TEST +int sqlite_open_file_count = 0; +#define OpenCounter(X) sqlite_open_file_count+=(X) +#else +#define OpenCounter(X) +#endif + + +/* +** Delete the named file +*/ +int sqliteOsDelete(const char *zFilename){ +#if OS_UNIX + unlink(zFilename); +#endif +#if OS_WIN + DeleteFile(zFilename); +#endif +#if OS_MAC + unlink(zFilename); +#endif + return SQLITE_OK; +} + +/* +** Return TRUE if the named file exists. +*/ +int sqliteOsFileExists(const char *zFilename){ +#if OS_UNIX + return access(zFilename, 0)==0; +#endif +#if OS_WIN + return GetFileAttributes(zFilename) != 0xffffffff; +#endif +#if OS_MAC + return access(zFilename, 0)==0; +#endif +} + + +#if 0 /* NOT USED */ +/* +** Change the name of an existing file. +*/ +int sqliteOsFileRename(const char *zOldName, const char *zNewName){ +#if OS_UNIX + if( link(zOldName, zNewName) ){ + return SQLITE_ERROR; + } + unlink(zOldName); + return SQLITE_OK; +#endif +#if OS_WIN + if( !MoveFile(zOldName, zNewName) ){ + return SQLITE_ERROR; + } + return SQLITE_OK; +#endif +#if OS_MAC + /**** FIX ME ***/ + return SQLITE_ERROR; +#endif +} +#endif /* NOT USED */ + +/* +** Attempt to open a file for both reading and writing. If that +** fails, try opening it read-only. If the file does not exist, +** try to create it. +** +** On success, a handle for the open file is written to *id +** and *pReadonly is set to 0 if the file was opened for reading and +** writing or 1 if the file was opened read-only. The function returns +** SQLITE_OK. +** +** On failure, the function returns SQLITE_CANTOPEN and leaves +** *id and *pReadonly unchanged. +*/ +int sqliteOsOpenReadWrite( + const char *zFilename, + OsFile *id, + int *pReadonly +){ +#if OS_UNIX + int rc; + id->dirfd = -1; + id->fd = open(zFilename, O_RDWR|O_CREAT|O_LARGEFILE|O_BINARY, 0644); + if( id->fd<0 ){ +#ifdef EISDIR + if( errno==EISDIR ){ + return SQLITE_CANTOPEN; + } +#endif + id->fd = open(zFilename, O_RDONLY|O_LARGEFILE|O_BINARY); + if( id->fd<0 ){ + return SQLITE_CANTOPEN; + } + *pReadonly = 1; + }else{ + *pReadonly = 0; + } + sqliteOsEnterMutex(); + rc = findLockInfo(id->fd, &id->pLock, &id->pOpen); + sqliteOsLeaveMutex(); + if( rc ){ + close(id->fd); + return SQLITE_NOMEM; + } + id->locked = 0; + TRACE3("OPEN %-3d %s\n", id->fd, zFilename); + OpenCounter(+1); + return SQLITE_OK; +#endif +#if OS_WIN + HANDLE h = CreateFile(zFilename, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, + NULL + ); + if( h==INVALID_HANDLE_VALUE ){ + h = CreateFile(zFilename, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, + NULL + ); + if( h==INVALID_HANDLE_VALUE ){ + return SQLITE_CANTOPEN; + } + *pReadonly = 1; + }else{ + *pReadonly = 0; + } + id->h = h; + id->locked = 0; + OpenCounter(+1); + return SQLITE_OK; +#endif +#if OS_MAC + FSSpec fsSpec; +# ifdef _LARGE_FILE + HFSUniStr255 dfName; + FSRef fsRef; + if( __path2fss(zFilename, &fsSpec) != noErr ){ + if( HCreate(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, 'SQLI', cDocumentFile) != noErr ) + return SQLITE_CANTOPEN; + } + if( FSpMakeFSRef(&fsSpec, &fsRef) != noErr ) + return SQLITE_CANTOPEN; + FSGetDataForkName(&dfName); + if( FSOpenFork(&fsRef, dfName.length, dfName.unicode, + fsRdWrShPerm, &(id->refNum)) != noErr ){ + if( FSOpenFork(&fsRef, dfName.length, dfName.unicode, + fsRdWrPerm, &(id->refNum)) != noErr ){ + if (FSOpenFork(&fsRef, dfName.length, dfName.unicode, + fsRdPerm, &(id->refNum)) != noErr ) + return SQLITE_CANTOPEN; + else + *pReadonly = 1; + } else + *pReadonly = 0; + } else + *pReadonly = 0; +# else + __path2fss(zFilename, &fsSpec); + if( !sqliteOsFileExists(zFilename) ){ + if( HCreate(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, 'SQLI', cDocumentFile) != noErr ) + return SQLITE_CANTOPEN; + } + if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrShPerm, &(id->refNum)) != noErr ){ + if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrPerm, &(id->refNum)) != noErr ){ + if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdPerm, &(id->refNum)) != noErr ) + return SQLITE_CANTOPEN; + else + *pReadonly = 1; + } else + *pReadonly = 0; + } else + *pReadonly = 0; +# endif + if( HOpenRF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrShPerm, &(id->refNumRF)) != noErr){ + id->refNumRF = -1; + } + id->locked = 0; + id->delOnClose = 0; + OpenCounter(+1); + return SQLITE_OK; +#endif +} + + +/* +** Attempt to open a new file for exclusive access by this process. +** The file will be opened for both reading and writing. To avoid +** a potential security problem, we do not allow the file to have +** previously existed. Nor do we allow the file to be a symbolic +** link. +** +** If delFlag is true, then make arrangements to automatically delete +** the file when it is closed. +** +** On success, write the file handle into *id and return SQLITE_OK. +** +** On failure, return SQLITE_CANTOPEN. +*/ +int sqliteOsOpenExclusive(const char *zFilename, OsFile *id, int delFlag){ +#if OS_UNIX + int rc; + if( access(zFilename, 0)==0 ){ + return SQLITE_CANTOPEN; + } + id->dirfd = -1; + id->fd = open(zFilename, + O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW|O_LARGEFILE|O_BINARY, 0600); + if( id->fd<0 ){ + return SQLITE_CANTOPEN; + } + sqliteOsEnterMutex(); + rc = findLockInfo(id->fd, &id->pLock, &id->pOpen); + sqliteOsLeaveMutex(); + if( rc ){ + close(id->fd); + unlink(zFilename); + return SQLITE_NOMEM; + } + id->locked = 0; + if( delFlag ){ + unlink(zFilename); + } + TRACE3("OPEN-EX %-3d %s\n", id->fd, zFilename); + OpenCounter(+1); + return SQLITE_OK; +#endif +#if OS_WIN + HANDLE h; + int fileflags; + if( delFlag ){ + fileflags = FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_RANDOM_ACCESS + | FILE_FLAG_DELETE_ON_CLOSE; + }else{ + fileflags = FILE_FLAG_RANDOM_ACCESS; + } + h = CreateFile(zFilename, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + fileflags, + NULL + ); + if( h==INVALID_HANDLE_VALUE ){ + return SQLITE_CANTOPEN; + } + id->h = h; + id->locked = 0; + OpenCounter(+1); + return SQLITE_OK; +#endif +#if OS_MAC + FSSpec fsSpec; +# ifdef _LARGE_FILE + HFSUniStr255 dfName; + FSRef fsRef; + __path2fss(zFilename, &fsSpec); + if( HCreate(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, 'SQLI', cDocumentFile) != noErr ) + return SQLITE_CANTOPEN; + if( FSpMakeFSRef(&fsSpec, &fsRef) != noErr ) + return SQLITE_CANTOPEN; + FSGetDataForkName(&dfName); + if( FSOpenFork(&fsRef, dfName.length, dfName.unicode, + fsRdWrPerm, &(id->refNum)) != noErr ) + return SQLITE_CANTOPEN; +# else + __path2fss(zFilename, &fsSpec); + if( HCreate(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, 'SQLI', cDocumentFile) != noErr ) + return SQLITE_CANTOPEN; + if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrPerm, &(id->refNum)) != noErr ) + return SQLITE_CANTOPEN; +# endif + id->refNumRF = -1; + id->locked = 0; + id->delOnClose = delFlag; + if (delFlag) + id->pathToDel = sqliteOsFullPathname(zFilename); + OpenCounter(+1); + return SQLITE_OK; +#endif +} + +/* +** Attempt to open a new file for read-only access. +** +** On success, write the file handle into *id and return SQLITE_OK. +** +** On failure, return SQLITE_CANTOPEN. +*/ +int sqliteOsOpenReadOnly(const char *zFilename, OsFile *id){ +#if OS_UNIX + int rc; + id->dirfd = -1; + id->fd = open(zFilename, O_RDONLY|O_LARGEFILE|O_BINARY); + if( id->fd<0 ){ + return SQLITE_CANTOPEN; + } + sqliteOsEnterMutex(); + rc = findLockInfo(id->fd, &id->pLock, &id->pOpen); + sqliteOsLeaveMutex(); + if( rc ){ + close(id->fd); + return SQLITE_NOMEM; + } + id->locked = 0; + TRACE3("OPEN-RO %-3d %s\n", id->fd, zFilename); + OpenCounter(+1); + return SQLITE_OK; +#endif +#if OS_WIN + HANDLE h = CreateFile(zFilename, + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, + NULL + ); + if( h==INVALID_HANDLE_VALUE ){ + return SQLITE_CANTOPEN; + } + id->h = h; + id->locked = 0; + OpenCounter(+1); + return SQLITE_OK; +#endif +#if OS_MAC + FSSpec fsSpec; +# ifdef _LARGE_FILE + HFSUniStr255 dfName; + FSRef fsRef; + if( __path2fss(zFilename, &fsSpec) != noErr ) + return SQLITE_CANTOPEN; + if( FSpMakeFSRef(&fsSpec, &fsRef) != noErr ) + return SQLITE_CANTOPEN; + FSGetDataForkName(&dfName); + if( FSOpenFork(&fsRef, dfName.length, dfName.unicode, + fsRdPerm, &(id->refNum)) != noErr ) + return SQLITE_CANTOPEN; +# else + __path2fss(zFilename, &fsSpec); + if( HOpenDF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdPerm, &(id->refNum)) != noErr ) + return SQLITE_CANTOPEN; +# endif + if( HOpenRF(fsSpec.vRefNum, fsSpec.parID, fsSpec.name, fsRdWrShPerm, &(id->refNumRF)) != noErr){ + id->refNumRF = -1; + } + id->locked = 0; + id->delOnClose = 0; + OpenCounter(+1); + return SQLITE_OK; +#endif +} + +/* +** Attempt to open a file descriptor for the directory that contains a +** file. This file descriptor can be used to fsync() the directory +** in order to make sure the creation of a new file is actually written +** to disk. +** +** This routine is only meaningful for Unix. It is a no-op under +** windows since windows does not support hard links. +** +** On success, a handle for a previously open file is at *id is +** updated with the new directory file descriptor and SQLITE_OK is +** returned. +** +** On failure, the function returns SQLITE_CANTOPEN and leaves +** *id unchanged. +*/ +int sqliteOsOpenDirectory( + const char *zDirname, + OsFile *id +){ +#if OS_UNIX + if( id->fd<0 ){ + /* Do not open the directory if the corresponding file is not already + ** open. */ + return SQLITE_CANTOPEN; + } + assert( id->dirfd<0 ); + id->dirfd = open(zDirname, O_RDONLY|O_BINARY, 0644); + if( id->dirfd<0 ){ + return SQLITE_CANTOPEN; + } + TRACE3("OPENDIR %-3d %s\n", id->dirfd, zDirname); +#endif + return SQLITE_OK; +} + +/* +** If the following global variable points to a string which is the +** name of a directory, then that directory will be used to store +** temporary files. +*/ +const char *sqlite_temp_directory = 0; + +/* +** Create a temporary file name in zBuf. zBuf must be big enough to +** hold at least SQLITE_TEMPNAME_SIZE characters. +*/ +int sqliteOsTempFileName(char *zBuf){ +#if OS_UNIX + static const char *azDirs[] = { + 0, + "/var/tmp", + "/usr/tmp", + "/tmp", + ".", + }; + static unsigned char zChars[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; + int i, j; + struct stat buf; + const char *zDir = "."; + azDirs[0] = sqlite_temp_directory; + for(i=0; i0 && zTempPath[i-1]=='\\'; i--){} + zTempPath[i] = 0; + zDir = zTempPath; + }else{ + zDir = sqlite_temp_directory; + } + for(;;){ + sprintf(zBuf, "%s\\"TEMP_FILE_PREFIX, zDir); + j = strlen(zBuf); + sqliteRandomness(15, &zBuf[j]); + for(i=0; i<15; i++, j++){ + zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; + } + zBuf[j] = 0; + if( !sqliteOsFileExists(zBuf) ) break; + } +#endif +#if OS_MAC + static char zChars[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; + int i, j; + char *zDir; + char zTempPath[SQLITE_TEMPNAME_SIZE]; + char zdirName[32]; + CInfoPBRec infoRec; + Str31 dirName; + memset(&infoRec, 0, sizeof(infoRec)); + memset(zTempPath, 0, SQLITE_TEMPNAME_SIZE); + if( sqlite_temp_directory!=0 ){ + zDir = sqlite_temp_directory; + }else if( FindFolder(kOnSystemDisk, kTemporaryFolderType, kCreateFolder, + &(infoRec.dirInfo.ioVRefNum), &(infoRec.dirInfo.ioDrParID)) == noErr ){ + infoRec.dirInfo.ioNamePtr = dirName; + do{ + infoRec.dirInfo.ioFDirIndex = -1; + infoRec.dirInfo.ioDrDirID = infoRec.dirInfo.ioDrParID; + if( PBGetCatInfoSync(&infoRec) == noErr ){ + CopyPascalStringToC(dirName, zdirName); + i = strlen(zdirName); + memmove(&(zTempPath[i+1]), zTempPath, strlen(zTempPath)); + strcpy(zTempPath, zdirName); + zTempPath[i] = ':'; + }else{ + *zTempPath = 0; + break; + } + } while( infoRec.dirInfo.ioDrDirID != fsRtDirID ); + zDir = zTempPath; + } + if( zDir[0]==0 ){ + getcwd(zTempPath, SQLITE_TEMPNAME_SIZE-24); + zDir = zTempPath; + } + for(;;){ + sprintf(zBuf, "%s"TEMP_FILE_PREFIX, zDir); + j = strlen(zBuf); + sqliteRandomness(15, &zBuf[j]); + for(i=0; i<15; i++, j++){ + zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; + } + zBuf[j] = 0; + if( !sqliteOsFileExists(zBuf) ) break; + } +#endif + return SQLITE_OK; +} + +/* +** Close a file. +*/ +int sqliteOsClose(OsFile *id){ +#if OS_UNIX + sqliteOsUnlock(id); + if( id->dirfd>=0 ) close(id->dirfd); + id->dirfd = -1; + sqliteOsEnterMutex(); + if( id->pOpen->nLock ){ + /* If there are outstanding locks, do not actually close the file just + ** yet because that would clear those locks. Instead, add the file + ** descriptor to pOpen->aPending. It will be automatically closed when + ** the last lock is cleared. + */ + int *aNew; + struct openCnt *pOpen = id->pOpen; + pOpen->nPending++; + aNew = sqliteRealloc( pOpen->aPending, pOpen->nPending*sizeof(int) ); + if( aNew==0 ){ + /* If a malloc fails, just leak the file descriptor */ + }else{ + pOpen->aPending = aNew; + pOpen->aPending[pOpen->nPending-1] = id->fd; + } + }else{ + /* There are no outstanding locks so we can close the file immediately */ + close(id->fd); + } + releaseLockInfo(id->pLock); + releaseOpenCnt(id->pOpen); + sqliteOsLeaveMutex(); + TRACE2("CLOSE %-3d\n", id->fd); + OpenCounter(-1); + return SQLITE_OK; +#endif +#if OS_WIN + CloseHandle(id->h); + OpenCounter(-1); + return SQLITE_OK; +#endif +#if OS_MAC + if( id->refNumRF!=-1 ) + FSClose(id->refNumRF); +# ifdef _LARGE_FILE + FSCloseFork(id->refNum); +# else + FSClose(id->refNum); +# endif + if( id->delOnClose ){ + unlink(id->pathToDel); + sqliteFree(id->pathToDel); + } + OpenCounter(-1); + return SQLITE_OK; +#endif +} + +/* +** Read data from a file into a buffer. Return SQLITE_OK if all +** bytes were read successfully and SQLITE_IOERR if anything goes +** wrong. +*/ +int sqliteOsRead(OsFile *id, void *pBuf, int amt){ +#if OS_UNIX + int got; + SimulateIOError(SQLITE_IOERR); + TIMER_START; + got = read(id->fd, pBuf, amt); + TIMER_END; + TRACE4("READ %-3d %7d %d\n", id->fd, last_page, elapse); + SEEK(0); + /* if( got<0 ) got = 0; */ + if( got==amt ){ + return SQLITE_OK; + }else{ + return SQLITE_IOERR; + } +#endif +#if OS_WIN + DWORD got; + SimulateIOError(SQLITE_IOERR); + TRACE2("READ %d\n", last_page); + if( !ReadFile(id->h, pBuf, amt, &got, 0) ){ + got = 0; + } + if( got==(DWORD)amt ){ + return SQLITE_OK; + }else{ + return SQLITE_IOERR; + } +#endif +#if OS_MAC + int got; + SimulateIOError(SQLITE_IOERR); + TRACE2("READ %d\n", last_page); +# ifdef _LARGE_FILE + FSReadFork(id->refNum, fsAtMark, 0, (ByteCount)amt, pBuf, (ByteCount*)&got); +# else + got = amt; + FSRead(id->refNum, &got, pBuf); +# endif + if( got==amt ){ + return SQLITE_OK; + }else{ + return SQLITE_IOERR; + } +#endif +} + +/* +** Write data from a buffer into a file. Return SQLITE_OK on success +** or some other error code on failure. +*/ +int sqliteOsWrite(OsFile *id, const void *pBuf, int amt){ +#if OS_UNIX + int wrote = 0; + SimulateIOError(SQLITE_IOERR); + TIMER_START; + while( amt>0 && (wrote = write(id->fd, pBuf, amt))>0 ){ + amt -= wrote; + pBuf = &((char*)pBuf)[wrote]; + } + TIMER_END; + TRACE4("WRITE %-3d %7d %d\n", id->fd, last_page, elapse); + SEEK(0); + if( amt>0 ){ + return SQLITE_FULL; + } + return SQLITE_OK; +#endif +#if OS_WIN + int rc; + DWORD wrote; + SimulateIOError(SQLITE_IOERR); + TRACE2("WRITE %d\n", last_page); + while( amt>0 && (rc = WriteFile(id->h, pBuf, amt, &wrote, 0))!=0 && wrote>0 ){ + amt -= wrote; + pBuf = &((char*)pBuf)[wrote]; + } + if( !rc || amt>(int)wrote ){ + return SQLITE_FULL; + } + return SQLITE_OK; +#endif +#if OS_MAC + OSErr oserr; + int wrote = 0; + SimulateIOError(SQLITE_IOERR); + TRACE2("WRITE %d\n", last_page); + while( amt>0 ){ +# ifdef _LARGE_FILE + oserr = FSWriteFork(id->refNum, fsAtMark, 0, + (ByteCount)amt, pBuf, (ByteCount*)&wrote); +# else + wrote = amt; + oserr = FSWrite(id->refNum, &wrote, pBuf); +# endif + if( wrote == 0 || oserr != noErr) + break; + amt -= wrote; + pBuf = &((char*)pBuf)[wrote]; + } + if( oserr != noErr || amt>wrote ){ + return SQLITE_FULL; + } + return SQLITE_OK; +#endif +} + +/* +** Move the read/write pointer in a file. +*/ +int sqliteOsSeek(OsFile *id, off_t offset){ + SEEK(offset/1024 + 1); +#if OS_UNIX + lseek(id->fd, offset, SEEK_SET); + return SQLITE_OK; +#endif +#if OS_WIN + { + LONG upperBits = offset>>32; + LONG lowerBits = offset & 0xffffffff; + DWORD rc; + rc = SetFilePointer(id->h, lowerBits, &upperBits, FILE_BEGIN); + /* TRACE3("SEEK rc=0x%x upper=0x%x\n", rc, upperBits); */ + } + return SQLITE_OK; +#endif +#if OS_MAC + { + off_t curSize; + if( sqliteOsFileSize(id, &curSize) != SQLITE_OK ){ + return SQLITE_IOERR; + } + if( offset >= curSize ){ + if( sqliteOsTruncate(id, offset+1) != SQLITE_OK ){ + return SQLITE_IOERR; + } + } +# ifdef _LARGE_FILE + if( FSSetForkPosition(id->refNum, fsFromStart, offset) != noErr ){ +# else + if( SetFPos(id->refNum, fsFromStart, offset) != noErr ){ +# endif + return SQLITE_IOERR; + }else{ + return SQLITE_OK; + } + } +#endif +} + +#ifdef SQLITE_NOSYNC +# define fsync(X) 0 +#endif + +/* +** Make sure all writes to a particular file are committed to disk. +** +** Under Unix, also make sure that the directory entry for the file +** has been created by fsync-ing the directory that contains the file. +** If we do not do this and we encounter a power failure, the directory +** entry for the journal might not exist after we reboot. The next +** SQLite to access the file will not know that the journal exists (because +** the directory entry for the journal was never created) and the transaction +** will not roll back - possibly leading to database corruption. +*/ +int sqliteOsSync(OsFile *id){ +#if OS_UNIX + SimulateIOError(SQLITE_IOERR); + TRACE2("SYNC %-3d\n", id->fd); + if( fsync(id->fd) ){ + return SQLITE_IOERR; + }else{ + if( id->dirfd>=0 ){ + TRACE2("DIRSYNC %-3d\n", id->dirfd); + fsync(id->dirfd); + close(id->dirfd); /* Only need to sync once, so close the directory */ + id->dirfd = -1; /* when we are done. */ + } + return SQLITE_OK; + } +#endif +#if OS_WIN + if( FlushFileBuffers(id->h) ){ + return SQLITE_OK; + }else{ + return SQLITE_IOERR; + } +#endif +#if OS_MAC +# ifdef _LARGE_FILE + if( FSFlushFork(id->refNum) != noErr ){ +# else + ParamBlockRec params; + memset(¶ms, 0, sizeof(ParamBlockRec)); + params.ioParam.ioRefNum = id->refNum; + if( PBFlushFileSync(¶ms) != noErr ){ +# endif + return SQLITE_IOERR; + }else{ + return SQLITE_OK; + } +#endif +} + +/* +** Truncate an open file to a specified size +*/ +int sqliteOsTruncate(OsFile *id, off_t nByte){ + SimulateIOError(SQLITE_IOERR); +#if OS_UNIX + return ftruncate(id->fd, nByte)==0 ? SQLITE_OK : SQLITE_IOERR; +#endif +#if OS_WIN + { + LONG upperBits = nByte>>32; + SetFilePointer(id->h, nByte, &upperBits, FILE_BEGIN); + SetEndOfFile(id->h); + } + return SQLITE_OK; +#endif +#if OS_MAC +# ifdef _LARGE_FILE + if( FSSetForkSize(id->refNum, fsFromStart, nByte) != noErr){ +# else + if( SetEOF(id->refNum, nByte) != noErr ){ +# endif + return SQLITE_IOERR; + }else{ + return SQLITE_OK; + } +#endif +} + +/* +** Determine the current size of a file in bytes +*/ +int sqliteOsFileSize(OsFile *id, off_t *pSize){ +#if OS_UNIX + struct stat buf; + SimulateIOError(SQLITE_IOERR); + if( fstat(id->fd, &buf)!=0 ){ + return SQLITE_IOERR; + } + *pSize = buf.st_size; + return SQLITE_OK; +#endif +#if OS_WIN + DWORD upperBits, lowerBits; + SimulateIOError(SQLITE_IOERR); + lowerBits = GetFileSize(id->h, &upperBits); + *pSize = (((off_t)upperBits)<<32) + lowerBits; + return SQLITE_OK; +#endif +#if OS_MAC +# ifdef _LARGE_FILE + if( FSGetForkSize(id->refNum, pSize) != noErr){ +# else + if( GetEOF(id->refNum, pSize) != noErr ){ +# endif + return SQLITE_IOERR; + }else{ + return SQLITE_OK; + } +#endif +} + +#if OS_WIN +/* +** Return true (non-zero) if we are running under WinNT, Win2K or WinXP. +** Return false (zero) for Win95, Win98, or WinME. +** +** Here is an interesting observation: Win95, Win98, and WinME lack +** the LockFileEx() API. But we can still statically link against that +** API as long as we don't call it win running Win95/98/ME. A call to +** this routine is used to determine if the host is Win95/98/ME or +** WinNT/2K/XP so that we will know whether or not we can safely call +** the LockFileEx() API. +*/ +int isNT(void){ + static int osType = 0; /* 0=unknown 1=win95 2=winNT */ + if( osType==0 ){ + OSVERSIONINFO sInfo; + sInfo.dwOSVersionInfoSize = sizeof(sInfo); + GetVersionEx(&sInfo); + osType = sInfo.dwPlatformId==VER_PLATFORM_WIN32_NT ? 2 : 1; + } + return osType==2; +} +#endif + +/* +** Windows file locking notes: [similar issues apply to MacOS] +** +** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because +** those functions are not available. So we use only LockFile() and +** UnlockFile(). +** +** LockFile() prevents not just writing but also reading by other processes. +** (This is a design error on the part of Windows, but there is nothing +** we can do about that.) So the region used for locking is at the +** end of the file where it is unlikely to ever interfere with an +** actual read attempt. +** +** A database read lock is obtained by locking a single randomly-chosen +** byte out of a specific range of bytes. The lock byte is obtained at +** random so two separate readers can probably access the file at the +** same time, unless they are unlucky and choose the same lock byte. +** A database write lock is obtained by locking all bytes in the range. +** There can only be one writer. +** +** A lock is obtained on the first byte of the lock range before acquiring +** either a read lock or a write lock. This prevents two processes from +** attempting to get a lock at a same time. The semantics of +** sqliteOsReadLock() require that if there is already a write lock, that +** lock is converted into a read lock atomically. The lock on the first +** byte allows us to drop the old write lock and get the read lock without +** another process jumping into the middle and messing us up. The same +** argument applies to sqliteOsWriteLock(). +** +** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available, +** which means we can use reader/writer locks. When reader writer locks +** are used, the lock is placed on the same range of bytes that is used +** for probabilistic locking in Win95/98/ME. Hence, the locking scheme +** will support two or more Win95 readers or two or more WinNT readers. +** But a single Win95 reader will lock out all WinNT readers and a single +** WinNT reader will lock out all other Win95 readers. +** +** Note: On MacOS we use the resource fork for locking. +** +** The following #defines specify the range of bytes used for locking. +** N_LOCKBYTE is the number of bytes available for doing the locking. +** The first byte used to hold the lock while the lock is changing does +** not count toward this number. FIRST_LOCKBYTE is the address of +** the first byte in the range of bytes used for locking. +*/ +#define N_LOCKBYTE 10239 +#if OS_MAC +# define FIRST_LOCKBYTE (0x000fffff - N_LOCKBYTE) +#else +# define FIRST_LOCKBYTE (0xffffffff - N_LOCKBYTE) +#endif + +/* +** Change the status of the lock on the file "id" to be a readlock. +** If the file was write locked, then this reduces the lock to a read. +** If the file was read locked, then this acquires a new read lock. +** +** Return SQLITE_OK on success and SQLITE_BUSY on failure. If this +** library was compiled with large file support (LFS) but LFS is not +** available on the host, then an SQLITE_NOLFS is returned. +*/ +int sqliteOsReadLock(OsFile *id){ +#if OS_UNIX + int rc; + sqliteOsEnterMutex(); + if( id->pLock->cnt>0 ){ + if( !id->locked ){ + id->pLock->cnt++; + id->locked = 1; + id->pOpen->nLock++; + } + rc = SQLITE_OK; + }else if( id->locked || id->pLock->cnt==0 ){ + struct flock lock; + int s; + lock.l_type = F_RDLCK; + lock.l_whence = SEEK_SET; + lock.l_start = lock.l_len = 0L; + s = fcntl(id->fd, F_SETLK, &lock); + if( s!=0 ){ + rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY; + }else{ + rc = SQLITE_OK; + if( !id->locked ){ + id->pOpen->nLock++; + id->locked = 1; + } + id->pLock->cnt = 1; + } + }else{ + rc = SQLITE_BUSY; + } + sqliteOsLeaveMutex(); + return rc; +#endif +#if OS_WIN + int rc; + if( id->locked>0 ){ + rc = SQLITE_OK; + }else{ + int lk; + int res; + int cnt = 100; + sqliteRandomness(sizeof(lk), &lk); + lk = (lk & 0x7fffffff)%N_LOCKBYTE + 1; + while( cnt-->0 && (res = LockFile(id->h, FIRST_LOCKBYTE, 0, 1, 0))==0 ){ + Sleep(1); + } + if( res ){ + UnlockFile(id->h, FIRST_LOCKBYTE+1, 0, N_LOCKBYTE, 0); + if( isNT() ){ + OVERLAPPED ovlp; + ovlp.Offset = FIRST_LOCKBYTE+1; + ovlp.OffsetHigh = 0; + ovlp.hEvent = 0; + res = LockFileEx(id->h, LOCKFILE_FAIL_IMMEDIATELY, + 0, N_LOCKBYTE, 0, &ovlp); + }else{ + res = LockFile(id->h, FIRST_LOCKBYTE+lk, 0, 1, 0); + } + UnlockFile(id->h, FIRST_LOCKBYTE, 0, 1, 0); + } + if( res ){ + id->locked = lk; + rc = SQLITE_OK; + }else{ + rc = SQLITE_BUSY; + } + } + return rc; +#endif +#if OS_MAC + int rc; + if( id->locked>0 || id->refNumRF == -1 ){ + rc = SQLITE_OK; + }else{ + int lk; + OSErr res; + int cnt = 5; + ParamBlockRec params; + sqliteRandomness(sizeof(lk), &lk); + lk = (lk & 0x7fffffff)%N_LOCKBYTE + 1; + memset(¶ms, 0, sizeof(params)); + params.ioParam.ioRefNum = id->refNumRF; + params.ioParam.ioPosMode = fsFromStart; + params.ioParam.ioPosOffset = FIRST_LOCKBYTE; + params.ioParam.ioReqCount = 1; + while( cnt-->0 && (res = PBLockRangeSync(¶ms))!=noErr ){ + UInt32 finalTicks; + Delay(1, &finalTicks); /* 1/60 sec */ + } + if( res == noErr ){ + params.ioParam.ioPosOffset = FIRST_LOCKBYTE+1; + params.ioParam.ioReqCount = N_LOCKBYTE; + PBUnlockRangeSync(¶ms); + params.ioParam.ioPosOffset = FIRST_LOCKBYTE+lk; + params.ioParam.ioReqCount = 1; + res = PBLockRangeSync(¶ms); + params.ioParam.ioPosOffset = FIRST_LOCKBYTE; + params.ioParam.ioReqCount = 1; + PBUnlockRangeSync(¶ms); + } + if( res == noErr ){ + id->locked = lk; + rc = SQLITE_OK; + }else{ + rc = SQLITE_BUSY; + } + } + return rc; +#endif +} + +/* +** Change the lock status to be an exclusive or write lock. Return +** SQLITE_OK on success and SQLITE_BUSY on a failure. If this +** library was compiled with large file support (LFS) but LFS is not +** available on the host, then an SQLITE_NOLFS is returned. +*/ +int sqliteOsWriteLock(OsFile *id){ +#if OS_UNIX + int rc; + sqliteOsEnterMutex(); + if( id->pLock->cnt==0 || (id->pLock->cnt==1 && id->locked==1) ){ + struct flock lock; + int s; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = lock.l_len = 0L; + s = fcntl(id->fd, F_SETLK, &lock); + if( s!=0 ){ + rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY; + }else{ + rc = SQLITE_OK; + if( !id->locked ){ + id->pOpen->nLock++; + id->locked = 1; + } + id->pLock->cnt = -1; + } + }else{ + rc = SQLITE_BUSY; + } + sqliteOsLeaveMutex(); + return rc; +#endif +#if OS_WIN + int rc; + if( id->locked<0 ){ + rc = SQLITE_OK; + }else{ + int res; + int cnt = 100; + while( cnt-->0 && (res = LockFile(id->h, FIRST_LOCKBYTE, 0, 1, 0))==0 ){ + Sleep(1); + } + if( res ){ + if( id->locked>0 ){ + if( isNT() ){ + UnlockFile(id->h, FIRST_LOCKBYTE+1, 0, N_LOCKBYTE, 0); + }else{ + res = UnlockFile(id->h, FIRST_LOCKBYTE + id->locked, 0, 1, 0); + } + } + if( res ){ + res = LockFile(id->h, FIRST_LOCKBYTE+1, 0, N_LOCKBYTE, 0); + }else{ + res = 0; + } + UnlockFile(id->h, FIRST_LOCKBYTE, 0, 1, 0); + } + if( res ){ + id->locked = -1; + rc = SQLITE_OK; + }else{ + rc = SQLITE_BUSY; + } + } + return rc; +#endif +#if OS_MAC + int rc; + if( id->locked<0 || id->refNumRF == -1 ){ + rc = SQLITE_OK; + }else{ + OSErr res; + int cnt = 5; + ParamBlockRec params; + memset(¶ms, 0, sizeof(params)); + params.ioParam.ioRefNum = id->refNumRF; + params.ioParam.ioPosMode = fsFromStart; + params.ioParam.ioPosOffset = FIRST_LOCKBYTE; + params.ioParam.ioReqCount = 1; + while( cnt-->0 && (res = PBLockRangeSync(¶ms))!=noErr ){ + UInt32 finalTicks; + Delay(1, &finalTicks); /* 1/60 sec */ + } + if( res == noErr ){ + params.ioParam.ioPosOffset = FIRST_LOCKBYTE + id->locked; + params.ioParam.ioReqCount = 1; + if( id->locked==0 + || PBUnlockRangeSync(¶ms)==noErr ){ + params.ioParam.ioPosOffset = FIRST_LOCKBYTE+1; + params.ioParam.ioReqCount = N_LOCKBYTE; + res = PBLockRangeSync(¶ms); + }else{ + res = afpRangeNotLocked; + } + params.ioParam.ioPosOffset = FIRST_LOCKBYTE; + params.ioParam.ioReqCount = 1; + PBUnlockRangeSync(¶ms); + } + if( res == noErr ){ + id->locked = -1; + rc = SQLITE_OK; + }else{ + rc = SQLITE_BUSY; + } + } + return rc; +#endif +} + +/* +** Unlock the given file descriptor. If the file descriptor was +** not previously locked, then this routine is a no-op. If this +** library was compiled with large file support (LFS) but LFS is not +** available on the host, then an SQLITE_NOLFS is returned. +*/ +int sqliteOsUnlock(OsFile *id){ +#if OS_UNIX + int rc; + if( !id->locked ) return SQLITE_OK; + sqliteOsEnterMutex(); + assert( id->pLock->cnt!=0 ); + if( id->pLock->cnt>1 ){ + id->pLock->cnt--; + rc = SQLITE_OK; + }else{ + struct flock lock; + int s; + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = lock.l_len = 0L; + s = fcntl(id->fd, F_SETLK, &lock); + if( s!=0 ){ + rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY; + }else{ + rc = SQLITE_OK; + id->pLock->cnt = 0; + } + } + if( rc==SQLITE_OK ){ + /* Decrement the count of locks against this same file. When the + ** count reaches zero, close any other file descriptors whose close + ** was deferred because of outstanding locks. + */ + struct openCnt *pOpen = id->pOpen; + pOpen->nLock--; + assert( pOpen->nLock>=0 ); + if( pOpen->nLock==0 && pOpen->nPending>0 ){ + int i; + for(i=0; inPending; i++){ + close(pOpen->aPending[i]); + } + sqliteFree(pOpen->aPending); + pOpen->nPending = 0; + pOpen->aPending = 0; + } + } + sqliteOsLeaveMutex(); + id->locked = 0; + return rc; +#endif +#if OS_WIN + int rc; + if( id->locked==0 ){ + rc = SQLITE_OK; + }else if( isNT() || id->locked<0 ){ + UnlockFile(id->h, FIRST_LOCKBYTE+1, 0, N_LOCKBYTE, 0); + rc = SQLITE_OK; + id->locked = 0; + }else{ + UnlockFile(id->h, FIRST_LOCKBYTE+id->locked, 0, 1, 0); + rc = SQLITE_OK; + id->locked = 0; + } + return rc; +#endif +#if OS_MAC + int rc; + ParamBlockRec params; + memset(¶ms, 0, sizeof(params)); + params.ioParam.ioRefNum = id->refNumRF; + params.ioParam.ioPosMode = fsFromStart; + if( id->locked==0 || id->refNumRF == -1 ){ + rc = SQLITE_OK; + }else if( id->locked<0 ){ + params.ioParam.ioPosOffset = FIRST_LOCKBYTE+1; + params.ioParam.ioReqCount = N_LOCKBYTE; + PBUnlockRangeSync(¶ms); + rc = SQLITE_OK; + id->locked = 0; + }else{ + params.ioParam.ioPosOffset = FIRST_LOCKBYTE+id->locked; + params.ioParam.ioReqCount = 1; + PBUnlockRangeSync(¶ms); + rc = SQLITE_OK; + id->locked = 0; + } + return rc; +#endif +} + +/* +** Get information to seed the random number generator. The seed +** is written into the buffer zBuf[256]. The calling function must +** supply a sufficiently large buffer. +*/ +int sqliteOsRandomSeed(char *zBuf){ + /* We have to initialize zBuf to prevent valgrind from reporting + ** errors. The reports issued by valgrind are incorrect - we would + ** prefer that the randomness be increased by making use of the + ** uninitialized space in zBuf - but valgrind errors tend to worry + ** some users. Rather than argue, it seems easier just to initialize + ** the whole array and silence valgrind, even if that means less randomness + ** in the random seed. + ** + ** When testing, initializing zBuf[] to zero is all we do. That means + ** that we always use the same random number sequence.* This makes the + ** tests repeatable. + */ + memset(zBuf, 0, 256); +#if OS_UNIX && !defined(SQLITE_TEST) + { + int pid; + time((time_t*)zBuf); + pid = getpid(); + memcpy(&zBuf[sizeof(time_t)], &pid, sizeof(pid)); + } +#endif +#if OS_WIN && !defined(SQLITE_TEST) + GetSystemTime((LPSYSTEMTIME)zBuf); +#endif +#if OS_MAC + { + int pid; + Microseconds((UnsignedWide*)zBuf); + pid = getpid(); + memcpy(&zBuf[sizeof(UnsignedWide)], &pid, sizeof(pid)); + } +#endif + return SQLITE_OK; +} + +/* +** Sleep for a little while. Return the amount of time slept. +*/ +int sqliteOsSleep(int ms){ +#if OS_UNIX +#if defined(HAVE_USLEEP) && HAVE_USLEEP + usleep(ms*1000); + return ms; +#else + sleep((ms+999)/1000); + return 1000*((ms+999)/1000); +#endif +#endif +#if OS_WIN + Sleep(ms); + return ms; +#endif +#if OS_MAC + UInt32 finalTicks; + UInt32 ticks = (((UInt32)ms+16)*3)/50; /* 1/60 sec per tick */ + Delay(ticks, &finalTicks); + return (int)((ticks*50)/3); +#endif +} + +/* +** Static variables used for thread synchronization +*/ +static int inMutex = 0; +#ifdef SQLITE_UNIX_THREADS + static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +#endif +#ifdef SQLITE_W32_THREADS + static CRITICAL_SECTION cs; +#endif +#ifdef SQLITE_MACOS_MULTITASKING + static MPCriticalRegionID criticalRegion; +#endif + +/* +** The following pair of routine implement mutual exclusion for +** multi-threaded processes. Only a single thread is allowed to +** executed code that is surrounded by EnterMutex() and LeaveMutex(). +** +** SQLite uses only a single Mutex. There is not much critical +** code and what little there is executes quickly and without blocking. +*/ +void sqliteOsEnterMutex(){ +#ifdef SQLITE_UNIX_THREADS + pthread_mutex_lock(&mutex); +#endif +#ifdef SQLITE_W32_THREADS + static int isInit = 0; + while( !isInit ){ + static long lock = 0; + if( InterlockedIncrement(&lock)==1 ){ + InitializeCriticalSection(&cs); + isInit = 1; + }else{ + Sleep(1); + } + } + EnterCriticalSection(&cs); +#endif +#ifdef SQLITE_MACOS_MULTITASKING + static volatile int notInit = 1; + if( notInit ){ + if( notInit == 2 ) /* as close as you can get to thread safe init */ + MPYield(); + else{ + notInit = 2; + MPCreateCriticalRegion(&criticalRegion); + notInit = 0; + } + } + MPEnterCriticalRegion(criticalRegion, kDurationForever); +#endif + assert( !inMutex ); + inMutex = 1; +} +void sqliteOsLeaveMutex(){ + assert( inMutex ); + inMutex = 0; +#ifdef SQLITE_UNIX_THREADS + pthread_mutex_unlock(&mutex); +#endif +#ifdef SQLITE_W32_THREADS + LeaveCriticalSection(&cs); +#endif +#ifdef SQLITE_MACOS_MULTITASKING + MPExitCriticalRegion(criticalRegion); +#endif +} + +/* +** Turn a relative pathname into a full pathname. Return a pointer +** to the full pathname stored in space obtained from sqliteMalloc(). +** The calling function is responsible for freeing this space once it +** is no longer needed. +*/ +char *sqliteOsFullPathname(const char *zRelative){ +#if OS_UNIX + char *zFull = 0; + if( zRelative[0]=='/' ){ + sqliteSetString(&zFull, zRelative, (char*)0); + }else{ + char zBuf[5000]; + zBuf[0] = 0; + sqliteSetString(&zFull, getcwd(zBuf, sizeof(zBuf)), "/", zRelative, + (char*)0); + } + return zFull; +#endif +#if OS_WIN + char *zNotUsed; + char *zFull; + int nByte; + nByte = GetFullPathName(zRelative, 0, 0, &zNotUsed) + 1; + zFull = sqliteMalloc( nByte ); + if( zFull==0 ) return 0; + GetFullPathName(zRelative, nByte, zFull, &zNotUsed); + return zFull; +#endif +#if OS_MAC + char *zFull = 0; + if( zRelative[0]==':' ){ + char zBuf[_MAX_PATH+1]; + sqliteSetString(&zFull, getcwd(zBuf, sizeof(zBuf)), &(zRelative[1]), + (char*)0); + }else{ + if( strchr(zRelative, ':') ){ + sqliteSetString(&zFull, zRelative, (char*)0); + }else{ + char zBuf[_MAX_PATH+1]; + sqliteSetString(&zFull, getcwd(zBuf, sizeof(zBuf)), zRelative, (char*)0); + } + } + return zFull; +#endif +} + +/* +** The following variable, if set to a non-zero value, becomes the result +** returned from sqliteOsCurrentTime(). This is used for testing. +*/ +#ifdef SQLITE_TEST +int sqlite_current_time = 0; +#endif + +/* +** Find the current time (in Universal Coordinated Time). Write the +** current time and date as a Julian Day number into *prNow and +** return 0. Return 1 if the time and date cannot be found. +*/ +int sqliteOsCurrentTime(double *prNow){ +#if OS_UNIX + time_t t; + time(&t); + *prNow = t/86400.0 + 2440587.5; +#endif +#if OS_WIN + FILETIME ft; + /* FILETIME structure is a 64-bit value representing the number of + 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5). + */ + double now; + GetSystemTimeAsFileTime( &ft ); + now = ((double)ft.dwHighDateTime) * 4294967296.0; + *prNow = (now + ft.dwLowDateTime)/864000000000.0 + 2305813.5; +#endif +#ifdef SQLITE_TEST + if( sqlite_current_time ){ + *prNow = sqlite_current_time/86400.0 + 2440587.5; + } +#endif + return 0; +} diff --git a/src/libs/sqlite2/os.h b/src/libs/sqlite2/os.h new file mode 100644 index 00000000..d11198c9 --- /dev/null +++ b/src/libs/sqlite2/os.h @@ -0,0 +1,191 @@ +/* +** 2001 September 16 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This header file (together with is companion C source-code file +** "os.c") attempt to abstract the underlying operating system so that +** the SQLite library will work on both POSIX and windows systems. +*/ +#ifndef _SQLITE_OS_H_ +#define _SQLITE_OS_H_ + +/* +** Helpful hint: To get this to compile on HP/UX, add -D_INCLUDE_POSIX_SOURCE +** to the compiler command line. +*/ + +/* +** These #defines should enable >2GB file support on Posix if the +** underlying operating system supports it. If the OS lacks +** large file support, or if the OS is windows, these should be no-ops. +** +** Large file support can be disabled using the -DSQLITE_DISABLE_LFS switch +** on the compiler command line. This is necessary if you are compiling +** on a recent machine (ex: RedHat 7.2) but you want your code to work +** on an older machine (ex: RedHat 6.0). If you compile on RedHat 7.2 +** without this option, LFS is enable. But LFS does not exist in the kernel +** in RedHat 6.0, so the code won't work. Hence, for maximum binary +** portability you should omit LFS. +** +** Similar is true for MacOS. LFS is only supported on MacOS 9 and later. +*/ +#ifndef SQLITE_DISABLE_LFS +# define _LARGE_FILE 1 +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# define _LARGEFILE_SOURCE 1 +#endif + +/* +** Temporary files are named starting with this prefix followed by 16 random +** alphanumeric characters, and no file extension. They are stored in the +** OS's standard temporary file directory, and are deleted prior to exit. +** If sqlite is being embedded in another program, you may wish to change the +** prefix to reflect your program's name, so that if your program exits +** prematurely, old temporary files can be easily identified. This can be done +** using -DTEMP_FILE_PREFIX=myprefix_ on the compiler command line. +*/ +#ifndef TEMP_FILE_PREFIX +# define TEMP_FILE_PREFIX "sqlite_" +#endif + +/* +** Figure out if we are dealing with Unix, Windows or MacOS. +** +** N.B. MacOS means Mac Classic (or Carbon). Treat Darwin (OS X) as Unix. +** The MacOS build is designed to use CodeWarrior (tested with v8) +*/ +#ifndef OS_UNIX +# ifndef OS_WIN +# ifndef OS_MAC +# if defined(__MACOS__) +# define OS_MAC 1 +# define OS_WIN 0 +# define OS_UNIX 0 +# elif defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) +# define OS_MAC 0 +# define OS_WIN 1 +# define OS_UNIX 0 +# else +# define OS_MAC 0 +# define OS_WIN 0 +# define OS_UNIX 1 +# endif +# else +# define OS_WIN 0 +# define OS_UNIX 0 +# endif +# else +# define OS_MAC 0 +# define OS_UNIX 0 +# endif +#else +# define OS_MAC 0 +# ifndef OS_WIN +# define OS_WIN 0 +# endif +#endif + +/* +** A handle for an open file is stored in an OsFile object. +*/ +#if OS_UNIX +# include +# include +# include +# include + typedef struct OsFile OsFile; + struct OsFile { + struct openCnt *pOpen; /* Info about all open fd's on this inode */ + struct lockInfo *pLock; /* Info about locks on this inode */ + int fd; /* The file descriptor */ + int locked; /* True if this instance holds the lock */ + int dirfd; /* File descriptor for the directory */ + }; +# define SQLITE_TEMPNAME_SIZE 200 +# if defined(HAVE_USLEEP) && HAVE_USLEEP +# define SQLITE_MIN_SLEEP_MS 1 +# else +# define SQLITE_MIN_SLEEP_MS 1000 +# endif +#endif + +#if OS_WIN +#include +#include + typedef struct OsFile OsFile; + struct OsFile { + HANDLE h; /* Handle for accessing the file */ + int locked; /* 0: unlocked, <0: write lock, >0: read lock */ + }; +# if defined(_MSC_VER) + typedef __int64 off_t; +# else +# if !defined(_CYGWIN_TYPES_H) + typedef long long off_t; +# if defined(__MINGW32__) +# define _OFF_T_ +# endif +# endif +# endif +# define SQLITE_TEMPNAME_SIZE (MAX_PATH+50) +# define SQLITE_MIN_SLEEP_MS 1 +#endif + +#if OS_MAC +# include +# include + typedef struct OsFile OsFile; + struct OsFile { + SInt16 refNum; /* Data fork/file reference number */ + SInt16 refNumRF; /* Resource fork reference number (for locking) */ + int locked; /* 0: unlocked, <0: write lock, >0: read lock */ + int delOnClose; /* True if file is to be deleted on close */ + char *pathToDel; /* Name of file to delete on close */ + }; +# ifdef _LARGE_FILE + typedef SInt64 off_t; +# else + typedef SInt32 off_t; +# endif +# define SQLITE_TEMPNAME_SIZE _MAX_PATH +# define SQLITE_MIN_SLEEP_MS 17 +#endif + +int sqliteOsDelete(const char*); +int sqliteOsFileExists(const char*); +int sqliteOsFileRename(const char*, const char*); +int sqliteOsOpenReadWrite(const char*, OsFile*, int*); +int sqliteOsOpenExclusive(const char*, OsFile*, int); +int sqliteOsOpenReadOnly(const char*, OsFile*); +int sqliteOsOpenDirectory(const char*, OsFile*); +int sqliteOsTempFileName(char*); +int sqliteOsClose(OsFile*); +int sqliteOsRead(OsFile*, void*, int amt); +int sqliteOsWrite(OsFile*, const void*, int amt); +int sqliteOsSeek(OsFile*, off_t offset); +int sqliteOsSync(OsFile*); +int sqliteOsTruncate(OsFile*, off_t size); +int sqliteOsFileSize(OsFile*, off_t *pSize); +int sqliteOsReadLock(OsFile*); +int sqliteOsWriteLock(OsFile*); +int sqliteOsUnlock(OsFile*); +int sqliteOsRandomSeed(char*); +int sqliteOsSleep(int ms); +int sqliteOsCurrentTime(double*); +void sqliteOsEnterMutex(void); +void sqliteOsLeaveMutex(void); +char *sqliteOsFullPathname(const char*); + + + +#endif /* _SQLITE_OS_H_ */ diff --git a/src/libs/sqlite2/pager.c b/src/libs/sqlite2/pager.c new file mode 100644 index 00000000..409f9201 --- /dev/null +++ b/src/libs/sqlite2/pager.c @@ -0,0 +1,2220 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the implementation of the page cache subsystem or "pager". +** +** The pager is used to access a database disk file. It implements +** atomic commit and rollback through the use of a journal file that +** is separate from the database file. The pager also implements file +** locking to prevent two processes from writing the same database +** file simultaneously, or one process from reading the database while +** another is writing. +** +** @(#) $Id: pager.c 875429 2008-10-24 12:20:41Z cgilles $ +*/ +#include "os.h" /* Must be first to enable large file support */ +#include "sqliteInt.h" +#include "pager.h" +#include +#include + +/* +** Macros for troubleshooting. Normally turned off +*/ +#if 0 +static Pager *mainPager = 0; +#define SET_PAGER(X) if( mainPager==0 ) mainPager = (X) +#define CLR_PAGER(X) if( mainPager==(X) ) mainPager = 0 +#define TRACE1(X) if( pPager==mainPager ) fprintf(stderr,X) +#define TRACE2(X,Y) if( pPager==mainPager ) fprintf(stderr,X,Y) +#define TRACE3(X,Y,Z) if( pPager==mainPager ) fprintf(stderr,X,Y,Z) +#else +#define SET_PAGER(X) +#define CLR_PAGER(X) +#define TRACE1(X) +#define TRACE2(X,Y) +#define TRACE3(X,Y,Z) +#endif + + +/* +** The page cache as a whole is always in one of the following +** states: +** +** SQLITE_UNLOCK The page cache is not currently reading or +** writing the database file. There is no +** data held in memory. This is the initial +** state. +** +** SQLITE_READLOCK The page cache is reading the database. +** Writing is not permitted. There can be +** multiple readers accessing the same database +** file at the same time. +** +** SQLITE_WRITELOCK The page cache is writing the database. +** Access is exclusive. No other processes or +** threads can be reading or writing while one +** process is writing. +** +** The page cache comes up in SQLITE_UNLOCK. The first time a +** sqlite_page_get() occurs, the state transitions to SQLITE_READLOCK. +** After all pages have been released using sqlite_page_unref(), +** the state transitions back to SQLITE_UNLOCK. The first time +** that sqlite_page_write() is called, the state transitions to +** SQLITE_WRITELOCK. (Note that sqlite_page_write() can only be +** called on an outstanding page which means that the pager must +** be in SQLITE_READLOCK before it transitions to SQLITE_WRITELOCK.) +** The sqlite_page_rollback() and sqlite_page_commit() functions +** transition the state from SQLITE_WRITELOCK back to SQLITE_READLOCK. +*/ +#define SQLITE_UNLOCK 0 +#define SQLITE_READLOCK 1 +#define SQLITE_WRITELOCK 2 + + +/* +** Each in-memory image of a page begins with the following header. +** This header is only visible to this pager module. The client +** code that calls pager sees only the data that follows the header. +** +** Client code should call sqlitepager_write() on a page prior to making +** any modifications to that page. The first time sqlitepager_write() +** is called, the original page contents are written into the rollback +** journal and PgHdr.inJournal and PgHdr.needSync are set. Later, once +** the journal page has made it onto the disk surface, PgHdr.needSync +** is cleared. The modified page cannot be written back into the original +** database file until the journal pages has been synced to disk and the +** PgHdr.needSync has been cleared. +** +** The PgHdr.dirty flag is set when sqlitepager_write() is called and +** is cleared again when the page content is written back to the original +** database file. +*/ +typedef struct PgHdr PgHdr; +struct PgHdr { + Pager *pPager; /* The pager to which this page belongs */ + Pgno pgno; /* The page number for this page */ + PgHdr *pNextHash, *pPrevHash; /* Hash collision chain for PgHdr.pgno */ + int nRef; /* Number of users of this page */ + PgHdr *pNextFree, *pPrevFree; /* Freelist of pages where nRef==0 */ + PgHdr *pNextAll, *pPrevAll; /* A list of all pages */ + PgHdr *pNextCkpt, *pPrevCkpt; /* List of pages in the checkpoint journal */ + u8 inJournal; /* TRUE if has been written to journal */ + u8 inCkpt; /* TRUE if written to the checkpoint journal */ + u8 dirty; /* TRUE if we need to write back changes */ + u8 needSync; /* Sync journal before writing this page */ + u8 alwaysRollback; /* Disable dont_rollback() for this page */ + PgHdr *pDirty; /* Dirty pages sorted by PgHdr.pgno */ + /* SQLITE_PAGE_SIZE bytes of page data follow this header */ + /* Pager.nExtra bytes of local data follow the page data */ +}; + + +/* +** A macro used for invoking the codec if there is one +*/ +#ifdef SQLITE_HAS_CODEC +# define CODEC(P,D,N,X) if( P->xCodec ){ P->xCodec(P->pCodecArg,D,N,X); } +#else +# define CODEC(P,D,N,X) +#endif + +/* +** Convert a pointer to a PgHdr into a pointer to its data +** and back again. +*/ +#define PGHDR_TO_DATA(P) ((void*)(&(P)[1])) +#define DATA_TO_PGHDR(D) (&((PgHdr*)(D))[-1]) +#define PGHDR_TO_EXTRA(P) ((void*)&((char*)(&(P)[1]))[SQLITE_PAGE_SIZE]) + +/* +** How big to make the hash table used for locating in-memory pages +** by page number. +*/ +#define N_PG_HASH 2048 + +/* +** Hash a page number +*/ +#define pager_hash(PN) ((PN)&(N_PG_HASH-1)) + +/* +** A open page cache is an instance of the following structure. +*/ +struct Pager { + char *zFilename; /* Name of the database file */ + char *zJournal; /* Name of the journal file */ + char *zDirectory; /* Directory hold database and journal files */ + OsFile fd, jfd; /* File descriptors for database and journal */ + OsFile cpfd; /* File descriptor for the checkpoint journal */ + int dbSize; /* Number of pages in the file */ + int origDbSize; /* dbSize before the current change */ + int ckptSize; /* Size of database (in pages) at ckpt_begin() */ + off_t ckptJSize; /* Size of journal at ckpt_begin() */ + int nRec; /* Number of pages written to the journal */ + u32 cksumInit; /* Quasi-random value added to every checksum */ + int ckptNRec; /* Number of records in the checkpoint journal */ + int nExtra; /* Add this many bytes to each in-memory page */ + void (*xDestructor)(void*); /* Call this routine when freeing pages */ + int nPage; /* Total number of in-memory pages */ + int nRef; /* Number of in-memory pages with PgHdr.nRef>0 */ + int mxPage; /* Maximum number of pages to hold in cache */ + int nHit, nMiss, nOvfl; /* Cache hits, missing, and LRU overflows */ + void (*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */ + void *pCodecArg; /* First argument to xCodec() */ + u8 journalOpen; /* True if journal file descriptors is valid */ + u8 journalStarted; /* True if header of journal is synced */ + u8 useJournal; /* Use a rollback journal on this file */ + u8 ckptOpen; /* True if the checkpoint journal is open */ + u8 ckptInUse; /* True we are in a checkpoint */ + u8 ckptAutoopen; /* Open ckpt journal when main journal is opened*/ + u8 noSync; /* Do not sync the journal if true */ + u8 fullSync; /* Do extra syncs of the journal for robustness */ + u8 state; /* SQLITE_UNLOCK, _READLOCK or _WRITELOCK */ + u8 errMask; /* One of several kinds of errors */ + u8 tempFile; /* zFilename is a temporary file */ + u8 readOnly; /* True for a read-only database */ + u8 needSync; /* True if an fsync() is needed on the journal */ + u8 dirtyFile; /* True if database file has changed in any way */ + u8 alwaysRollback; /* Disable dont_rollback() for all pages */ + u8 *aInJournal; /* One bit for each page in the database file */ + u8 *aInCkpt; /* One bit for each page in the database */ + PgHdr *pFirst, *pLast; /* List of free pages */ + PgHdr *pFirstSynced; /* First free page with PgHdr.needSync==0 */ + PgHdr *pAll; /* List of all pages */ + PgHdr *pCkpt; /* List of pages in the checkpoint journal */ + PgHdr *aHash[N_PG_HASH]; /* Hash table to map page number of PgHdr */ +}; + +/* +** These are bits that can be set in Pager.errMask. +*/ +#define PAGER_ERR_FULL 0x01 /* a write() failed */ +#define PAGER_ERR_MEM 0x02 /* malloc() failed */ +#define PAGER_ERR_LOCK 0x04 /* error in the locking protocol */ +#define PAGER_ERR_CORRUPT 0x08 /* database or journal corruption */ +#define PAGER_ERR_DISK 0x10 /* general disk I/O error - bad hard drive? */ + +/* +** The journal file contains page records in the following +** format. +** +** Actually, this structure is the complete page record for pager +** formats less than 3. Beginning with format 3, this record is surrounded +** by two checksums. +*/ +typedef struct PageRecord PageRecord; +struct PageRecord { + Pgno pgno; /* The page number */ + char aData[SQLITE_PAGE_SIZE]; /* Original data for page pgno */ +}; + +/* +** Journal files begin with the following magic string. The data +** was obtained from /dev/random. It is used only as a sanity check. +** +** There are three journal formats (so far). The 1st journal format writes +** 32-bit integers in the byte-order of the host machine. New +** formats writes integers as big-endian. All new journals use the +** new format, but we have to be able to read an older journal in order +** to rollback journals created by older versions of the library. +** +** The 3rd journal format (added for 2.8.0) adds additional sanity +** checking information to the journal. If the power fails while the +** journal is being written, semi-random garbage data might appear in +** the journal file after power is restored. If an attempt is then made +** to roll the journal back, the database could be corrupted. The additional +** sanity checking data is an attempt to discover the garbage in the +** journal and ignore it. +** +** The sanity checking information for the 3rd journal format consists +** of a 32-bit checksum on each page of data. The checksum covers both +** the page number and the SQLITE_PAGE_SIZE bytes of data for the page. +** This cksum is initialized to a 32-bit random value that appears in the +** journal file right after the header. The random initializer is important, +** because garbage data that appears at the end of a journal is likely +** data that was once in other files that have now been deleted. If the +** garbage data came from an obsolete journal file, the checksums might +** be correct. But by initializing the checksum to random value which +** is different for every journal, we minimize that risk. +*/ +static const unsigned char aJournalMagic1[] = { + 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd4, +}; +static const unsigned char aJournalMagic2[] = { + 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd5, +}; +static const unsigned char aJournalMagic3[] = { + 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd6, +}; +#define JOURNAL_FORMAT_1 1 +#define JOURNAL_FORMAT_2 2 +#define JOURNAL_FORMAT_3 3 + +/* +** The following integer determines what format to use when creating +** new primary journal files. By default we always use format 3. +** When testing, we can set this value to older journal formats in order to +** make sure that newer versions of the library are able to rollback older +** journal files. +** +** Note that checkpoint journals always use format 2 and omit the header. +*/ +#ifdef SQLITE_TEST +int journal_format = 3; +#else +# define journal_format 3 +#endif + +/* +** The size of the header and of each page in the journal varies according +** to which journal format is being used. The following macros figure out +** the sizes based on format numbers. +*/ +#define JOURNAL_HDR_SZ(X) \ + (sizeof(aJournalMagic1) + sizeof(Pgno) + ((X)>=3)*2*sizeof(u32)) +#define JOURNAL_PG_SZ(X) \ + (SQLITE_PAGE_SIZE + sizeof(Pgno) + ((X)>=3)*sizeof(u32)) + +/* +** Enable reference count tracking here: +*/ +#ifdef SQLITE_TEST + int pager_refinfo_enable = 0; + static void pager_refinfo(PgHdr *p){ + static int cnt = 0; + if( !pager_refinfo_enable ) return; + printf( + "REFCNT: %4d addr=0x%08x nRef=%d\n", + p->pgno, (int)PGHDR_TO_DATA(p), p->nRef + ); + cnt++; /* Something to set a breakpoint on */ + } +# define REFINFO(X) pager_refinfo(X) +#else +# define REFINFO(X) +#endif + +/* +** Read a 32-bit integer from the given file descriptor. Store the integer +** that is read in *pRes. Return SQLITE_OK if everything worked, or an +** error code is something goes wrong. +** +** If the journal format is 2 or 3, read a big-endian integer. If the +** journal format is 1, read an integer in the native byte-order of the +** host machine. +*/ +static int read32bits(int format, OsFile *fd, u32 *pRes){ + u32 res; + int rc; + rc = sqliteOsRead(fd, &res, sizeof(res)); + if( rc==SQLITE_OK && format>JOURNAL_FORMAT_1 ){ + unsigned char ac[4]; + memcpy(ac, &res, 4); + res = (ac[0]<<24) | (ac[1]<<16) | (ac[2]<<8) | ac[3]; + } + *pRes = res; + return rc; +} + +/* +** Write a 32-bit integer into the given file descriptor. Return SQLITE_OK +** on success or an error code is something goes wrong. +** +** If the journal format is 2 or 3, write the integer as 4 big-endian +** bytes. If the journal format is 1, write the integer in the native +** byte order. In normal operation, only formats 2 and 3 are used. +** Journal format 1 is only used for testing. +*/ +static int write32bits(OsFile *fd, u32 val){ + unsigned char ac[4]; + if( journal_format<=1 ){ + return sqliteOsWrite(fd, &val, 4); + } + ac[0] = (val>>24) & 0xff; + ac[1] = (val>>16) & 0xff; + ac[2] = (val>>8) & 0xff; + ac[3] = val & 0xff; + return sqliteOsWrite(fd, ac, 4); +} + +/* +** Write a 32-bit integer into a page header right before the +** page data. This will overwrite the PgHdr.pDirty pointer. +** +** The integer is big-endian for formats 2 and 3 and native byte order +** for journal format 1. +*/ +static void store32bits(u32 val, PgHdr *p, int offset){ + unsigned char *ac; + ac = &((unsigned char*)PGHDR_TO_DATA(p))[offset]; + if( journal_format<=1 ){ + memcpy(ac, &val, 4); + }else{ + ac[0] = (val>>24) & 0xff; + ac[1] = (val>>16) & 0xff; + ac[2] = (val>>8) & 0xff; + ac[3] = val & 0xff; + } +} + + +/* +** Convert the bits in the pPager->errMask into an approprate +** return code. +*/ +static int pager_errcode(Pager *pPager){ + int rc = SQLITE_OK; + if( pPager->errMask & PAGER_ERR_LOCK ) rc = SQLITE_PROTOCOL; + if( pPager->errMask & PAGER_ERR_DISK ) rc = SQLITE_IOERR; + if( pPager->errMask & PAGER_ERR_FULL ) rc = SQLITE_FULL; + if( pPager->errMask & PAGER_ERR_MEM ) rc = SQLITE_NOMEM; + if( pPager->errMask & PAGER_ERR_CORRUPT ) rc = SQLITE_CORRUPT; + return rc; +} + +/* +** Add or remove a page from the list of all pages that are in the +** checkpoint journal. +** +** The Pager keeps a separate list of pages that are currently in +** the checkpoint journal. This helps the sqlitepager_ckpt_commit() +** routine run MUCH faster for the common case where there are many +** pages in memory but only a few are in the checkpoint journal. +*/ +static void page_add_to_ckpt_list(PgHdr *pPg){ + Pager *pPager = pPg->pPager; + if( pPg->inCkpt ) return; + assert( pPg->pPrevCkpt==0 && pPg->pNextCkpt==0 ); + pPg->pPrevCkpt = 0; + if( pPager->pCkpt ){ + pPager->pCkpt->pPrevCkpt = pPg; + } + pPg->pNextCkpt = pPager->pCkpt; + pPager->pCkpt = pPg; + pPg->inCkpt = 1; +} +static void page_remove_from_ckpt_list(PgHdr *pPg){ + if( !pPg->inCkpt ) return; + if( pPg->pPrevCkpt ){ + assert( pPg->pPrevCkpt->pNextCkpt==pPg ); + pPg->pPrevCkpt->pNextCkpt = pPg->pNextCkpt; + }else{ + assert( pPg->pPager->pCkpt==pPg ); + pPg->pPager->pCkpt = pPg->pNextCkpt; + } + if( pPg->pNextCkpt ){ + assert( pPg->pNextCkpt->pPrevCkpt==pPg ); + pPg->pNextCkpt->pPrevCkpt = pPg->pPrevCkpt; + } + pPg->pNextCkpt = 0; + pPg->pPrevCkpt = 0; + pPg->inCkpt = 0; +} + +/* +** Find a page in the hash table given its page number. Return +** a pointer to the page or NULL if not found. +*/ +static PgHdr *pager_lookup(Pager *pPager, Pgno pgno){ + PgHdr *p = pPager->aHash[pager_hash(pgno)]; + while( p && p->pgno!=pgno ){ + p = p->pNextHash; + } + return p; +} + +/* +** Unlock the database and clear the in-memory cache. This routine +** sets the state of the pager back to what it was when it was first +** opened. Any outstanding pages are invalidated and subsequent attempts +** to access those pages will likely result in a coredump. +*/ +static void pager_reset(Pager *pPager){ + PgHdr *pPg, *pNext; + for(pPg=pPager->pAll; pPg; pPg=pNext){ + pNext = pPg->pNextAll; + sqliteFree(pPg); + } + pPager->pFirst = 0; + pPager->pFirstSynced = 0; + pPager->pLast = 0; + pPager->pAll = 0; + memset(pPager->aHash, 0, sizeof(pPager->aHash)); + pPager->nPage = 0; + if( pPager->state>=SQLITE_WRITELOCK ){ + sqlitepager_rollback(pPager); + } + sqliteOsUnlock(&pPager->fd); + pPager->state = SQLITE_UNLOCK; + pPager->dbSize = -1; + pPager->nRef = 0; + assert( pPager->journalOpen==0 ); +} + +/* +** When this routine is called, the pager has the journal file open and +** a write lock on the database. This routine releases the database +** write lock and acquires a read lock in its place. The journal file +** is deleted and closed. +** +** TODO: Consider keeping the journal file open for temporary databases. +** This might give a performance improvement on windows where opening +** a file is an expensive operation. +*/ +static int pager_unwritelock(Pager *pPager){ + int rc; + PgHdr *pPg; + if( pPager->stateckptOpen ){ + sqliteOsClose(&pPager->cpfd); + pPager->ckptOpen = 0; + } + if( pPager->journalOpen ){ + sqliteOsClose(&pPager->jfd); + pPager->journalOpen = 0; + sqliteOsDelete(pPager->zJournal); + sqliteFree( pPager->aInJournal ); + pPager->aInJournal = 0; + for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ + pPg->inJournal = 0; + pPg->dirty = 0; + pPg->needSync = 0; + } + }else{ + assert( pPager->dirtyFile==0 || pPager->useJournal==0 ); + } + rc = sqliteOsReadLock(&pPager->fd); + if( rc==SQLITE_OK ){ + pPager->state = SQLITE_READLOCK; + }else{ + /* This can only happen if a process does a BEGIN, then forks and the + ** child process does the COMMIT. Because of the semantics of unix + ** file locking, the unlock will fail. + */ + pPager->state = SQLITE_UNLOCK; + } + return rc; +} + +/* +** Compute and return a checksum for the page of data. +** +** This is not a real checksum. It is really just the sum of the +** random initial value and the page number. We considered do a checksum +** of the database, but that was found to be too slow. +*/ +static u32 pager_cksum(Pager *pPager, Pgno pgno, const char *aData){ + u32 cksum = pPager->cksumInit + pgno; + return cksum; +} + +/* +** Read a single page from the journal file opened on file descriptor +** jfd. Playback this one page. +** +** There are three different journal formats. The format parameter determines +** which format is used by the journal that is played back. +*/ +static int pager_playback_one_page(Pager *pPager, OsFile *jfd, int format){ + int rc; + PgHdr *pPg; /* An existing page in the cache */ + PageRecord pgRec; + u32 cksum; + + rc = read32bits(format, jfd, &pgRec.pgno); + if( rc!=SQLITE_OK ) return rc; + rc = sqliteOsRead(jfd, &pgRec.aData, sizeof(pgRec.aData)); + if( rc!=SQLITE_OK ) return rc; + + /* Sanity checking on the page. This is more important that I originally + ** thought. If a power failure occurs while the journal is being written, + ** it could cause invalid data to be written into the journal. We need to + ** detect this invalid data (with high probability) and ignore it. + */ + if( pgRec.pgno==0 ){ + return SQLITE_DONE; + } + if( pgRec.pgno>(unsigned)pPager->dbSize ){ + return SQLITE_OK; + } + if( format>=JOURNAL_FORMAT_3 ){ + rc = read32bits(format, jfd, &cksum); + if( rc ) return rc; + if( pager_cksum(pPager, pgRec.pgno, pgRec.aData)!=cksum ){ + return SQLITE_DONE; + } + } + + /* Playback the page. Update the in-memory copy of the page + ** at the same time, if there is one. + */ + pPg = pager_lookup(pPager, pgRec.pgno); + TRACE2("PLAYBACK %d\n", pgRec.pgno); + sqliteOsSeek(&pPager->fd, (pgRec.pgno-1)*(off_t)SQLITE_PAGE_SIZE); + rc = sqliteOsWrite(&pPager->fd, pgRec.aData, SQLITE_PAGE_SIZE); + if( pPg ){ + /* No page should ever be rolled back that is in use, except for page + ** 1 which is held in use in order to keep the lock on the database + ** active. + */ + assert( pPg->nRef==0 || pPg->pgno==1 ); + memcpy(PGHDR_TO_DATA(pPg), pgRec.aData, SQLITE_PAGE_SIZE); + memset(PGHDR_TO_EXTRA(pPg), 0, pPager->nExtra); + pPg->dirty = 0; + pPg->needSync = 0; + CODEC(pPager, PGHDR_TO_DATA(pPg), pPg->pgno, 3); + } + return rc; +} + +/* +** Playback the journal and thus restore the database file to +** the state it was in before we started making changes. +** +** The journal file format is as follows: +** +** * 8 byte prefix. One of the aJournalMagic123 vectors defined +** above. The format of the journal file is determined by which +** of the three prefix vectors is seen. +** * 4 byte big-endian integer which is the number of valid page records +** in the journal. If this value is 0xffffffff, then compute the +** number of page records from the journal size. This field appears +** in format 3 only. +** * 4 byte big-endian integer which is the initial value for the +** sanity checksum. This field appears in format 3 only. +** * 4 byte integer which is the number of pages to truncate the +** database to during a rollback. +** * Zero or more pages instances, each as follows: +** + 4 byte page number. +** + SQLITE_PAGE_SIZE bytes of data. +** + 4 byte checksum (format 3 only) +** +** When we speak of the journal header, we mean the first 4 bullets above. +** Each entry in the journal is an instance of the 5th bullet. Note that +** bullets 2 and 3 only appear in format-3 journals. +** +** Call the value from the second bullet "nRec". nRec is the number of +** valid page entries in the journal. In most cases, you can compute the +** value of nRec from the size of the journal file. But if a power +** failure occurred while the journal was being written, it could be the +** case that the size of the journal file had already been increased but +** the extra entries had not yet made it safely to disk. In such a case, +** the value of nRec computed from the file size would be too large. For +** that reason, we always use the nRec value in the header. +** +** If the nRec value is 0xffffffff it means that nRec should be computed +** from the file size. This value is used when the user selects the +** no-sync option for the journal. A power failure could lead to corruption +** in this case. But for things like temporary table (which will be +** deleted when the power is restored) we don't care. +** +** Journal formats 1 and 2 do not have an nRec value in the header so we +** have to compute nRec from the file size. This has risks (as described +** above) which is why all persistent tables have been changed to use +** format 3. +** +** If the file opened as the journal file is not a well-formed +** journal file then the database will likely already be +** corrupted, so the PAGER_ERR_CORRUPT bit is set in pPager->errMask +** and SQLITE_CORRUPT is returned. If it all works, then this routine +** returns SQLITE_OK. +*/ +static int pager_playback(Pager *pPager, int useJournalSize){ + off_t szJ; /* Size of the journal file in bytes */ + int nRec; /* Number of Records in the journal */ + int i; /* Loop counter */ + Pgno mxPg = 0; /* Size of the original file in pages */ + int format; /* Format of the journal file. */ + unsigned char aMagic[sizeof(aJournalMagic1)]; + int rc; + + /* Figure out how many records are in the journal. Abort early if + ** the journal is empty. + */ + assert( pPager->journalOpen ); + sqliteOsSeek(&pPager->jfd, 0); + rc = sqliteOsFileSize(&pPager->jfd, &szJ); + if( rc!=SQLITE_OK ){ + goto end_playback; + } + + /* If the journal file is too small to contain a complete header, + ** it must mean that the process that created the journal was just + ** beginning to write the journal file when it died. In that case, + ** the database file should have still been completely unchanged. + ** Nothing needs to be rolled back. We can safely ignore this journal. + */ + if( szJ < sizeof(aMagic)+sizeof(Pgno) ){ + goto end_playback; + } + + /* Read the beginning of the journal and truncate the + ** database file back to its original size. + */ + rc = sqliteOsRead(&pPager->jfd, aMagic, sizeof(aMagic)); + if( rc!=SQLITE_OK ){ + rc = SQLITE_PROTOCOL; + goto end_playback; + } + if( memcmp(aMagic, aJournalMagic3, sizeof(aMagic))==0 ){ + format = JOURNAL_FORMAT_3; + }else if( memcmp(aMagic, aJournalMagic2, sizeof(aMagic))==0 ){ + format = JOURNAL_FORMAT_2; + }else if( memcmp(aMagic, aJournalMagic1, sizeof(aMagic))==0 ){ + format = JOURNAL_FORMAT_1; + }else{ + rc = SQLITE_PROTOCOL; + goto end_playback; + } + if( format>=JOURNAL_FORMAT_3 ){ + if( szJ < sizeof(aMagic) + 3*sizeof(u32) ){ + /* Ignore the journal if it is too small to contain a complete + ** header. We already did this test once above, but at the prior + ** test, we did not know the journal format and so we had to assume + ** the smallest possible header. Now we know the header is bigger + ** than the minimum so we test again. + */ + goto end_playback; + } + rc = read32bits(format, &pPager->jfd, (u32*)&nRec); + if( rc ) goto end_playback; + rc = read32bits(format, &pPager->jfd, &pPager->cksumInit); + if( rc ) goto end_playback; + if( nRec==0xffffffff || useJournalSize ){ + nRec = (szJ - JOURNAL_HDR_SZ(3))/JOURNAL_PG_SZ(3); + } + }else{ + nRec = (szJ - JOURNAL_HDR_SZ(2))/JOURNAL_PG_SZ(2); + assert( nRec*JOURNAL_PG_SZ(2)+JOURNAL_HDR_SZ(2)==szJ ); + } + rc = read32bits(format, &pPager->jfd, &mxPg); + if( rc!=SQLITE_OK ){ + goto end_playback; + } + assert( pPager->origDbSize==0 || pPager->origDbSize==mxPg ); + rc = sqliteOsTruncate(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)mxPg); + if( rc!=SQLITE_OK ){ + goto end_playback; + } + pPager->dbSize = mxPg; + + /* Copy original pages out of the journal and back into the database file. + */ + for(i=0; ijfd, format); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + } + break; + } + } + + /* Pages that have been written to the journal but never synced + ** where not restored by the loop above. We have to restore those + ** pages by reading them back from the original database. + */ + if( rc==SQLITE_OK ){ + PgHdr *pPg; + for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ + char zBuf[SQLITE_PAGE_SIZE]; + if( !pPg->dirty ) continue; + if( (int)pPg->pgno <= pPager->origDbSize ){ + sqliteOsSeek(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)(pPg->pgno-1)); + rc = sqliteOsRead(&pPager->fd, zBuf, SQLITE_PAGE_SIZE); + TRACE2("REFETCH %d\n", pPg->pgno); + CODEC(pPager, zBuf, pPg->pgno, 2); + if( rc ) break; + }else{ + memset(zBuf, 0, SQLITE_PAGE_SIZE); + } + if( pPg->nRef==0 || memcmp(zBuf, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE) ){ + memcpy(PGHDR_TO_DATA(pPg), zBuf, SQLITE_PAGE_SIZE); + memset(PGHDR_TO_EXTRA(pPg), 0, pPager->nExtra); + } + pPg->needSync = 0; + pPg->dirty = 0; + } + } + +end_playback: + if( rc!=SQLITE_OK ){ + pager_unwritelock(pPager); + pPager->errMask |= PAGER_ERR_CORRUPT; + rc = SQLITE_CORRUPT; + }else{ + rc = pager_unwritelock(pPager); + } + return rc; +} + +/* +** Playback the checkpoint journal. +** +** This is similar to playing back the transaction journal but with +** a few extra twists. +** +** (1) The number of pages in the database file at the start of +** the checkpoint is stored in pPager->ckptSize, not in the +** journal file itself. +** +** (2) In addition to playing back the checkpoint journal, also +** playback all pages of the transaction journal beginning +** at offset pPager->ckptJSize. +*/ +static int pager_ckpt_playback(Pager *pPager){ + off_t szJ; /* Size of the full journal */ + int nRec; /* Number of Records */ + int i; /* Loop counter */ + int rc; + + /* Truncate the database back to its original size. + */ + rc = sqliteOsTruncate(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)pPager->ckptSize); + pPager->dbSize = pPager->ckptSize; + + /* Figure out how many records are in the checkpoint journal. + */ + assert( pPager->ckptInUse && pPager->journalOpen ); + sqliteOsSeek(&pPager->cpfd, 0); + nRec = pPager->ckptNRec; + + /* Copy original pages out of the checkpoint journal and back into the + ** database file. Note that the checkpoint journal always uses format + ** 2 instead of format 3 since it does not need to be concerned with + ** power failures corrupting the journal and can thus omit the checksums. + */ + for(i=nRec-1; i>=0; i--){ + rc = pager_playback_one_page(pPager, &pPager->cpfd, 2); + assert( rc!=SQLITE_DONE ); + if( rc!=SQLITE_OK ) goto end_ckpt_playback; + } + + /* Figure out how many pages need to be copied out of the transaction + ** journal. + */ + rc = sqliteOsSeek(&pPager->jfd, pPager->ckptJSize); + if( rc!=SQLITE_OK ){ + goto end_ckpt_playback; + } + rc = sqliteOsFileSize(&pPager->jfd, &szJ); + if( rc!=SQLITE_OK ){ + goto end_ckpt_playback; + } + nRec = (szJ - pPager->ckptJSize)/JOURNAL_PG_SZ(journal_format); + for(i=nRec-1; i>=0; i--){ + rc = pager_playback_one_page(pPager, &pPager->jfd, journal_format); + if( rc!=SQLITE_OK ){ + assert( rc!=SQLITE_DONE ); + goto end_ckpt_playback; + } + } + +end_ckpt_playback: + if( rc!=SQLITE_OK ){ + pPager->errMask |= PAGER_ERR_CORRUPT; + rc = SQLITE_CORRUPT; + } + return rc; +} + +/* +** Change the maximum number of in-memory pages that are allowed. +** +** The maximum number is the absolute value of the mxPage parameter. +** If mxPage is negative, the noSync flag is also set. noSync bypasses +** calls to sqliteOsSync(). The pager runs much faster with noSync on, +** but if the operating system crashes or there is an abrupt power +** failure, the database file might be left in an inconsistent and +** unrepairable state. +*/ +void sqlitepager_set_cachesize(Pager *pPager, int mxPage){ + if( mxPage>=0 ){ + pPager->noSync = pPager->tempFile; + if( pPager->noSync==0 ) pPager->needSync = 0; + }else{ + pPager->noSync = 1; + mxPage = -mxPage; + } + if( mxPage>10 ){ + pPager->mxPage = mxPage; + } +} + +/* +** Adjust the robustness of the database to damage due to OS crashes +** or power failures by changing the number of syncs()s when writing +** the rollback journal. There are three levels: +** +** OFF sqliteOsSync() is never called. This is the default +** for temporary and transient files. +** +** NORMAL The journal is synced once before writes begin on the +** database. This is normally adequate protection, but +** it is theoretically possible, though very unlikely, +** that an inopertune power failure could leave the journal +** in a state which would cause damage to the database +** when it is rolled back. +** +** FULL The journal is synced twice before writes begin on the +** database (with some additional information - the nRec field +** of the journal header - being written in between the two +** syncs). If we assume that writing a +** single disk sector is atomic, then this mode provides +** assurance that the journal will not be corrupted to the +** point of causing damage to the database during rollback. +** +** Numeric values associated with these states are OFF==1, NORMAL=2, +** and FULL=3. +*/ +void sqlitepager_set_safety_level(Pager *pPager, int level){ + pPager->noSync = level==1 || pPager->tempFile; + pPager->fullSync = level==3 && !pPager->tempFile; + if( pPager->noSync==0 ) pPager->needSync = 0; +} + +/* +** Open a temporary file. Write the name of the file into zName +** (zName must be at least SQLITE_TEMPNAME_SIZE bytes long.) Write +** the file descriptor into *fd. Return SQLITE_OK on success or some +** other error code if we fail. +** +** The OS will automatically delete the temporary file when it is +** closed. +*/ +static int sqlitepager_opentemp(char *zFile, OsFile *fd){ + int cnt = 8; + int rc; + do{ + cnt--; + sqliteOsTempFileName(zFile); + rc = sqliteOsOpenExclusive(zFile, fd, 1); + }while( cnt>0 && rc!=SQLITE_OK ); + return rc; +} + +/* +** Create a new page cache and put a pointer to the page cache in *ppPager. +** The file to be cached need not exist. The file is not locked until +** the first call to sqlitepager_get() and is only held open until the +** last page is released using sqlitepager_unref(). +** +** If zFilename is NULL then a randomly-named temporary file is created +** and used as the file to be cached. The file will be deleted +** automatically when it is closed. +*/ +int sqlitepager_open( + Pager **ppPager, /* Return the Pager structure here */ + const char *zFilename, /* Name of the database file to open */ + int mxPage, /* Max number of in-memory cache pages */ + int nExtra, /* Extra bytes append to each in-memory page */ + int useJournal /* TRUE to use a rollback journal on this file */ +){ + Pager *pPager; + char *zFullPathname; + int nameLen; + OsFile fd; + int rc, i; + int tempFile; + int readOnly = 0; + char zTemp[SQLITE_TEMPNAME_SIZE]; + + *ppPager = 0; + if( sqlite_malloc_failed ){ + return SQLITE_NOMEM; + } + if( zFilename && zFilename[0] ){ + zFullPathname = sqliteOsFullPathname(zFilename); + rc = sqliteOsOpenReadWrite(zFullPathname, &fd, &readOnly); + tempFile = 0; + }else{ + rc = sqlitepager_opentemp(zTemp, &fd); + zFilename = zTemp; + zFullPathname = sqliteOsFullPathname(zFilename); + tempFile = 1; + } + if( sqlite_malloc_failed ){ + return SQLITE_NOMEM; + } + if( rc!=SQLITE_OK ){ + sqliteFree(zFullPathname); + return SQLITE_CANTOPEN; + } + nameLen = strlen(zFullPathname); + pPager = sqliteMalloc( sizeof(*pPager) + nameLen*3 + 30 ); + if( pPager==0 ){ + sqliteOsClose(&fd); + sqliteFree(zFullPathname); + return SQLITE_NOMEM; + } + SET_PAGER(pPager); + pPager->zFilename = (char*)&pPager[1]; + pPager->zDirectory = &pPager->zFilename[nameLen+1]; + pPager->zJournal = &pPager->zDirectory[nameLen+1]; + strcpy(pPager->zFilename, zFullPathname); + strcpy(pPager->zDirectory, zFullPathname); + for(i=nameLen; i>0 && pPager->zDirectory[i-1]!='/'; i--){} + if( i>0 ) pPager->zDirectory[i-1] = 0; + strcpy(pPager->zJournal, zFullPathname); + sqliteFree(zFullPathname); + strcpy(&pPager->zJournal[nameLen], "-journal"); + pPager->fd = fd; + pPager->journalOpen = 0; + pPager->useJournal = useJournal; + pPager->ckptOpen = 0; + pPager->ckptInUse = 0; + pPager->nRef = 0; + pPager->dbSize = -1; + pPager->ckptSize = 0; + pPager->ckptJSize = 0; + pPager->nPage = 0; + pPager->mxPage = mxPage>5 ? mxPage : 10; + pPager->state = SQLITE_UNLOCK; + pPager->errMask = 0; + pPager->tempFile = tempFile; + pPager->readOnly = readOnly; + pPager->needSync = 0; + pPager->noSync = pPager->tempFile || !useJournal; + pPager->pFirst = 0; + pPager->pFirstSynced = 0; + pPager->pLast = 0; + pPager->nExtra = nExtra; + memset(pPager->aHash, 0, sizeof(pPager->aHash)); + *ppPager = pPager; + return SQLITE_OK; +} + +/* +** Set the destructor for this pager. If not NULL, the destructor is called +** when the reference count on each page reaches zero. The destructor can +** be used to clean up information in the extra segment appended to each page. +** +** The destructor is not called as a result sqlitepager_close(). +** Destructors are only called by sqlitepager_unref(). +*/ +void sqlitepager_set_destructor(Pager *pPager, void (*xDesc)(void*)){ + pPager->xDestructor = xDesc; +} + +/* +** Return the total number of pages in the disk file associated with +** pPager. +*/ +int sqlitepager_pagecount(Pager *pPager){ + off_t n; + assert( pPager!=0 ); + if( pPager->dbSize>=0 ){ + return pPager->dbSize; + } + if( sqliteOsFileSize(&pPager->fd, &n)!=SQLITE_OK ){ + pPager->errMask |= PAGER_ERR_DISK; + return 0; + } + n /= SQLITE_PAGE_SIZE; + if( pPager->state!=SQLITE_UNLOCK ){ + pPager->dbSize = n; + } + return n; +} + +/* +** Forward declaration +*/ +static int syncJournal(Pager*); + +/* +** Truncate the file to the number of pages specified. +*/ +int sqlitepager_truncate(Pager *pPager, Pgno nPage){ + int rc; + if( pPager->dbSize<0 ){ + sqlitepager_pagecount(pPager); + } + if( pPager->errMask!=0 ){ + rc = pager_errcode(pPager); + return rc; + } + if( nPage>=(unsigned)pPager->dbSize ){ + return SQLITE_OK; + } + syncJournal(pPager); + rc = sqliteOsTruncate(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)nPage); + if( rc==SQLITE_OK ){ + pPager->dbSize = nPage; + } + return rc; +} + +/* +** Shutdown the page cache. Free all memory and close all files. +** +** If a transaction was in progress when this routine is called, that +** transaction is rolled back. All outstanding pages are invalidated +** and their memory is freed. Any attempt to use a page associated +** with this page cache after this function returns will likely +** result in a coredump. +*/ +int sqlitepager_close(Pager *pPager){ + PgHdr *pPg, *pNext; + switch( pPager->state ){ + case SQLITE_WRITELOCK: { + sqlitepager_rollback(pPager); + sqliteOsUnlock(&pPager->fd); + assert( pPager->journalOpen==0 ); + break; + } + case SQLITE_READLOCK: { + sqliteOsUnlock(&pPager->fd); + break; + } + default: { + /* Do nothing */ + break; + } + } + for(pPg=pPager->pAll; pPg; pPg=pNext){ + pNext = pPg->pNextAll; + sqliteFree(pPg); + } + sqliteOsClose(&pPager->fd); + assert( pPager->journalOpen==0 ); + /* Temp files are automatically deleted by the OS + ** if( pPager->tempFile ){ + ** sqliteOsDelete(pPager->zFilename); + ** } + */ + CLR_PAGER(pPager); + if( pPager->zFilename!=(char*)&pPager[1] ){ + assert( 0 ); /* Cannot happen */ + sqliteFree(pPager->zFilename); + sqliteFree(pPager->zJournal); + sqliteFree(pPager->zDirectory); + } + sqliteFree(pPager); + return SQLITE_OK; +} + +/* +** Return the page number for the given page data. +*/ +Pgno sqlitepager_pagenumber(void *pData){ + PgHdr *p = DATA_TO_PGHDR(pData); + return p->pgno; +} + +/* +** Increment the reference count for a page. If the page is +** currently on the freelist (the reference count is zero) then +** remove it from the freelist. +*/ +#define page_ref(P) ((P)->nRef==0?_page_ref(P):(void)(P)->nRef++) +static void _page_ref(PgHdr *pPg){ + if( pPg->nRef==0 ){ + /* The page is currently on the freelist. Remove it. */ + if( pPg==pPg->pPager->pFirstSynced ){ + PgHdr *p = pPg->pNextFree; + while( p && p->needSync ){ p = p->pNextFree; } + pPg->pPager->pFirstSynced = p; + } + if( pPg->pPrevFree ){ + pPg->pPrevFree->pNextFree = pPg->pNextFree; + }else{ + pPg->pPager->pFirst = pPg->pNextFree; + } + if( pPg->pNextFree ){ + pPg->pNextFree->pPrevFree = pPg->pPrevFree; + }else{ + pPg->pPager->pLast = pPg->pPrevFree; + } + pPg->pPager->nRef++; + } + pPg->nRef++; + REFINFO(pPg); +} + +/* +** Increment the reference count for a page. The input pointer is +** a reference to the page data. +*/ +int sqlitepager_ref(void *pData){ + PgHdr *pPg = DATA_TO_PGHDR(pData); + page_ref(pPg); + return SQLITE_OK; +} + +/* +** Sync the journal. In other words, make sure all the pages that have +** been written to the journal have actually reached the surface of the +** disk. It is not safe to modify the original database file until after +** the journal has been synced. If the original database is modified before +** the journal is synced and a power failure occurs, the unsynced journal +** data would be lost and we would be unable to completely rollback the +** database changes. Database corruption would occur. +** +** This routine also updates the nRec field in the header of the journal. +** (See comments on the pager_playback() routine for additional information.) +** If the sync mode is FULL, two syncs will occur. First the whole journal +** is synced, then the nRec field is updated, then a second sync occurs. +** +** For temporary databases, we do not care if we are able to rollback +** after a power failure, so sync occurs. +** +** This routine clears the needSync field of every page current held in +** memory. +*/ +static int syncJournal(Pager *pPager){ + PgHdr *pPg; + int rc = SQLITE_OK; + + /* Sync the journal before modifying the main database + ** (assuming there is a journal and it needs to be synced.) + */ + if( pPager->needSync ){ + if( !pPager->tempFile ){ + assert( pPager->journalOpen ); + /* assert( !pPager->noSync ); // noSync might be set if synchronous + ** was turned off after the transaction was started. Ticket #615 */ +#ifndef NDEBUG + { + /* Make sure the pPager->nRec counter we are keeping agrees + ** with the nRec computed from the size of the journal file. + */ + off_t hdrSz, pgSz, jSz; + hdrSz = JOURNAL_HDR_SZ(journal_format); + pgSz = JOURNAL_PG_SZ(journal_format); + rc = sqliteOsFileSize(&pPager->jfd, &jSz); + if( rc!=0 ) return rc; + assert( pPager->nRec*pgSz+hdrSz==jSz ); + } +#endif + if( journal_format>=3 ){ + /* Write the nRec value into the journal file header */ + off_t szJ; + if( pPager->fullSync ){ + TRACE1("SYNC\n"); + rc = sqliteOsSync(&pPager->jfd); + if( rc!=0 ) return rc; + } + sqliteOsSeek(&pPager->jfd, sizeof(aJournalMagic1)); + rc = write32bits(&pPager->jfd, pPager->nRec); + if( rc ) return rc; + szJ = JOURNAL_HDR_SZ(journal_format) + + pPager->nRec*JOURNAL_PG_SZ(journal_format); + sqliteOsSeek(&pPager->jfd, szJ); + } + TRACE1("SYNC\n"); + rc = sqliteOsSync(&pPager->jfd); + if( rc!=0 ) return rc; + pPager->journalStarted = 1; + } + pPager->needSync = 0; + + /* Erase the needSync flag from every page. + */ + for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ + pPg->needSync = 0; + } + pPager->pFirstSynced = pPager->pFirst; + } + +#ifndef NDEBUG + /* If the Pager.needSync flag is clear then the PgHdr.needSync + ** flag must also be clear for all pages. Verify that this + ** invariant is true. + */ + else{ + for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ + assert( pPg->needSync==0 ); + } + assert( pPager->pFirstSynced==pPager->pFirst ); + } +#endif + + return rc; +} + +/* +** Given a list of pages (connected by the PgHdr.pDirty pointer) write +** every one of those pages out to the database file and mark them all +** as clean. +*/ +static int pager_write_pagelist(PgHdr *pList){ + Pager *pPager; + int rc; + + if( pList==0 ) return SQLITE_OK; + pPager = pList->pPager; + while( pList ){ + assert( pList->dirty ); + sqliteOsSeek(&pPager->fd, (pList->pgno-1)*(off_t)SQLITE_PAGE_SIZE); + CODEC(pPager, PGHDR_TO_DATA(pList), pList->pgno, 6); + TRACE2("STORE %d\n", pList->pgno); + rc = sqliteOsWrite(&pPager->fd, PGHDR_TO_DATA(pList), SQLITE_PAGE_SIZE); + CODEC(pPager, PGHDR_TO_DATA(pList), pList->pgno, 0); + if( rc ) return rc; + pList->dirty = 0; + pList = pList->pDirty; + } + return SQLITE_OK; +} + +/* +** Collect every dirty page into a dirty list and +** return a pointer to the head of that list. All pages are +** collected even if they are still in use. +*/ +static PgHdr *pager_get_all_dirty_pages(Pager *pPager){ + PgHdr *p, *pList; + pList = 0; + for(p=pPager->pAll; p; p=p->pNextAll){ + if( p->dirty ){ + p->pDirty = pList; + pList = p; + } + } + return pList; +} + +/* +** Acquire a page. +** +** A read lock on the disk file is obtained when the first page is acquired. +** This read lock is dropped when the last page is released. +** +** A _get works for any page number greater than 0. If the database +** file is smaller than the requested page, then no actual disk +** read occurs and the memory image of the page is initialized to +** all zeros. The extra data appended to a page is always initialized +** to zeros the first time a page is loaded into memory. +** +** The acquisition might fail for several reasons. In all cases, +** an appropriate error code is returned and *ppPage is set to NULL. +** +** See also sqlitepager_lookup(). Both this routine and _lookup() attempt +** to find a page in the in-memory cache first. If the page is not already +** in memory, this routine goes to disk to read it in whereas _lookup() +** just returns 0. This routine acquires a read-lock the first time it +** has to go to disk, and could also playback an old journal if necessary. +** Since _lookup() never goes to disk, it never has to deal with locks +** or journal files. +*/ +int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage){ + PgHdr *pPg; + int rc; + + /* Make sure we have not hit any critical errors. + */ + assert( pPager!=0 ); + assert( pgno!=0 ); + *ppPage = 0; + if( pPager->errMask & ~(PAGER_ERR_FULL) ){ + return pager_errcode(pPager); + } + + /* If this is the first page accessed, then get a read lock + ** on the database file. + */ + if( pPager->nRef==0 ){ + rc = sqliteOsReadLock(&pPager->fd); + if( rc!=SQLITE_OK ){ + return rc; + } + pPager->state = SQLITE_READLOCK; + + /* If a journal file exists, try to play it back. + */ + if( pPager->useJournal && sqliteOsFileExists(pPager->zJournal) ){ + int rc; + + /* Get a write lock on the database + */ + rc = sqliteOsWriteLock(&pPager->fd); + if( rc!=SQLITE_OK ){ + if( sqliteOsUnlock(&pPager->fd)!=SQLITE_OK ){ + /* This should never happen! */ + rc = SQLITE_INTERNAL; + } + return rc; + } + pPager->state = SQLITE_WRITELOCK; + + /* Open the journal for reading only. Return SQLITE_BUSY if + ** we are unable to open the journal file. + ** + ** The journal file does not need to be locked itself. The + ** journal file is never open unless the main database file holds + ** a write lock, so there is never any chance of two or more + ** processes opening the journal at the same time. + */ + rc = sqliteOsOpenReadOnly(pPager->zJournal, &pPager->jfd); + if( rc!=SQLITE_OK ){ + rc = sqliteOsUnlock(&pPager->fd); + assert( rc==SQLITE_OK ); + return SQLITE_BUSY; + } + pPager->journalOpen = 1; + pPager->journalStarted = 0; + + /* Playback and delete the journal. Drop the database write + ** lock and reacquire the read lock. + */ + rc = pager_playback(pPager, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + } + pPg = 0; + }else{ + /* Search for page in cache */ + pPg = pager_lookup(pPager, pgno); + } + if( pPg==0 ){ + /* The requested page is not in the page cache. */ + int h; + pPager->nMiss++; + if( pPager->nPagemxPage || pPager->pFirst==0 ){ + /* Create a new page */ + pPg = sqliteMallocRaw( sizeof(*pPg) + SQLITE_PAGE_SIZE + + sizeof(u32) + pPager->nExtra ); + if( pPg==0 ){ + pager_unwritelock(pPager); + pPager->errMask |= PAGER_ERR_MEM; + return SQLITE_NOMEM; + } + memset(pPg, 0, sizeof(*pPg)); + pPg->pPager = pPager; + pPg->pNextAll = pPager->pAll; + if( pPager->pAll ){ + pPager->pAll->pPrevAll = pPg; + } + pPg->pPrevAll = 0; + pPager->pAll = pPg; + pPager->nPage++; + }else{ + /* Find a page to recycle. Try to locate a page that does not + ** require us to do an fsync() on the journal. + */ + pPg = pPager->pFirstSynced; + + /* If we could not find a page that does not require an fsync() + ** on the journal file then fsync the journal file. This is a + ** very slow operation, so we work hard to avoid it. But sometimes + ** it can't be helped. + */ + if( pPg==0 ){ + int rc = syncJournal(pPager); + if( rc!=0 ){ + sqlitepager_rollback(pPager); + return SQLITE_IOERR; + } + pPg = pPager->pFirst; + } + assert( pPg->nRef==0 ); + + /* Write the page to the database file if it is dirty. + */ + if( pPg->dirty ){ + assert( pPg->needSync==0 ); + pPg->pDirty = 0; + rc = pager_write_pagelist( pPg ); + if( rc!=SQLITE_OK ){ + sqlitepager_rollback(pPager); + return SQLITE_IOERR; + } + } + assert( pPg->dirty==0 ); + + /* If the page we are recycling is marked as alwaysRollback, then + ** set the global alwaysRollback flag, thus disabling the + ** sqlite_dont_rollback() optimization for the rest of this transaction. + ** It is necessary to do this because the page marked alwaysRollback + ** might be reloaded at a later time but at that point we won't remember + ** that is was marked alwaysRollback. This means that all pages must + ** be marked as alwaysRollback from here on out. + */ + if( pPg->alwaysRollback ){ + pPager->alwaysRollback = 1; + } + + /* Unlink the old page from the free list and the hash table + */ + if( pPg==pPager->pFirstSynced ){ + PgHdr *p = pPg->pNextFree; + while( p && p->needSync ){ p = p->pNextFree; } + pPager->pFirstSynced = p; + } + if( pPg->pPrevFree ){ + pPg->pPrevFree->pNextFree = pPg->pNextFree; + }else{ + assert( pPager->pFirst==pPg ); + pPager->pFirst = pPg->pNextFree; + } + if( pPg->pNextFree ){ + pPg->pNextFree->pPrevFree = pPg->pPrevFree; + }else{ + assert( pPager->pLast==pPg ); + pPager->pLast = pPg->pPrevFree; + } + pPg->pNextFree = pPg->pPrevFree = 0; + if( pPg->pNextHash ){ + pPg->pNextHash->pPrevHash = pPg->pPrevHash; + } + if( pPg->pPrevHash ){ + pPg->pPrevHash->pNextHash = pPg->pNextHash; + }else{ + h = pager_hash(pPg->pgno); + assert( pPager->aHash[h]==pPg ); + pPager->aHash[h] = pPg->pNextHash; + } + pPg->pNextHash = pPg->pPrevHash = 0; + pPager->nOvfl++; + } + pPg->pgno = pgno; + if( pPager->aInJournal && (int)pgno<=pPager->origDbSize ){ + sqliteCheckMemory(pPager->aInJournal, pgno/8); + assert( pPager->journalOpen ); + pPg->inJournal = (pPager->aInJournal[pgno/8] & (1<<(pgno&7)))!=0; + pPg->needSync = 0; + }else{ + pPg->inJournal = 0; + pPg->needSync = 0; + } + if( pPager->aInCkpt && (int)pgno<=pPager->ckptSize + && (pPager->aInCkpt[pgno/8] & (1<<(pgno&7)))!=0 ){ + page_add_to_ckpt_list(pPg); + }else{ + page_remove_from_ckpt_list(pPg); + } + pPg->dirty = 0; + pPg->nRef = 1; + REFINFO(pPg); + pPager->nRef++; + h = pager_hash(pgno); + pPg->pNextHash = pPager->aHash[h]; + pPager->aHash[h] = pPg; + if( pPg->pNextHash ){ + assert( pPg->pNextHash->pPrevHash==0 ); + pPg->pNextHash->pPrevHash = pPg; + } + if( pPager->nExtra>0 ){ + memset(PGHDR_TO_EXTRA(pPg), 0, pPager->nExtra); + } + if( pPager->dbSize<0 ) sqlitepager_pagecount(pPager); + if( pPager->errMask!=0 ){ + sqlitepager_unref(PGHDR_TO_DATA(pPg)); + rc = pager_errcode(pPager); + return rc; + } + if( pPager->dbSize<(int)pgno ){ + memset(PGHDR_TO_DATA(pPg), 0, SQLITE_PAGE_SIZE); + }else{ + int rc; + sqliteOsSeek(&pPager->fd, (pgno-1)*(off_t)SQLITE_PAGE_SIZE); + rc = sqliteOsRead(&pPager->fd, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE); + TRACE2("FETCH %d\n", pPg->pgno); + CODEC(pPager, PGHDR_TO_DATA(pPg), pPg->pgno, 3); + if( rc!=SQLITE_OK ){ + off_t fileSize; + if( sqliteOsFileSize(&pPager->fd,&fileSize)!=SQLITE_OK + || fileSize>=pgno*SQLITE_PAGE_SIZE ){ + sqlitepager_unref(PGHDR_TO_DATA(pPg)); + return rc; + }else{ + memset(PGHDR_TO_DATA(pPg), 0, SQLITE_PAGE_SIZE); + } + } + } + }else{ + /* The requested page is in the page cache. */ + pPager->nHit++; + page_ref(pPg); + } + *ppPage = PGHDR_TO_DATA(pPg); + return SQLITE_OK; +} + +/* +** Acquire a page if it is already in the in-memory cache. Do +** not read the page from disk. Return a pointer to the page, +** or 0 if the page is not in cache. +** +** See also sqlitepager_get(). The difference between this routine +** and sqlitepager_get() is that _get() will go to the disk and read +** in the page if the page is not already in cache. This routine +** returns NULL if the page is not in cache or if a disk I/O error +** has ever happened. +*/ +void *sqlitepager_lookup(Pager *pPager, Pgno pgno){ + PgHdr *pPg; + + assert( pPager!=0 ); + assert( pgno!=0 ); + if( pPager->errMask & ~(PAGER_ERR_FULL) ){ + return 0; + } + /* if( pPager->nRef==0 ){ + ** return 0; + ** } + */ + pPg = pager_lookup(pPager, pgno); + if( pPg==0 ) return 0; + page_ref(pPg); + return PGHDR_TO_DATA(pPg); +} + +/* +** Release a page. +** +** If the number of references to the page drop to zero, then the +** page is added to the LRU list. When all references to all pages +** are released, a rollback occurs and the lock on the database is +** removed. +*/ +int sqlitepager_unref(void *pData){ + PgHdr *pPg; + + /* Decrement the reference count for this page + */ + pPg = DATA_TO_PGHDR(pData); + assert( pPg->nRef>0 ); + pPg->nRef--; + REFINFO(pPg); + + /* When the number of references to a page reach 0, call the + ** destructor and add the page to the freelist. + */ + if( pPg->nRef==0 ){ + Pager *pPager; + pPager = pPg->pPager; + pPg->pNextFree = 0; + pPg->pPrevFree = pPager->pLast; + pPager->pLast = pPg; + if( pPg->pPrevFree ){ + pPg->pPrevFree->pNextFree = pPg; + }else{ + pPager->pFirst = pPg; + } + if( pPg->needSync==0 && pPager->pFirstSynced==0 ){ + pPager->pFirstSynced = pPg; + } + if( pPager->xDestructor ){ + pPager->xDestructor(pData); + } + + /* When all pages reach the freelist, drop the read lock from + ** the database file. + */ + pPager->nRef--; + assert( pPager->nRef>=0 ); + if( pPager->nRef==0 ){ + pager_reset(pPager); + } + } + return SQLITE_OK; +} + +/* +** Create a journal file for pPager. There should already be a write +** lock on the database file when this routine is called. +** +** Return SQLITE_OK if everything. Return an error code and release the +** write lock if anything goes wrong. +*/ +static int pager_open_journal(Pager *pPager){ + int rc; + assert( pPager->state==SQLITE_WRITELOCK ); + assert( pPager->journalOpen==0 ); + assert( pPager->useJournal ); + sqlitepager_pagecount(pPager); + pPager->aInJournal = sqliteMalloc( pPager->dbSize/8 + 1 ); + if( pPager->aInJournal==0 ){ + sqliteOsReadLock(&pPager->fd); + pPager->state = SQLITE_READLOCK; + return SQLITE_NOMEM; + } + rc = sqliteOsOpenExclusive(pPager->zJournal, &pPager->jfd,pPager->tempFile); + if( rc!=SQLITE_OK ){ + sqliteFree(pPager->aInJournal); + pPager->aInJournal = 0; + sqliteOsReadLock(&pPager->fd); + pPager->state = SQLITE_READLOCK; + return SQLITE_CANTOPEN; + } + sqliteOsOpenDirectory(pPager->zDirectory, &pPager->jfd); + pPager->journalOpen = 1; + pPager->journalStarted = 0; + pPager->needSync = 0; + pPager->alwaysRollback = 0; + pPager->nRec = 0; + if( pPager->errMask!=0 ){ + rc = pager_errcode(pPager); + return rc; + } + pPager->origDbSize = pPager->dbSize; + if( journal_format==JOURNAL_FORMAT_3 ){ + rc = sqliteOsWrite(&pPager->jfd, aJournalMagic3, sizeof(aJournalMagic3)); + if( rc==SQLITE_OK ){ + rc = write32bits(&pPager->jfd, pPager->noSync ? 0xffffffff : 0); + } + if( rc==SQLITE_OK ){ + sqliteRandomness(sizeof(pPager->cksumInit), &pPager->cksumInit); + rc = write32bits(&pPager->jfd, pPager->cksumInit); + } + }else if( journal_format==JOURNAL_FORMAT_2 ){ + rc = sqliteOsWrite(&pPager->jfd, aJournalMagic2, sizeof(aJournalMagic2)); + }else{ + assert( journal_format==JOURNAL_FORMAT_1 ); + rc = sqliteOsWrite(&pPager->jfd, aJournalMagic1, sizeof(aJournalMagic1)); + } + if( rc==SQLITE_OK ){ + rc = write32bits(&pPager->jfd, pPager->dbSize); + } + if( pPager->ckptAutoopen && rc==SQLITE_OK ){ + rc = sqlitepager_ckpt_begin(pPager); + } + if( rc!=SQLITE_OK ){ + rc = pager_unwritelock(pPager); + if( rc==SQLITE_OK ){ + rc = SQLITE_FULL; + } + } + return rc; +} + +/* +** Acquire a write-lock on the database. The lock is removed when +** the any of the following happen: +** +** * sqlitepager_commit() is called. +** * sqlitepager_rollback() is called. +** * sqlitepager_close() is called. +** * sqlitepager_unref() is called to on every outstanding page. +** +** The parameter to this routine is a pointer to any open page of the +** database file. Nothing changes about the page - it is used merely +** to acquire a pointer to the Pager structure and as proof that there +** is already a read-lock on the database. +** +** A journal file is opened if this is not a temporary file. For +** temporary files, the opening of the journal file is deferred until +** there is an actual need to write to the journal. +** +** If the database is already write-locked, this routine is a no-op. +*/ +int sqlitepager_begin(void *pData){ + PgHdr *pPg = DATA_TO_PGHDR(pData); + Pager *pPager = pPg->pPager; + int rc = SQLITE_OK; + assert( pPg->nRef>0 ); + assert( pPager->state!=SQLITE_UNLOCK ); + if( pPager->state==SQLITE_READLOCK ){ + assert( pPager->aInJournal==0 ); + rc = sqliteOsWriteLock(&pPager->fd); + if( rc!=SQLITE_OK ){ + return rc; + } + pPager->state = SQLITE_WRITELOCK; + pPager->dirtyFile = 0; + TRACE1("TRANSACTION\n"); + if( pPager->useJournal && !pPager->tempFile ){ + rc = pager_open_journal(pPager); + } + } + return rc; +} + +/* +** Mark a data page as writeable. The page is written into the journal +** if it is not there already. This routine must be called before making +** changes to a page. +** +** The first time this routine is called, the pager creates a new +** journal and acquires a write lock on the database. If the write +** lock could not be acquired, this routine returns SQLITE_BUSY. The +** calling routine must check for that return value and be careful not to +** change any page data until this routine returns SQLITE_OK. +** +** If the journal file could not be written because the disk is full, +** then this routine returns SQLITE_FULL and does an immediate rollback. +** All subsequent write attempts also return SQLITE_FULL until there +** is a call to sqlitepager_commit() or sqlitepager_rollback() to +** reset. +*/ +int sqlitepager_write(void *pData){ + PgHdr *pPg = DATA_TO_PGHDR(pData); + Pager *pPager = pPg->pPager; + int rc = SQLITE_OK; + + /* Check for errors + */ + if( pPager->errMask ){ + return pager_errcode(pPager); + } + if( pPager->readOnly ){ + return SQLITE_PERM; + } + + /* Mark the page as dirty. If the page has already been written + ** to the journal then we can return right away. + */ + pPg->dirty = 1; + if( pPg->inJournal && (pPg->inCkpt || pPager->ckptInUse==0) ){ + pPager->dirtyFile = 1; + return SQLITE_OK; + } + + /* If we get this far, it means that the page needs to be + ** written to the transaction journal or the ckeckpoint journal + ** or both. + ** + ** First check to see that the transaction journal exists and + ** create it if it does not. + */ + assert( pPager->state!=SQLITE_UNLOCK ); + rc = sqlitepager_begin(pData); + if( rc!=SQLITE_OK ){ + return rc; + } + assert( pPager->state==SQLITE_WRITELOCK ); + if( !pPager->journalOpen && pPager->useJournal ){ + rc = pager_open_journal(pPager); + if( rc!=SQLITE_OK ) return rc; + } + assert( pPager->journalOpen || !pPager->useJournal ); + pPager->dirtyFile = 1; + + /* The transaction journal now exists and we have a write lock on the + ** main database file. Write the current page to the transaction + ** journal if it is not there already. + */ + if( !pPg->inJournal && pPager->useJournal ){ + if( (int)pPg->pgno <= pPager->origDbSize ){ + int szPg; + u32 saved; + if( journal_format>=JOURNAL_FORMAT_3 ){ + u32 cksum = pager_cksum(pPager, pPg->pgno, pData); + saved = *(u32*)PGHDR_TO_EXTRA(pPg); + store32bits(cksum, pPg, SQLITE_PAGE_SIZE); + szPg = SQLITE_PAGE_SIZE+8; + }else{ + szPg = SQLITE_PAGE_SIZE+4; + } + store32bits(pPg->pgno, pPg, -4); + CODEC(pPager, pData, pPg->pgno, 7); + rc = sqliteOsWrite(&pPager->jfd, &((char*)pData)[-4], szPg); + TRACE3("JOURNAL %d %d\n", pPg->pgno, pPg->needSync); + CODEC(pPager, pData, pPg->pgno, 0); + if( journal_format>=JOURNAL_FORMAT_3 ){ + *(u32*)PGHDR_TO_EXTRA(pPg) = saved; + } + if( rc!=SQLITE_OK ){ + sqlitepager_rollback(pPager); + pPager->errMask |= PAGER_ERR_FULL; + return rc; + } + pPager->nRec++; + assert( pPager->aInJournal!=0 ); + pPager->aInJournal[pPg->pgno/8] |= 1<<(pPg->pgno&7); + pPg->needSync = !pPager->noSync; + pPg->inJournal = 1; + if( pPager->ckptInUse ){ + pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7); + page_add_to_ckpt_list(pPg); + } + }else{ + pPg->needSync = !pPager->journalStarted && !pPager->noSync; + TRACE3("APPEND %d %d\n", pPg->pgno, pPg->needSync); + } + if( pPg->needSync ){ + pPager->needSync = 1; + } + } + + /* If the checkpoint journal is open and the page is not in it, + ** then write the current page to the checkpoint journal. Note that + ** the checkpoint journal always uses the simplier format 2 that lacks + ** checksums. The header is also omitted from the checkpoint journal. + */ + if( pPager->ckptInUse && !pPg->inCkpt && (int)pPg->pgno<=pPager->ckptSize ){ + assert( pPg->inJournal || (int)pPg->pgno>pPager->origDbSize ); + store32bits(pPg->pgno, pPg, -4); + CODEC(pPager, pData, pPg->pgno, 7); + rc = sqliteOsWrite(&pPager->cpfd, &((char*)pData)[-4], SQLITE_PAGE_SIZE+4); + TRACE2("CKPT-JOURNAL %d\n", pPg->pgno); + CODEC(pPager, pData, pPg->pgno, 0); + if( rc!=SQLITE_OK ){ + sqlitepager_rollback(pPager); + pPager->errMask |= PAGER_ERR_FULL; + return rc; + } + pPager->ckptNRec++; + assert( pPager->aInCkpt!=0 ); + pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7); + page_add_to_ckpt_list(pPg); + } + + /* Update the database size and return. + */ + if( pPager->dbSize<(int)pPg->pgno ){ + pPager->dbSize = pPg->pgno; + } + return rc; +} + +/* +** Return TRUE if the page given in the argument was previously passed +** to sqlitepager_write(). In other words, return TRUE if it is ok +** to change the content of the page. +*/ +int sqlitepager_iswriteable(void *pData){ + PgHdr *pPg = DATA_TO_PGHDR(pData); + return pPg->dirty; +} + +/* +** Replace the content of a single page with the information in the third +** argument. +*/ +int sqlitepager_overwrite(Pager *pPager, Pgno pgno, void *pData){ + void *pPage; + int rc; + + rc = sqlitepager_get(pPager, pgno, &pPage); + if( rc==SQLITE_OK ){ + rc = sqlitepager_write(pPage); + if( rc==SQLITE_OK ){ + memcpy(pPage, pData, SQLITE_PAGE_SIZE); + } + sqlitepager_unref(pPage); + } + return rc; +} + +/* +** A call to this routine tells the pager that it is not necessary to +** write the information on page "pgno" back to the disk, even though +** that page might be marked as dirty. +** +** The overlying software layer calls this routine when all of the data +** on the given page is unused. The pager marks the page as clean so +** that it does not get written to disk. +** +** Tests show that this optimization, together with the +** sqlitepager_dont_rollback() below, more than double the speed +** of large INSERT operations and quadruple the speed of large DELETEs. +** +** When this routine is called, set the alwaysRollback flag to true. +** Subsequent calls to sqlitepager_dont_rollback() for the same page +** will thereafter be ignored. This is necessary to avoid a problem +** where a page with data is added to the freelist during one part of +** a transaction then removed from the freelist during a later part +** of the same transaction and reused for some other purpose. When it +** is first added to the freelist, this routine is called. When reused, +** the dont_rollback() routine is called. But because the page contains +** critical data, we still need to be sure it gets rolled back in spite +** of the dont_rollback() call. +*/ +void sqlitepager_dont_write(Pager *pPager, Pgno pgno){ + PgHdr *pPg; + + pPg = pager_lookup(pPager, pgno); + pPg->alwaysRollback = 1; + if( pPg && pPg->dirty && !pPager->ckptInUse ){ + if( pPager->dbSize==(int)pPg->pgno && pPager->origDbSizedbSize ){ + /* If this pages is the last page in the file and the file has grown + ** during the current transaction, then do NOT mark the page as clean. + ** When the database file grows, we must make sure that the last page + ** gets written at least once so that the disk file will be the correct + ** size. If you do not write this page and the size of the file + ** on the disk ends up being too small, that can lead to database + ** corruption during the next transaction. + */ + }else{ + TRACE2("DONT_WRITE %d\n", pgno); + pPg->dirty = 0; + } + } +} + +/* +** A call to this routine tells the pager that if a rollback occurs, +** it is not necessary to restore the data on the given page. This +** means that the pager does not have to record the given page in the +** rollback journal. +*/ +void sqlitepager_dont_rollback(void *pData){ + PgHdr *pPg = DATA_TO_PGHDR(pData); + Pager *pPager = pPg->pPager; + + if( pPager->state!=SQLITE_WRITELOCK || pPager->journalOpen==0 ) return; + if( pPg->alwaysRollback || pPager->alwaysRollback ) return; + if( !pPg->inJournal && (int)pPg->pgno <= pPager->origDbSize ){ + assert( pPager->aInJournal!=0 ); + pPager->aInJournal[pPg->pgno/8] |= 1<<(pPg->pgno&7); + pPg->inJournal = 1; + if( pPager->ckptInUse ){ + pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7); + page_add_to_ckpt_list(pPg); + } + TRACE2("DONT_ROLLBACK %d\n", pPg->pgno); + } + if( pPager->ckptInUse && !pPg->inCkpt && (int)pPg->pgno<=pPager->ckptSize ){ + assert( pPg->inJournal || (int)pPg->pgno>pPager->origDbSize ); + assert( pPager->aInCkpt!=0 ); + pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7); + page_add_to_ckpt_list(pPg); + } +} + +/* +** Commit all changes to the database and release the write lock. +** +** If the commit fails for any reason, a rollback attempt is made +** and an error code is returned. If the commit worked, SQLITE_OK +** is returned. +*/ +int sqlitepager_commit(Pager *pPager){ + int rc; + PgHdr *pPg; + + if( pPager->errMask==PAGER_ERR_FULL ){ + rc = sqlitepager_rollback(pPager); + if( rc==SQLITE_OK ){ + rc = SQLITE_FULL; + } + return rc; + } + if( pPager->errMask!=0 ){ + rc = pager_errcode(pPager); + return rc; + } + if( pPager->state!=SQLITE_WRITELOCK ){ + return SQLITE_ERROR; + } + TRACE1("COMMIT\n"); + if( pPager->dirtyFile==0 ){ + /* Exit early (without doing the time-consuming sqliteOsSync() calls) + ** if there have been no changes to the database file. */ + assert( pPager->needSync==0 ); + rc = pager_unwritelock(pPager); + pPager->dbSize = -1; + return rc; + } + assert( pPager->journalOpen ); + rc = syncJournal(pPager); + if( rc!=SQLITE_OK ){ + goto commit_abort; + } + pPg = pager_get_all_dirty_pages(pPager); + if( pPg ){ + rc = pager_write_pagelist(pPg); + if( rc || (!pPager->noSync && sqliteOsSync(&pPager->fd)!=SQLITE_OK) ){ + goto commit_abort; + } + } + rc = pager_unwritelock(pPager); + pPager->dbSize = -1; + return rc; + + /* Jump here if anything goes wrong during the commit process. + */ +commit_abort: + rc = sqlitepager_rollback(pPager); + if( rc==SQLITE_OK ){ + rc = SQLITE_FULL; + } + return rc; +} + +/* +** Rollback all changes. The database falls back to read-only mode. +** All in-memory cache pages revert to their original data contents. +** The journal is deleted. +** +** This routine cannot fail unless some other process is not following +** the correct locking protocol (SQLITE_PROTOCOL) or unless some other +** process is writing trash into the journal file (SQLITE_CORRUPT) or +** unless a prior malloc() failed (SQLITE_NOMEM). Appropriate error +** codes are returned for all these occasions. Otherwise, +** SQLITE_OK is returned. +*/ +int sqlitepager_rollback(Pager *pPager){ + int rc; + TRACE1("ROLLBACK\n"); + if( !pPager->dirtyFile || !pPager->journalOpen ){ + rc = pager_unwritelock(pPager); + pPager->dbSize = -1; + return rc; + } + + if( pPager->errMask!=0 && pPager->errMask!=PAGER_ERR_FULL ){ + if( pPager->state>=SQLITE_WRITELOCK ){ + pager_playback(pPager, 1); + } + return pager_errcode(pPager); + } + if( pPager->state!=SQLITE_WRITELOCK ){ + return SQLITE_OK; + } + rc = pager_playback(pPager, 1); + if( rc!=SQLITE_OK ){ + rc = SQLITE_CORRUPT; + pPager->errMask |= PAGER_ERR_CORRUPT; + } + pPager->dbSize = -1; + return rc; +} + +/* +** Return TRUE if the database file is opened read-only. Return FALSE +** if the database is (in theory) writable. +*/ +int sqlitepager_isreadonly(Pager *pPager){ + return pPager->readOnly; +} + +/* +** This routine is used for testing and analysis only. +*/ +int *sqlitepager_stats(Pager *pPager){ + static int a[9]; + a[0] = pPager->nRef; + a[1] = pPager->nPage; + a[2] = pPager->mxPage; + a[3] = pPager->dbSize; + a[4] = pPager->state; + a[5] = pPager->errMask; + a[6] = pPager->nHit; + a[7] = pPager->nMiss; + a[8] = pPager->nOvfl; + return a; +} + +/* +** Set the checkpoint. +** +** This routine should be called with the transaction journal already +** open. A new checkpoint journal is created that can be used to rollback +** changes of a single SQL command within a larger transaction. +*/ +int sqlitepager_ckpt_begin(Pager *pPager){ + int rc; + char zTemp[SQLITE_TEMPNAME_SIZE]; + if( !pPager->journalOpen ){ + pPager->ckptAutoopen = 1; + return SQLITE_OK; + } + assert( pPager->journalOpen ); + assert( !pPager->ckptInUse ); + pPager->aInCkpt = sqliteMalloc( pPager->dbSize/8 + 1 ); + if( pPager->aInCkpt==0 ){ + sqliteOsReadLock(&pPager->fd); + return SQLITE_NOMEM; + } +#ifndef NDEBUG + rc = sqliteOsFileSize(&pPager->jfd, &pPager->ckptJSize); + if( rc ) goto ckpt_begin_failed; + assert( pPager->ckptJSize == + pPager->nRec*JOURNAL_PG_SZ(journal_format)+JOURNAL_HDR_SZ(journal_format) ); +#endif + pPager->ckptJSize = pPager->nRec*JOURNAL_PG_SZ(journal_format) + + JOURNAL_HDR_SZ(journal_format); + pPager->ckptSize = pPager->dbSize; + if( !pPager->ckptOpen ){ + rc = sqlitepager_opentemp(zTemp, &pPager->cpfd); + if( rc ) goto ckpt_begin_failed; + pPager->ckptOpen = 1; + pPager->ckptNRec = 0; + } + pPager->ckptInUse = 1; + return SQLITE_OK; + +ckpt_begin_failed: + if( pPager->aInCkpt ){ + sqliteFree(pPager->aInCkpt); + pPager->aInCkpt = 0; + } + return rc; +} + +/* +** Commit a checkpoint. +*/ +int sqlitepager_ckpt_commit(Pager *pPager){ + if( pPager->ckptInUse ){ + PgHdr *pPg, *pNext; + sqliteOsSeek(&pPager->cpfd, 0); + /* sqliteOsTruncate(&pPager->cpfd, 0); */ + pPager->ckptNRec = 0; + pPager->ckptInUse = 0; + sqliteFree( pPager->aInCkpt ); + pPager->aInCkpt = 0; + for(pPg=pPager->pCkpt; pPg; pPg=pNext){ + pNext = pPg->pNextCkpt; + assert( pPg->inCkpt ); + pPg->inCkpt = 0; + pPg->pPrevCkpt = pPg->pNextCkpt = 0; + } + pPager->pCkpt = 0; + } + pPager->ckptAutoopen = 0; + return SQLITE_OK; +} + +/* +** Rollback a checkpoint. +*/ +int sqlitepager_ckpt_rollback(Pager *pPager){ + int rc; + if( pPager->ckptInUse ){ + rc = pager_ckpt_playback(pPager); + sqlitepager_ckpt_commit(pPager); + }else{ + rc = SQLITE_OK; + } + pPager->ckptAutoopen = 0; + return rc; +} + +/* +** Return the full pathname of the database file. +*/ +const char *sqlitepager_filename(Pager *pPager){ + return pPager->zFilename; +} + +/* +** Set the codec for this pager +*/ +void sqlitepager_set_codec( + Pager *pPager, + void (*xCodec)(void*,void*,Pgno,int), + void *pCodecArg +){ + pPager->xCodec = xCodec; + pPager->pCodecArg = pCodecArg; +} + +#ifdef SQLITE_TEST +/* +** Print a listing of all referenced pages and their ref count. +*/ +void sqlitepager_refdump(Pager *pPager){ + PgHdr *pPg; + for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ + if( pPg->nRef<=0 ) continue; + printf("PAGE %3d addr=0x%08x nRef=%d\n", + pPg->pgno, (int)PGHDR_TO_DATA(pPg), pPg->nRef); + } +} +#endif diff --git a/src/libs/sqlite2/pager.h b/src/libs/sqlite2/pager.h new file mode 100644 index 00000000..96f9d406 --- /dev/null +++ b/src/libs/sqlite2/pager.h @@ -0,0 +1,107 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite page cache +** subsystem. The page cache subsystem reads and writes a file a page +** at a time and provides a journal for rollback. +** +** @(#) $Id: pager.h 326789 2004-07-07 21:25:56Z pahlibar $ +*/ + +/* +** The size of one page +** +** You can change this value to another (reasonable) value you want. +** It need not be a power of two, though the interface to the disk +** will likely be faster if it is. +** +** Experiments show that a page size of 1024 gives the best speed +** for common usages. The speed differences for different sizes +** such as 512, 2048, 4096, an so forth, is minimal. Note, however, +** that changing the page size results in a completely imcompatible +** file format. +*/ +#ifndef SQLITE_PAGE_SIZE +#define SQLITE_PAGE_SIZE 1024 +#endif + +/* +** Number of extra bytes of data allocated at the end of each page and +** stored on disk but not used by the higher level btree layer. Changing +** this value results in a completely incompatible file format. +*/ +#ifndef SQLITE_PAGE_RESERVE +#define SQLITE_PAGE_RESERVE 0 +#endif + +/* +** The total number of usable bytes stored on disk for each page. +** The usable bytes come at the beginning of the page and the reserve +** bytes come at the end. +*/ +#define SQLITE_USABLE_SIZE (SQLITE_PAGE_SIZE-SQLITE_PAGE_RESERVE) + +/* +** Maximum number of pages in one database. (This is a limitation of +** imposed by 4GB files size limits.) +*/ +#define SQLITE_MAX_PAGE 1073741823 + +/* +** The type used to represent a page number. The first page in a file +** is called page 1. 0 is used to represent "not a page". +*/ +typedef unsigned int Pgno; + +/* +** Each open file is managed by a separate instance of the "Pager" structure. +*/ +typedef struct Pager Pager; + +/* +** See source code comments for a detailed description of the following +** routines: +*/ +int sqlitepager_open(Pager **ppPager, const char *zFilename, + int nPage, int nExtra, int useJournal); +void sqlitepager_set_destructor(Pager*, void(*)(void*)); +void sqlitepager_set_cachesize(Pager*, int); +int sqlitepager_close(Pager *pPager); +int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage); +void *sqlitepager_lookup(Pager *pPager, Pgno pgno); +int sqlitepager_ref(void*); +int sqlitepager_unref(void*); +Pgno sqlitepager_pagenumber(void*); +int sqlitepager_write(void*); +int sqlitepager_iswriteable(void*); +int sqlitepager_overwrite(Pager *pPager, Pgno pgno, void*); +int sqlitepager_pagecount(Pager*); +int sqlitepager_truncate(Pager*,Pgno); +int sqlitepager_begin(void*); +int sqlitepager_commit(Pager*); +int sqlitepager_rollback(Pager*); +int sqlitepager_isreadonly(Pager*); +int sqlitepager_ckpt_begin(Pager*); +int sqlitepager_ckpt_commit(Pager*); +int sqlitepager_ckpt_rollback(Pager*); +void sqlitepager_dont_rollback(void*); +void sqlitepager_dont_write(Pager*, Pgno); +int *sqlitepager_stats(Pager*); +void sqlitepager_set_safety_level(Pager*,int); +const char *sqlitepager_filename(Pager*); +int sqlitepager_rename(Pager*, const char *zNewName); +void sqlitepager_set_codec(Pager*,void(*)(void*,void*,Pgno,int),void*); + +#ifdef SQLITE_TEST +void sqlitepager_refdump(Pager*); +int pager_refinfo_enable; +int journal_format; +#endif diff --git a/src/libs/sqlite2/parse.c b/src/libs/sqlite2/parse.c new file mode 100644 index 00000000..46353691 --- /dev/null +++ b/src/libs/sqlite2/parse.c @@ -0,0 +1,4035 @@ +/* Driver template for the LEMON parser generator. +** The author disclaims copyright to this source code. +*/ +/* First off, code is include which follows the "include" declaration +** in the input file. */ +#include +#line 33 "parse.y" + +#include "sqliteInt.h" +#include "parse.h" + +/* +** An instance of this structure holds information about the +** LIMIT clause of a SELECT statement. +*/ +struct LimitVal { + int limit; /* The LIMIT value. -1 if there is no limit */ + int offset; /* The OFFSET. 0 if there is none */ +}; + +/* +** An instance of the following structure describes the event of a +** TRIGGER. "a" is the event type, one of TK_UPDATE, TK_INSERT, +** TK_DELETE, or TK_INSTEAD. If the event is of the form +** +** UPDATE ON (a,b,c) +** +** Then the "b" IdList records the list "a,b,c". +*/ +struct TrigEvent { int a; IdList * b; }; + + +#line 34 "parse.c" +/* Next is all token values, in a form suitable for use by makeheaders. +** This section will be null unless lemon is run with the -m switch. +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ +/* Make sure the INTERFACE macro is defined. +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** YYCODETYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 terminals +** and nonterminals. "int" is used otherwise. +** YYNOCODE is a number of type YYCODETYPE which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** YYACTIONTYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 rules and +** states combined. "int" is used otherwise. +** sqliteParserTOKENTYPE is the data type used for minor tokens given +** directly to the parser from the tokenizer. +** YYMINORTYPE is the data type used for all minor tokens. +** This is typically a union of many types, one of +** which is sqliteParserTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. +** sqliteParserARG_SDECL A static variable declaration for the %extra_argument +** sqliteParserARG_PDECL A parameter declaration for the %extra_argument +** sqliteParserARG_STORE Code to store %extra_argument into yypParser +** sqliteParserARG_FETCH Code to extract %extra_argument from yypParser +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ +/*  */ +#define YYCODETYPE unsigned char +#define YYNOCODE 221 +#define YYACTIONTYPE unsigned short int +#define sqliteParserTOKENTYPE Token +typedef union { + sqliteParserTOKENTYPE yy0; + TriggerStep * yy19; + struct LimitVal yy124; + Select* yy179; + Expr * yy182; + Expr* yy242; + struct TrigEvent yy290; + Token yy298; + SrcList* yy307; + IdList* yy320; + ExprList* yy322; + int yy372; + struct {int value; int mask;} yy407; + int yy441; +} YYMINORTYPE; +#define YYSTACKDEPTH 100 +#define sqliteParserARG_SDECL Parse *pParse; +#define sqliteParserARG_PDECL ,Parse *pParse +#define sqliteParserARG_FETCH Parse *pParse = yypParser->pParse +#define sqliteParserARG_STORE yypParser->pParse = pParse +#define YYNSTATE 563 +#define YYNRULE 293 +#define YYERRORSYMBOL 131 +#define YYERRSYMDT yy441 +#define YYFALLBACK 1 +#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) +#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) +#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) + +/* Next are that tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < YYNSTATE Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** +** N == YYNSTATE+YYNRULE A syntax error has occurred. +** +** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** +** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as +** +** yy_action[ yy_shift_ofst[S] + X ] +** +** If the index value yy_shift_ofst[S]+X is out of range or if the value +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] +** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table +** and that yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +*/ +static YYACTIONTYPE yy_action[] = { + /* 0 */ 264, 5, 262, 119, 123, 117, 121, 129, 131, 133, + /* 10 */ 135, 144, 146, 148, 150, 152, 154, 568, 106, 106, + /* 20 */ 143, 857, 1, 562, 3, 142, 129, 131, 133, 135, + /* 30 */ 144, 146, 148, 150, 152, 154, 174, 103, 8, 115, + /* 40 */ 104, 139, 127, 125, 156, 161, 157, 162, 166, 119, + /* 50 */ 123, 117, 121, 129, 131, 133, 135, 144, 146, 148, + /* 60 */ 150, 152, 154, 31, 361, 392, 263, 143, 363, 369, + /* 70 */ 374, 97, 142, 148, 150, 152, 154, 68, 75, 377, + /* 80 */ 167, 64, 218, 46, 20, 289, 115, 104, 139, 127, + /* 90 */ 125, 156, 161, 157, 162, 166, 119, 123, 117, 121, + /* 100 */ 129, 131, 133, 135, 144, 146, 148, 150, 152, 154, + /* 110 */ 193, 41, 336, 563, 44, 54, 60, 62, 308, 331, + /* 120 */ 175, 20, 560, 561, 572, 333, 640, 18, 359, 144, + /* 130 */ 146, 148, 150, 152, 154, 143, 181, 179, 303, 18, + /* 140 */ 142, 84, 86, 20, 177, 66, 67, 111, 21, 22, + /* 150 */ 112, 105, 83, 792, 115, 104, 139, 127, 125, 156, + /* 160 */ 161, 157, 162, 166, 119, 123, 117, 121, 129, 131, + /* 170 */ 133, 135, 144, 146, 148, 150, 152, 154, 790, 560, + /* 180 */ 561, 46, 13, 113, 183, 21, 22, 534, 361, 2, + /* 190 */ 3, 14, 363, 369, 374, 338, 361, 690, 544, 542, + /* 200 */ 363, 369, 374, 377, 836, 143, 15, 21, 22, 16, + /* 210 */ 142, 377, 44, 54, 60, 62, 308, 331, 396, 535, + /* 220 */ 17, 9, 191, 333, 115, 104, 139, 127, 125, 156, + /* 230 */ 161, 157, 162, 166, 119, 123, 117, 121, 129, 131, + /* 240 */ 133, 135, 144, 146, 148, 150, 152, 154, 571, 230, + /* 250 */ 340, 343, 143, 20, 536, 537, 538, 142, 402, 337, + /* 260 */ 398, 339, 357, 68, 346, 347, 32, 64, 266, 391, + /* 270 */ 37, 115, 104, 139, 127, 125, 156, 161, 157, 162, + /* 280 */ 166, 119, 123, 117, 121, 129, 131, 133, 135, 144, + /* 290 */ 146, 148, 150, 152, 154, 839, 193, 651, 291, 298, + /* 300 */ 300, 221, 357, 43, 173, 689, 175, 251, 330, 36, + /* 310 */ 37, 106, 232, 40, 335, 58, 137, 21, 22, 330, + /* 320 */ 411, 143, 181, 179, 47, 59, 142, 358, 390, 174, + /* 330 */ 177, 66, 67, 111, 448, 49, 112, 105, 583, 213, + /* 340 */ 115, 104, 139, 127, 125, 156, 161, 157, 162, 166, + /* 350 */ 119, 123, 117, 121, 129, 131, 133, 135, 144, 146, + /* 360 */ 148, 150, 152, 154, 306, 301, 106, 249, 259, 113, + /* 370 */ 183, 793, 70, 253, 281, 219, 20, 106, 20, 11, + /* 380 */ 106, 482, 454, 444, 299, 143, 169, 10, 171, 172, + /* 390 */ 142, 169, 73, 171, 172, 103, 688, 69, 174, 169, + /* 400 */ 252, 171, 172, 12, 115, 104, 139, 127, 125, 156, + /* 410 */ 161, 157, 162, 166, 119, 123, 117, 121, 129, 131, + /* 420 */ 133, 135, 144, 146, 148, 150, 152, 154, 95, 237, + /* 430 */ 313, 20, 143, 295, 244, 424, 169, 142, 171, 172, + /* 440 */ 21, 22, 21, 22, 219, 386, 316, 323, 325, 837, + /* 450 */ 19, 115, 104, 139, 127, 125, 156, 161, 157, 162, + /* 460 */ 166, 119, 123, 117, 121, 129, 131, 133, 135, 144, + /* 470 */ 146, 148, 150, 152, 154, 106, 661, 20, 264, 143, + /* 480 */ 262, 844, 315, 169, 142, 171, 172, 333, 38, 842, + /* 490 */ 10, 356, 348, 184, 421, 21, 22, 282, 115, 104, + /* 500 */ 139, 127, 125, 156, 161, 157, 162, 166, 119, 123, + /* 510 */ 117, 121, 129, 131, 133, 135, 144, 146, 148, 150, + /* 520 */ 152, 154, 69, 254, 262, 251, 143, 639, 663, 35, + /* 530 */ 65, 142, 726, 313, 283, 259, 185, 417, 419, 418, + /* 540 */ 284, 21, 22, 690, 263, 115, 104, 139, 127, 125, + /* 550 */ 156, 161, 157, 162, 166, 119, 123, 117, 121, 129, + /* 560 */ 131, 133, 135, 144, 146, 148, 150, 152, 154, 256, + /* 570 */ 20, 791, 424, 143, 169, 52, 171, 172, 142, 169, + /* 580 */ 24, 171, 172, 247, 53, 315, 26, 169, 263, 171, + /* 590 */ 172, 253, 115, 164, 139, 127, 125, 156, 161, 157, + /* 600 */ 162, 166, 119, 123, 117, 121, 129, 131, 133, 135, + /* 610 */ 144, 146, 148, 150, 152, 154, 426, 349, 252, 425, + /* 620 */ 143, 262, 575, 297, 591, 142, 169, 296, 171, 172, + /* 630 */ 169, 471, 171, 172, 21, 22, 427, 221, 91, 115, + /* 640 */ 227, 139, 127, 125, 156, 161, 157, 162, 166, 119, + /* 650 */ 123, 117, 121, 129, 131, 133, 135, 144, 146, 148, + /* 660 */ 150, 152, 154, 388, 312, 106, 89, 143, 720, 376, + /* 670 */ 387, 170, 142, 487, 666, 248, 320, 216, 319, 217, + /* 680 */ 28, 459, 30, 305, 189, 263, 209, 104, 139, 127, + /* 690 */ 125, 156, 161, 157, 162, 166, 119, 123, 117, 121, + /* 700 */ 129, 131, 133, 135, 144, 146, 148, 150, 152, 154, + /* 710 */ 106, 106, 809, 494, 143, 489, 106, 816, 33, 142, + /* 720 */ 395, 234, 273, 217, 274, 420, 20, 545, 114, 481, + /* 730 */ 137, 429, 576, 321, 116, 139, 127, 125, 156, 161, + /* 740 */ 157, 162, 166, 119, 123, 117, 121, 129, 131, 133, + /* 750 */ 135, 144, 146, 148, 150, 152, 154, 7, 322, 23, + /* 760 */ 25, 27, 394, 68, 415, 416, 10, 64, 197, 477, + /* 770 */ 577, 533, 266, 548, 578, 831, 276, 201, 520, 4, + /* 780 */ 6, 245, 430, 557, 29, 266, 491, 106, 441, 497, + /* 790 */ 21, 22, 205, 168, 443, 195, 193, 531, 276, 448, + /* 800 */ 276, 808, 267, 272, 529, 174, 175, 318, 440, 341, + /* 810 */ 344, 106, 342, 345, 69, 286, 68, 582, 69, 69, + /* 820 */ 64, 540, 181, 179, 541, 328, 302, 366, 217, 118, + /* 830 */ 177, 66, 67, 111, 34, 143, 112, 105, 445, 510, + /* 840 */ 142, 215, 278, 800, 467, 276, 498, 503, 444, 193, + /* 850 */ 106, 219, 486, 443, 42, 73, 231, 73, 45, 175, + /* 860 */ 449, 39, 225, 229, 278, 451, 278, 68, 174, 113, + /* 870 */ 183, 64, 371, 55, 106, 181, 179, 292, 69, 276, + /* 880 */ 276, 69, 48, 177, 66, 67, 111, 224, 276, 112, + /* 890 */ 105, 106, 481, 393, 106, 106, 63, 106, 106, 106, + /* 900 */ 193, 653, 106, 467, 233, 51, 380, 437, 526, 120, + /* 910 */ 175, 278, 122, 124, 219, 126, 128, 130, 69, 453, + /* 920 */ 132, 106, 113, 183, 451, 106, 181, 179, 159, 106, + /* 930 */ 106, 106, 518, 106, 177, 66, 67, 111, 106, 134, + /* 940 */ 112, 105, 422, 136, 106, 278, 278, 138, 141, 145, + /* 950 */ 720, 147, 106, 329, 275, 274, 149, 106, 852, 158, + /* 960 */ 106, 106, 151, 106, 106, 351, 106, 352, 106, 464, + /* 970 */ 153, 106, 106, 113, 183, 155, 106, 106, 163, 165, + /* 980 */ 106, 176, 178, 106, 180, 106, 182, 106, 401, 190, + /* 990 */ 192, 106, 106, 293, 210, 212, 106, 367, 214, 274, + /* 1000 */ 372, 226, 274, 228, 381, 241, 274, 106, 106, 246, + /* 1010 */ 280, 290, 106, 69, 375, 438, 472, 274, 422, 832, + /* 1020 */ 106, 73, 474, 73, 458, 412, 462, 480, 464, 478, + /* 1030 */ 466, 690, 515, 519, 475, 478, 516, 50, 479, 221, + /* 1040 */ 690, 221, 56, 57, 61, 592, 71, 69, 593, 73, + /* 1050 */ 72, 74, 245, 242, 93, 81, 76, 69, 77, 240, + /* 1060 */ 78, 82, 79, 245, 85, 554, 80, 88, 87, 90, + /* 1070 */ 92, 94, 96, 102, 100, 99, 101, 107, 109, 160, + /* 1080 */ 154, 667, 98, 508, 108, 668, 110, 220, 211, 669, + /* 1090 */ 137, 140, 188, 194, 186, 196, 187, 199, 198, 200, + /* 1100 */ 203, 204, 202, 207, 206, 208, 221, 223, 222, 235, + /* 1110 */ 236, 239, 238, 217, 250, 258, 243, 261, 279, 270, + /* 1120 */ 271, 255, 257, 260, 269, 265, 285, 294, 277, 268, + /* 1130 */ 287, 304, 309, 307, 327, 312, 288, 354, 389, 314, + /* 1140 */ 364, 365, 370, 378, 379, 382, 310, 49, 311, 362, + /* 1150 */ 368, 373, 317, 324, 326, 332, 350, 355, 383, 400, + /* 1160 */ 353, 397, 399, 403, 404, 334, 405, 406, 407, 384, + /* 1170 */ 413, 409, 824, 414, 360, 385, 829, 423, 410, 431, + /* 1180 */ 428, 432, 830, 433, 434, 436, 439, 798, 799, 447, + /* 1190 */ 442, 450, 727, 728, 446, 823, 452, 838, 455, 445, + /* 1200 */ 456, 457, 408, 435, 460, 461, 463, 840, 465, 468, + /* 1210 */ 470, 469, 476, 841, 483, 485, 843, 660, 662, 493, + /* 1220 */ 806, 496, 473, 849, 499, 719, 501, 484, 488, 490, + /* 1230 */ 492, 502, 504, 495, 500, 507, 505, 506, 509, 722, + /* 1240 */ 513, 511, 512, 514, 517, 725, 528, 522, 524, 525, + /* 1250 */ 527, 523, 807, 530, 810, 532, 811, 812, 813, 814, + /* 1260 */ 817, 819, 539, 820, 818, 815, 521, 543, 546, 552, + /* 1270 */ 556, 550, 850, 547, 549, 851, 555, 558, 551, 855, + /* 1280 */ 553, 559, +}; +static YYCODETYPE yy_lookahead[] = { + /* 0 */ 21, 9, 23, 70, 71, 72, 73, 74, 75, 76, + /* 10 */ 77, 78, 79, 80, 81, 82, 83, 9, 140, 140, + /* 20 */ 41, 132, 133, 134, 135, 46, 74, 75, 76, 77, + /* 30 */ 78, 79, 80, 81, 82, 83, 158, 158, 138, 60, + /* 40 */ 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + /* 50 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + /* 60 */ 81, 82, 83, 19, 90, 21, 87, 41, 94, 95, + /* 70 */ 96, 192, 46, 80, 81, 82, 83, 19, 174, 105, + /* 80 */ 19, 23, 204, 62, 23, 181, 60, 61, 62, 63, + /* 90 */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + /* 100 */ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + /* 110 */ 52, 90, 91, 0, 93, 94, 95, 96, 97, 98, + /* 120 */ 62, 23, 9, 10, 9, 104, 20, 12, 22, 78, + /* 130 */ 79, 80, 81, 82, 83, 41, 78, 79, 80, 12, + /* 140 */ 46, 78, 79, 23, 86, 87, 88, 89, 87, 88, + /* 150 */ 92, 93, 89, 127, 60, 61, 62, 63, 64, 65, + /* 160 */ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + /* 170 */ 76, 77, 78, 79, 80, 81, 82, 83, 14, 9, + /* 180 */ 10, 62, 15, 125, 126, 87, 88, 140, 90, 134, + /* 190 */ 135, 24, 94, 95, 96, 23, 90, 9, 78, 79, + /* 200 */ 94, 95, 96, 105, 11, 41, 39, 87, 88, 42, + /* 210 */ 46, 105, 93, 94, 95, 96, 97, 98, 17, 99, + /* 220 */ 53, 139, 128, 104, 60, 61, 62, 63, 64, 65, + /* 230 */ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + /* 240 */ 76, 77, 78, 79, 80, 81, 82, 83, 9, 19, + /* 250 */ 78, 79, 41, 23, 207, 208, 209, 46, 57, 87, + /* 260 */ 59, 89, 140, 19, 92, 93, 144, 23, 152, 147, + /* 270 */ 148, 60, 61, 62, 63, 64, 65, 66, 67, 68, + /* 280 */ 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, + /* 290 */ 79, 80, 81, 82, 83, 14, 52, 9, 182, 20, + /* 300 */ 20, 113, 140, 156, 20, 20, 62, 22, 161, 147, + /* 310 */ 148, 140, 20, 155, 156, 26, 200, 87, 88, 161, + /* 320 */ 127, 41, 78, 79, 93, 36, 46, 165, 166, 158, + /* 330 */ 86, 87, 88, 89, 53, 104, 92, 93, 9, 128, + /* 340 */ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + /* 350 */ 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + /* 360 */ 80, 81, 82, 83, 20, 194, 140, 183, 184, 125, + /* 370 */ 126, 127, 146, 88, 19, 204, 23, 140, 23, 31, + /* 380 */ 140, 100, 101, 102, 158, 41, 107, 99, 109, 110, + /* 390 */ 46, 107, 111, 109, 110, 158, 20, 171, 158, 107, + /* 400 */ 115, 109, 110, 170, 60, 61, 62, 63, 64, 65, + /* 410 */ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + /* 420 */ 76, 77, 78, 79, 80, 81, 82, 83, 191, 192, + /* 430 */ 47, 23, 41, 80, 194, 140, 107, 46, 109, 110, + /* 440 */ 87, 88, 87, 88, 204, 62, 100, 101, 102, 11, + /* 450 */ 140, 60, 61, 62, 63, 64, 65, 66, 67, 68, + /* 460 */ 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, + /* 470 */ 79, 80, 81, 82, 83, 140, 9, 23, 21, 41, + /* 480 */ 23, 9, 99, 107, 46, 109, 110, 104, 149, 9, + /* 490 */ 99, 152, 153, 158, 199, 87, 88, 146, 60, 61, + /* 500 */ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + /* 510 */ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + /* 520 */ 82, 83, 171, 115, 23, 22, 41, 20, 9, 22, + /* 530 */ 19, 46, 9, 47, 183, 184, 201, 100, 101, 102, + /* 540 */ 189, 87, 88, 19, 87, 60, 61, 62, 63, 64, + /* 550 */ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + /* 560 */ 75, 76, 77, 78, 79, 80, 81, 82, 83, 115, + /* 570 */ 23, 14, 140, 41, 107, 34, 109, 110, 46, 107, + /* 580 */ 138, 109, 110, 22, 43, 99, 138, 107, 87, 109, + /* 590 */ 110, 88, 60, 61, 62, 63, 64, 65, 66, 67, + /* 600 */ 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, + /* 610 */ 78, 79, 80, 81, 82, 83, 25, 19, 115, 28, + /* 620 */ 41, 23, 9, 108, 113, 46, 107, 112, 109, 110, + /* 630 */ 107, 199, 109, 110, 87, 88, 45, 113, 22, 60, + /* 640 */ 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + /* 650 */ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + /* 660 */ 81, 82, 83, 161, 162, 140, 50, 41, 9, 139, + /* 670 */ 168, 108, 46, 17, 111, 114, 91, 20, 93, 22, + /* 680 */ 138, 22, 142, 158, 127, 87, 129, 61, 62, 63, + /* 690 */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + /* 700 */ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + /* 710 */ 140, 140, 9, 57, 41, 59, 140, 9, 145, 46, + /* 720 */ 143, 20, 20, 22, 22, 49, 23, 19, 158, 158, + /* 730 */ 200, 18, 9, 29, 158, 62, 63, 64, 65, 66, + /* 740 */ 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, + /* 750 */ 77, 78, 79, 80, 81, 82, 83, 11, 54, 13, + /* 760 */ 14, 15, 16, 19, 55, 56, 99, 23, 15, 198, + /* 770 */ 9, 63, 152, 27, 9, 99, 140, 24, 32, 136, + /* 780 */ 137, 122, 205, 37, 141, 152, 130, 140, 211, 146, + /* 790 */ 87, 88, 39, 146, 146, 42, 52, 51, 140, 53, + /* 800 */ 140, 9, 182, 167, 58, 158, 62, 103, 95, 89, + /* 810 */ 89, 140, 92, 92, 171, 182, 19, 9, 171, 171, + /* 820 */ 23, 89, 78, 79, 92, 167, 20, 167, 22, 158, + /* 830 */ 86, 87, 88, 89, 20, 41, 92, 93, 60, 196, + /* 840 */ 46, 194, 206, 130, 196, 140, 100, 101, 102, 52, + /* 850 */ 140, 204, 106, 146, 140, 111, 146, 111, 139, 62, + /* 860 */ 212, 150, 68, 69, 206, 217, 206, 19, 158, 125, + /* 870 */ 126, 23, 167, 48, 140, 78, 79, 80, 171, 140, + /* 880 */ 140, 171, 139, 86, 87, 88, 89, 93, 140, 92, + /* 890 */ 93, 140, 158, 146, 140, 140, 19, 140, 140, 140, + /* 900 */ 52, 123, 140, 196, 194, 44, 167, 167, 116, 158, + /* 910 */ 62, 206, 158, 158, 204, 158, 158, 158, 171, 212, + /* 920 */ 158, 140, 125, 126, 217, 140, 78, 79, 62, 140, + /* 930 */ 140, 140, 198, 140, 86, 87, 88, 89, 140, 158, + /* 940 */ 92, 93, 22, 158, 140, 206, 206, 158, 158, 158, + /* 950 */ 9, 158, 140, 20, 206, 22, 158, 140, 9, 93, + /* 960 */ 140, 140, 158, 140, 140, 20, 140, 22, 140, 140, + /* 970 */ 158, 140, 140, 125, 126, 158, 140, 140, 158, 158, + /* 980 */ 140, 158, 158, 140, 158, 140, 158, 140, 146, 158, + /* 990 */ 158, 140, 140, 140, 158, 158, 140, 20, 158, 22, + /* 1000 */ 20, 158, 22, 158, 20, 158, 22, 140, 140, 158, + /* 1010 */ 158, 158, 140, 171, 158, 20, 20, 22, 22, 99, + /* 1020 */ 140, 111, 146, 111, 195, 158, 158, 20, 140, 22, + /* 1030 */ 158, 103, 146, 20, 124, 22, 124, 164, 158, 113, + /* 1040 */ 114, 113, 157, 139, 139, 113, 172, 171, 113, 111, + /* 1050 */ 171, 173, 122, 119, 117, 180, 175, 171, 176, 120, + /* 1060 */ 177, 121, 178, 122, 89, 116, 179, 154, 89, 154, + /* 1070 */ 154, 118, 22, 151, 98, 157, 23, 113, 113, 93, + /* 1080 */ 83, 111, 193, 195, 140, 111, 140, 140, 127, 111, + /* 1090 */ 200, 200, 14, 19, 202, 20, 203, 140, 22, 20, + /* 1100 */ 140, 20, 22, 140, 22, 20, 113, 186, 140, 140, + /* 1110 */ 186, 157, 193, 22, 185, 115, 118, 186, 99, 116, + /* 1120 */ 19, 140, 140, 140, 188, 140, 20, 113, 157, 187, + /* 1130 */ 187, 20, 140, 139, 19, 162, 188, 20, 166, 140, + /* 1140 */ 48, 19, 19, 48, 19, 97, 159, 104, 160, 140, + /* 1150 */ 139, 139, 163, 163, 163, 151, 154, 152, 140, 21, + /* 1160 */ 154, 140, 140, 140, 213, 164, 214, 99, 140, 159, + /* 1170 */ 40, 215, 11, 38, 166, 160, 99, 140, 216, 130, + /* 1180 */ 49, 140, 99, 99, 140, 19, 139, 9, 130, 169, + /* 1190 */ 11, 14, 123, 123, 170, 9, 9, 14, 169, 60, + /* 1200 */ 140, 103, 186, 186, 140, 63, 176, 9, 63, 123, + /* 1210 */ 19, 140, 19, 9, 114, 176, 9, 9, 9, 186, + /* 1220 */ 9, 186, 197, 9, 114, 9, 186, 140, 140, 140, + /* 1230 */ 140, 176, 169, 140, 140, 103, 140, 186, 176, 9, + /* 1240 */ 186, 123, 140, 197, 19, 9, 87, 140, 114, 140, + /* 1250 */ 35, 186, 9, 140, 9, 152, 9, 9, 9, 9, + /* 1260 */ 9, 9, 210, 9, 9, 9, 169, 210, 140, 140, + /* 1270 */ 33, 152, 9, 20, 218, 9, 152, 218, 21, 9, + /* 1280 */ 219, 140, +}; +#define YY_SHIFT_USE_DFLT (-68) +static short yy_shift_ofst[] = { + /* 0 */ 170, 113, -68, 746, -8, -68, 8, 127, 288, 239, + /* 10 */ 348, 167, -68, -68, -68, -68, -68, -68, 547, -68, + /* 20 */ -68, -68, -68, 115, 613, 115, 723, 115, 761, 44, + /* 30 */ 765, 547, 507, 814, 808, 98, -68, 501, -68, 21, + /* 40 */ -68, 547, 119, -68, 667, -68, 231, 667, -68, 861, + /* 50 */ -68, 541, -68, -68, 825, 289, 667, -68, -68, -68, + /* 60 */ 667, -68, 877, 848, 511, 58, 932, 935, 744, -68, + /* 70 */ 279, 938, -68, 515, -68, 561, 930, 934, 939, 937, + /* 80 */ 940, -68, 63, -68, 975, -68, 979, -68, 616, 63, + /* 90 */ -68, 63, -68, 953, 848, 1050, 848, 976, 289, -68, + /* 100 */ 1053, -68, -68, 485, 848, -68, 964, 547, 965, 547, + /* 110 */ -68, -68, -68, -68, 673, 848, 626, 848, -48, 848, + /* 120 */ -48, 848, -48, 848, -48, 848, -67, 848, -67, 848, + /* 130 */ 51, 848, 51, 848, 51, 848, 51, 848, -67, 794, + /* 140 */ 848, -67, -68, -68, 848, -7, 848, -7, 848, 997, + /* 150 */ 848, 997, 848, 997, 848, -68, -68, 866, -68, 986, + /* 160 */ -68, -68, 848, 532, 848, -67, 61, 744, 284, 563, + /* 170 */ 970, 974, 978, -68, 485, 848, 673, 848, -68, 848, + /* 180 */ -68, 848, -68, 244, 26, 961, 557, 1078, -68, 848, + /* 190 */ 94, 848, 485, 1074, 753, 1075, -68, 1076, 547, 1079, + /* 200 */ -68, 1080, 547, 1081, -68, 1082, 547, 1085, -68, 848, + /* 210 */ 164, 848, 211, 848, 485, 657, -68, 848, -68, -68, + /* 220 */ 993, 547, -68, -68, -68, 848, 579, 848, 673, 230, + /* 230 */ 744, 292, -68, 701, -68, 993, -68, 976, 289, -68, + /* 240 */ 848, 485, 998, 848, 1091, 848, 485, -68, -68, 503, + /* 250 */ -68, -68, -68, 408, -68, 454, -68, 1000, -68, 355, + /* 260 */ 993, 457, -68, -68, 547, -68, -68, 1019, 1003, -68, + /* 270 */ 1101, 547, 702, -68, 547, -68, 289, -68, -68, 848, + /* 280 */ 485, 938, 376, 285, 1106, 457, 1019, 1003, -68, 797, + /* 290 */ -21, -68, -68, 1014, 353, -68, -68, -68, -68, 280, + /* 300 */ -68, 806, -68, 1111, -68, 344, 667, -68, 547, 1115, + /* 310 */ -68, 486, -68, 547, -68, 346, 704, -68, 585, -68, + /* 320 */ -68, -68, -68, 704, -68, 704, -68, 547, 933, -68, + /* 330 */ -68, 1053, -68, 861, -68, -68, 172, -68, -68, -68, + /* 340 */ 720, -68, -68, 721, -68, -68, -68, -68, 598, 63, + /* 350 */ 945, -68, 63, 1117, -68, -68, -68, -68, 106, -26, + /* 360 */ -68, 547, -68, 1092, 1122, 547, 977, 667, -68, 1123, + /* 370 */ 547, 980, 667, -68, 848, 391, -68, 1095, 1125, 547, + /* 380 */ 984, 1048, 547, 1115, -68, 383, 1043, -68, -68, -68, + /* 390 */ -68, -68, 938, 329, 713, 201, 547, -68, 547, 1138, + /* 400 */ 938, 467, 547, 591, 437, 1068, 547, 993, 1130, 193, + /* 410 */ 1161, 848, 438, 1135, 709, -68, -68, 1077, 1083, 676, + /* 420 */ 547, 920, 547, -68, -68, -68, -68, 1131, -68, -68, + /* 430 */ 1049, 547, 1084, 547, 524, 1166, 547, 995, 288, 1178, + /* 440 */ 1058, 1179, 281, 472, 778, 167, -68, 1069, 1070, 1177, + /* 450 */ 1186, 1187, 281, 1183, 1139, 547, 1098, 547, 659, 547, + /* 460 */ 1142, 848, 485, 1198, 1145, 848, 485, 1086, 547, 1191, + /* 470 */ 547, 996, -68, 910, 480, 1193, 848, 1007, 848, 485, + /* 480 */ 1204, 485, 1100, 547, 941, 1207, 656, 547, 1208, 547, + /* 490 */ 1209, 547, 188, 1211, 547, 188, 1214, 519, 1110, 547, + /* 500 */ 993, 941, 1216, 1139, 547, 928, 1132, 547, 659, 1230, + /* 510 */ 1118, 547, 993, 1191, 912, 523, 1225, 848, 1013, 1236, + /* 520 */ 1139, 547, 926, 1134, 547, 792, 1215, 1159, 1243, 703, + /* 530 */ 1245, 501, 708, 120, 1247, 1248, 1249, 1250, 732, 1251, + /* 540 */ 1252, 1254, 732, 1255, -68, 547, 1253, 1256, 1237, 501, + /* 550 */ 1257, 547, 949, 1263, 501, 1266, -68, 1237, 547, 1270, + /* 560 */ -68, -68, -68, +}; +#define YY_REDUCE_USE_DFLT (-123) +static short yy_reduce_ofst[] = { + /* 0 */ -111, 55, -123, 643, -123, -123, -123, -100, 82, -123, + /* 10 */ -123, 233, -123, -123, -123, -123, -123, -123, 310, -123, + /* 20 */ -123, -123, -123, 442, -123, 448, -123, 542, -123, 540, + /* 30 */ -123, 122, 573, -123, -123, 162, -123, 339, 711, 158, + /* 40 */ -123, 714, 147, -123, 719, -123, -123, 743, -123, 873, + /* 50 */ -123, -123, -123, -123, -123, 885, 904, -123, -123, -123, + /* 60 */ 905, -123, -123, 525, -123, 171, -123, -123, 226, -123, + /* 70 */ 874, 879, -123, 878, -96, 881, 882, 883, 884, 887, + /* 80 */ 875, -123, 913, -123, -123, -123, -123, -123, -123, 915, + /* 90 */ -123, 916, -123, -123, 237, -123, -121, 889, 918, -123, + /* 100 */ 922, -123, -123, 890, 570, -123, -123, 944, -123, 946, + /* 110 */ -123, -123, -123, -123, 890, 576, 890, 671, 890, 751, + /* 120 */ 890, 754, 890, 755, 890, 757, 890, 758, 890, 759, + /* 130 */ 890, 762, 890, 781, 890, 785, 890, 789, 890, 891, + /* 140 */ 790, 890, -123, -123, 791, 890, 793, 890, 798, 890, + /* 150 */ 804, 890, 812, 890, 817, 890, -123, -123, -123, -123, + /* 160 */ -123, -123, 820, 890, 821, 890, 947, 647, 874, -123, + /* 170 */ -123, -123, -123, -123, 890, 823, 890, 824, 890, 826, + /* 180 */ 890, 828, 890, 335, 890, 892, 893, -123, -123, 831, + /* 190 */ 890, 832, 890, -123, -123, -123, -123, -123, 957, -123, + /* 200 */ -123, -123, 960, -123, -123, -123, 963, -123, -123, 836, + /* 210 */ 890, 837, 890, 840, 890, -123, -123, -122, -123, -123, + /* 220 */ 921, 968, -123, -123, -123, 843, 890, 845, 890, 969, + /* 230 */ 710, 874, -123, -123, -123, 924, -123, 919, 954, -123, + /* 240 */ 847, 890, -123, 240, -123, 851, 890, -123, 184, 929, + /* 250 */ -123, -123, -123, 981, -123, 982, -123, -123, -123, 983, + /* 260 */ 931, 620, -123, -123, 985, -123, -123, 942, 936, -123, + /* 270 */ -123, 636, -123, -123, 748, -123, 971, -123, -123, 852, + /* 280 */ 890, 351, 874, 929, -123, 633, 943, 948, -123, 853, + /* 290 */ 116, -123, -123, -123, 944, -123, -123, -123, -123, 890, + /* 300 */ -123, -123, -123, -123, -123, 890, 994, -123, 992, 987, + /* 310 */ 988, 973, -123, 999, -123, -123, 989, -123, -123, -123, + /* 320 */ -123, -123, -123, 990, -123, 991, -123, 658, -123, -123, + /* 330 */ -123, 1004, -123, 1001, -123, -123, -123, -123, -123, -123, + /* 340 */ -123, -123, -123, -123, -123, -123, -123, -123, 1005, 1002, + /* 350 */ -123, -123, 1006, -123, -123, -123, -123, -123, 972, 1008, + /* 360 */ -123, 1009, -123, -123, -123, 660, -123, 1011, -123, -123, + /* 370 */ 705, -123, 1012, -123, 856, 530, -123, -123, -123, 739, + /* 380 */ -123, -123, 1018, 1010, 1015, 502, -123, -123, -123, -123, + /* 390 */ -123, -123, 747, 874, 577, -123, 1021, -123, 1022, -123, + /* 400 */ 842, 874, 1023, 951, 952, -123, 1028, 1016, 956, 962, + /* 410 */ -123, 867, 890, -123, -123, -123, -123, -123, -123, -123, + /* 420 */ 295, -123, 1037, -123, -123, -123, -123, -123, -123, -123, + /* 430 */ -123, 1041, -123, 1044, 1017, -123, 740, -123, 1047, -123, + /* 440 */ -123, -123, 648, 874, 1020, 1024, -123, -123, -123, -123, + /* 450 */ -123, -123, 707, -123, 1029, 1060, -123, 829, 1030, 1064, + /* 460 */ -123, 868, 890, -123, -123, 872, 890, -123, 1071, 1025, + /* 470 */ 432, -123, -123, 876, 874, -123, 571, -123, 880, 890, + /* 480 */ -123, 890, -123, 1087, 1039, -123, -123, 1088, -123, 1089, + /* 490 */ -123, 1090, 1033, -123, 1093, 1035, -123, 874, -123, 1094, + /* 500 */ 1040, 1055, -123, 1063, 1096, 1051, -123, 888, 1062, -123, + /* 510 */ -123, 1102, 1054, 1046, 886, 874, -123, 734, -123, -123, + /* 520 */ 1097, 1107, 1065, -123, 1109, -123, -123, -123, -123, 1113, + /* 530 */ -123, 1103, -123, 47, -123, -123, -123, -123, 1052, -123, + /* 540 */ -123, -123, 1057, -123, -123, 1128, -123, -123, 1056, 1119, + /* 550 */ -123, 1129, 1061, -123, 1124, -123, -123, 1059, 1141, -123, + /* 560 */ -123, -123, -123, +}; +static YYACTIONTYPE yy_default[] = { + /* 0 */ 570, 570, 564, 856, 856, 566, 856, 572, 856, 856, + /* 10 */ 856, 856, 652, 655, 656, 657, 658, 659, 573, 574, + /* 20 */ 591, 592, 593, 856, 856, 856, 856, 856, 856, 856, + /* 30 */ 856, 856, 856, 856, 856, 856, 584, 594, 604, 586, + /* 40 */ 603, 856, 856, 605, 651, 616, 856, 651, 617, 636, + /* 50 */ 634, 856, 637, 638, 856, 708, 651, 618, 706, 707, + /* 60 */ 651, 619, 856, 856, 737, 797, 743, 738, 856, 664, + /* 70 */ 856, 856, 665, 673, 675, 682, 720, 711, 713, 701, + /* 80 */ 715, 670, 856, 600, 856, 601, 856, 602, 716, 856, + /* 90 */ 717, 856, 718, 856, 856, 702, 856, 709, 708, 703, + /* 100 */ 856, 588, 710, 705, 856, 736, 856, 856, 739, 856, + /* 110 */ 740, 741, 742, 744, 747, 856, 748, 856, 749, 856, + /* 120 */ 750, 856, 751, 856, 752, 856, 753, 856, 754, 856, + /* 130 */ 755, 856, 756, 856, 757, 856, 758, 856, 759, 856, + /* 140 */ 856, 760, 761, 762, 856, 763, 856, 764, 856, 765, + /* 150 */ 856, 766, 856, 767, 856, 768, 769, 856, 770, 856, + /* 160 */ 773, 771, 856, 856, 856, 779, 856, 797, 856, 856, + /* 170 */ 856, 856, 856, 782, 796, 856, 774, 856, 775, 856, + /* 180 */ 776, 856, 777, 856, 856, 856, 856, 856, 787, 856, + /* 190 */ 856, 856, 788, 856, 856, 856, 845, 856, 856, 856, + /* 200 */ 846, 856, 856, 856, 847, 856, 856, 856, 848, 856, + /* 210 */ 856, 856, 856, 856, 789, 856, 781, 797, 794, 795, + /* 220 */ 690, 856, 691, 785, 772, 856, 856, 856, 780, 856, + /* 230 */ 797, 856, 784, 856, 783, 690, 786, 709, 708, 704, + /* 240 */ 856, 714, 856, 797, 712, 856, 721, 674, 685, 683, + /* 250 */ 684, 692, 693, 856, 694, 856, 695, 856, 696, 856, + /* 260 */ 690, 681, 589, 590, 856, 679, 680, 698, 700, 686, + /* 270 */ 856, 856, 856, 699, 856, 803, 708, 805, 804, 856, + /* 280 */ 697, 685, 856, 856, 856, 681, 698, 700, 687, 856, + /* 290 */ 681, 676, 677, 856, 856, 678, 671, 672, 778, 856, + /* 300 */ 735, 856, 745, 856, 746, 856, 651, 620, 856, 801, + /* 310 */ 624, 621, 625, 856, 626, 856, 856, 627, 856, 630, + /* 320 */ 631, 632, 633, 856, 628, 856, 629, 856, 856, 802, + /* 330 */ 622, 856, 623, 636, 635, 606, 856, 607, 608, 609, + /* 340 */ 856, 610, 613, 856, 611, 614, 612, 615, 595, 856, + /* 350 */ 856, 596, 856, 856, 597, 599, 598, 587, 856, 856, + /* 360 */ 641, 856, 644, 856, 856, 856, 856, 651, 645, 856, + /* 370 */ 856, 856, 651, 646, 856, 651, 647, 856, 856, 856, + /* 380 */ 856, 856, 856, 801, 624, 649, 856, 648, 650, 642, + /* 390 */ 643, 585, 856, 856, 581, 856, 856, 579, 856, 856, + /* 400 */ 856, 856, 856, 828, 856, 856, 856, 690, 833, 856, + /* 410 */ 856, 856, 856, 856, 856, 834, 835, 856, 856, 856, + /* 420 */ 856, 856, 856, 733, 734, 825, 826, 856, 827, 580, + /* 430 */ 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, + /* 440 */ 856, 856, 856, 856, 856, 856, 654, 856, 856, 856, + /* 450 */ 856, 856, 856, 856, 653, 856, 856, 856, 856, 856, + /* 460 */ 856, 856, 723, 856, 856, 856, 724, 856, 856, 731, + /* 470 */ 856, 856, 732, 856, 856, 856, 856, 856, 856, 729, + /* 480 */ 856, 730, 856, 856, 856, 856, 856, 856, 856, 856, + /* 490 */ 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, + /* 500 */ 690, 856, 856, 653, 856, 856, 856, 856, 856, 856, + /* 510 */ 856, 856, 690, 731, 856, 856, 856, 856, 856, 856, + /* 520 */ 653, 856, 856, 856, 856, 856, 856, 856, 856, 856, + /* 530 */ 856, 856, 856, 822, 856, 856, 856, 856, 856, 856, + /* 540 */ 856, 856, 856, 856, 821, 856, 856, 856, 854, 856, + /* 550 */ 856, 856, 856, 856, 856, 856, 853, 854, 856, 856, + /* 560 */ 567, 569, 565, +}; +#define YY_SZ_ACTTAB (sizeof(yy_action)/sizeof(yy_action[0])) + +/* The next table maps tokens into fallback tokens. If a construct +** like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammer, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { + 0, /* $ => nothing */ + 0, /* END_OF_FILE => nothing */ + 0, /* ILLEGAL => nothing */ + 0, /* SPACE => nothing */ + 0, /* UNCLOSED_STRING => nothing */ + 0, /* COMMENT => nothing */ + 0, /* FUNCTION => nothing */ + 0, /* COLUMN => nothing */ + 0, /* AGG_FUNCTION => nothing */ + 0, /* SEMI => nothing */ + 23, /* EXPLAIN => ID */ + 23, /* BEGIN => ID */ + 0, /* TRANSACTION => nothing */ + 0, /* COMMIT => nothing */ + 23, /* END => ID */ + 0, /* ROLLBACK => nothing */ + 0, /* CREATE => nothing */ + 0, /* TABLE => nothing */ + 23, /* TEMP => ID */ + 0, /* LP => nothing */ + 0, /* RP => nothing */ + 0, /* AS => nothing */ + 0, /* COMMA => nothing */ + 0, /* ID => nothing */ + 23, /* ABORT => ID */ + 23, /* AFTER => ID */ + 23, /* ASC => ID */ + 23, /* ATTACH => ID */ + 23, /* BEFORE => ID */ + 23, /* CASCADE => ID */ + 23, /* CLUSTER => ID */ + 23, /* CONFLICT => ID */ + 23, /* COPY => ID */ + 23, /* DATABASE => ID */ + 23, /* DEFERRED => ID */ + 23, /* DELIMITERS => ID */ + 23, /* DESC => ID */ + 23, /* DETACH => ID */ + 23, /* EACH => ID */ + 23, /* FAIL => ID */ + 23, /* FOR => ID */ + 23, /* GLOB => ID */ + 23, /* IGNORE => ID */ + 23, /* IMMEDIATE => ID */ + 23, /* INITIALLY => ID */ + 23, /* INSTEAD => ID */ + 23, /* LIKE => ID */ + 23, /* MATCH => ID */ + 23, /* KEY => ID */ + 23, /* OF => ID */ + 23, /* OFFSET => ID */ + 23, /* PRAGMA => ID */ + 23, /* RAISE => ID */ + 23, /* REPLACE => ID */ + 23, /* RESTRICT => ID */ + 23, /* ROW => ID */ + 23, /* STATEMENT => ID */ + 23, /* TRIGGER => ID */ + 23, /* VACUUM => ID */ + 23, /* VIEW => ID */ + 0, /* OR => nothing */ + 0, /* AND => nothing */ + 0, /* NOT => nothing */ + 0, /* EQ => nothing */ + 0, /* NE => nothing */ + 0, /* ISNULL => nothing */ + 0, /* NOTNULL => nothing */ + 0, /* IS => nothing */ + 0, /* BETWEEN => nothing */ + 0, /* IN => nothing */ + 0, /* GT => nothing */ + 0, /* GE => nothing */ + 0, /* LT => nothing */ + 0, /* LE => nothing */ + 0, /* BITAND => nothing */ + 0, /* BITOR => nothing */ + 0, /* LSHIFT => nothing */ + 0, /* RSHIFT => nothing */ + 0, /* PLUS => nothing */ + 0, /* MINUS => nothing */ + 0, /* STAR => nothing */ + 0, /* SLASH => nothing */ + 0, /* REM => nothing */ + 0, /* CONCAT => nothing */ + 0, /* UMINUS => nothing */ + 0, /* UPLUS => nothing */ + 0, /* BITNOT => nothing */ + 0, /* STRING => nothing */ + 0, /* JOIN_KW => nothing */ + 0, /* INTEGER => nothing */ + 0, /* CONSTRAINT => nothing */ + 0, /* DEFAULT => nothing */ + 0, /* FLOAT => nothing */ + 0, /* NULL => nothing */ + 0, /* PRIMARY => nothing */ + 0, /* UNIQUE => nothing */ + 0, /* CHECK => nothing */ + 0, /* REFERENCES => nothing */ + 0, /* COLLATE => nothing */ + 0, /* ON => nothing */ + 0, /* DELETE => nothing */ + 0, /* UPDATE => nothing */ + 0, /* INSERT => nothing */ + 0, /* SET => nothing */ + 0, /* DEFERRABLE => nothing */ + 0, /* FOREIGN => nothing */ + 0, /* DROP => nothing */ + 0, /* UNION => nothing */ + 0, /* ALL => nothing */ + 0, /* INTERSECT => nothing */ + 0, /* EXCEPT => nothing */ + 0, /* SELECT => nothing */ + 0, /* DISTINCT => nothing */ + 0, /* DOT => nothing */ + 0, /* FROM => nothing */ + 0, /* JOIN => nothing */ + 0, /* USING => nothing */ + 0, /* ORDER => nothing */ + 0, /* BY => nothing */ + 0, /* GROUP => nothing */ + 0, /* HAVING => nothing */ + 0, /* LIMIT => nothing */ + 0, /* WHERE => nothing */ + 0, /* INTO => nothing */ + 0, /* VALUES => nothing */ + 0, /* VARIABLE => nothing */ + 0, /* CASE => nothing */ + 0, /* WHEN => nothing */ + 0, /* THEN => nothing */ + 0, /* ELSE => nothing */ + 0, /* INDEX => nothing */ +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +*/ +struct yyStackEntry { + int stateno; /* The state-number */ + int major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + int yyidx; /* Index of top element in stack */ + int yyerrcnt; /* Shifts left before out of the error */ + sqliteParserARG_SDECL /* A place to hold %extra_argument */ + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +**
      +**
    • A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +**
    • A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +**
    +** +** Outputs: +** None. +*/ +void sqliteParserTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *yyTokenName[] = { + "$", "END_OF_FILE", "ILLEGAL", "SPACE", + "UNCLOSED_STRING", "COMMENT", "FUNCTION", "COLUMN", + "AGG_FUNCTION", "SEMI", "EXPLAIN", "BEGIN", + "TRANSACTION", "COMMIT", "END", "ROLLBACK", + "CREATE", "TABLE", "TEMP", "LP", + "RP", "AS", "COMMA", "ID", + "ABORT", "AFTER", "ASC", "ATTACH", + "BEFORE", "CASCADE", "CLUSTER", "CONFLICT", + "COPY", "DATABASE", "DEFERRED", "DELIMITERS", + "DESC", "DETACH", "EACH", "FAIL", + "FOR", "GLOB", "IGNORE", "IMMEDIATE", + "INITIALLY", "INSTEAD", "LIKE", "MATCH", + "KEY", "OF", "OFFSET", "PRAGMA", + "RAISE", "REPLACE", "RESTRICT", "ROW", + "STATEMENT", "TRIGGER", "VACUUM", "VIEW", + "OR", "AND", "NOT", "EQ", + "NE", "ISNULL", "NOTNULL", "IS", + "BETWEEN", "IN", "GT", "GE", + "LT", "LE", "BITAND", "BITOR", + "LSHIFT", "RSHIFT", "PLUS", "MINUS", + "STAR", "SLASH", "REM", "CONCAT", + "UMINUS", "UPLUS", "BITNOT", "STRING", + "JOIN_KW", "INTEGER", "CONSTRAINT", "DEFAULT", + "FLOAT", "NULL", "PRIMARY", "UNIQUE", + "CHECK", "REFERENCES", "COLLATE", "ON", + "DELETE", "UPDATE", "INSERT", "SET", + "DEFERRABLE", "FOREIGN", "DROP", "UNION", + "ALL", "INTERSECT", "EXCEPT", "SELECT", + "DISTINCT", "DOT", "FROM", "JOIN", + "USING", "ORDER", "BY", "GROUP", + "HAVING", "LIMIT", "WHERE", "INTO", + "VALUES", "VARIABLE", "CASE", "WHEN", + "THEN", "ELSE", "INDEX", "error", + "input", "cmdlist", "ecmd", "explain", + "cmdx", "cmd", "trans_opt", "onconf", + "nm", "create_table", "create_table_args", "temp", + "columnlist", "conslist_opt", "select", "column", + "columnid", "type", "carglist", "id", + "ids", "typename", "signed", "carg", + "ccons", "sortorder", "expr", "idxlist_opt", + "refargs", "defer_subclause", "refarg", "refact", + "init_deferred_pred_opt", "conslist", "tcons", "idxlist", + "defer_subclause_opt", "orconf", "resolvetype", "oneselect", + "multiselect_op", "distinct", "selcollist", "from", + "where_opt", "groupby_opt", "having_opt", "orderby_opt", + "limit_opt", "sclp", "as", "seltablist", + "stl_prefix", "joinop", "dbnm", "on_opt", + "using_opt", "seltablist_paren", "joinop2", "sortlist", + "sortitem", "collate", "exprlist", "setlist", + "insert_cmd", "inscollist_opt", "itemlist", "inscollist", + "likeop", "case_operand", "case_exprlist", "case_else", + "expritem", "uniqueflag", "idxitem", "plus_num", + "minus_num", "plus_opt", "number", "trigger_decl", + "trigger_cmd_list", "trigger_time", "trigger_event", "foreach_clause", + "when_clause", "trigger_cmd", "database_kw_opt", "key_opt", +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *yyRuleName[] = { + /* 0 */ "input ::= cmdlist", + /* 1 */ "cmdlist ::= cmdlist ecmd", + /* 2 */ "cmdlist ::= ecmd", + /* 3 */ "ecmd ::= explain cmdx SEMI", + /* 4 */ "ecmd ::= SEMI", + /* 5 */ "cmdx ::= cmd", + /* 6 */ "explain ::= EXPLAIN", + /* 7 */ "explain ::=", + /* 8 */ "cmd ::= BEGIN trans_opt onconf", + /* 9 */ "trans_opt ::=", + /* 10 */ "trans_opt ::= TRANSACTION", + /* 11 */ "trans_opt ::= TRANSACTION nm", + /* 12 */ "cmd ::= COMMIT trans_opt", + /* 13 */ "cmd ::= END trans_opt", + /* 14 */ "cmd ::= ROLLBACK trans_opt", + /* 15 */ "cmd ::= create_table create_table_args", + /* 16 */ "create_table ::= CREATE temp TABLE nm", + /* 17 */ "temp ::= TEMP", + /* 18 */ "temp ::=", + /* 19 */ "create_table_args ::= LP columnlist conslist_opt RP", + /* 20 */ "create_table_args ::= AS select", + /* 21 */ "columnlist ::= columnlist COMMA column", + /* 22 */ "columnlist ::= column", + /* 23 */ "column ::= columnid type carglist", + /* 24 */ "columnid ::= nm", + /* 25 */ "id ::= ID", + /* 26 */ "ids ::= ID", + /* 27 */ "ids ::= STRING", + /* 28 */ "nm ::= ID", + /* 29 */ "nm ::= STRING", + /* 30 */ "nm ::= JOIN_KW", + /* 31 */ "type ::=", + /* 32 */ "type ::= typename", + /* 33 */ "type ::= typename LP signed RP", + /* 34 */ "type ::= typename LP signed COMMA signed RP", + /* 35 */ "typename ::= ids", + /* 36 */ "typename ::= typename ids", + /* 37 */ "signed ::= INTEGER", + /* 38 */ "signed ::= PLUS INTEGER", + /* 39 */ "signed ::= MINUS INTEGER", + /* 40 */ "carglist ::= carglist carg", + /* 41 */ "carglist ::=", + /* 42 */ "carg ::= CONSTRAINT nm ccons", + /* 43 */ "carg ::= ccons", + /* 44 */ "carg ::= DEFAULT STRING", + /* 45 */ "carg ::= DEFAULT ID", + /* 46 */ "carg ::= DEFAULT INTEGER", + /* 47 */ "carg ::= DEFAULT PLUS INTEGER", + /* 48 */ "carg ::= DEFAULT MINUS INTEGER", + /* 49 */ "carg ::= DEFAULT FLOAT", + /* 50 */ "carg ::= DEFAULT PLUS FLOAT", + /* 51 */ "carg ::= DEFAULT MINUS FLOAT", + /* 52 */ "carg ::= DEFAULT NULL", + /* 53 */ "ccons ::= NULL onconf", + /* 54 */ "ccons ::= NOT NULL onconf", + /* 55 */ "ccons ::= PRIMARY KEY sortorder onconf", + /* 56 */ "ccons ::= UNIQUE onconf", + /* 57 */ "ccons ::= CHECK LP expr RP onconf", + /* 58 */ "ccons ::= REFERENCES nm idxlist_opt refargs", + /* 59 */ "ccons ::= defer_subclause", + /* 60 */ "ccons ::= COLLATE id", + /* 61 */ "refargs ::=", + /* 62 */ "refargs ::= refargs refarg", + /* 63 */ "refarg ::= MATCH nm", + /* 64 */ "refarg ::= ON DELETE refact", + /* 65 */ "refarg ::= ON UPDATE refact", + /* 66 */ "refarg ::= ON INSERT refact", + /* 67 */ "refact ::= SET NULL", + /* 68 */ "refact ::= SET DEFAULT", + /* 69 */ "refact ::= CASCADE", + /* 70 */ "refact ::= RESTRICT", + /* 71 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", + /* 72 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", + /* 73 */ "init_deferred_pred_opt ::=", + /* 74 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", + /* 75 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", + /* 76 */ "conslist_opt ::=", + /* 77 */ "conslist_opt ::= COMMA conslist", + /* 78 */ "conslist ::= conslist COMMA tcons", + /* 79 */ "conslist ::= conslist tcons", + /* 80 */ "conslist ::= tcons", + /* 81 */ "tcons ::= CONSTRAINT nm", + /* 82 */ "tcons ::= PRIMARY KEY LP idxlist RP onconf", + /* 83 */ "tcons ::= UNIQUE LP idxlist RP onconf", + /* 84 */ "tcons ::= CHECK expr onconf", + /* 85 */ "tcons ::= FOREIGN KEY LP idxlist RP REFERENCES nm idxlist_opt refargs defer_subclause_opt", + /* 86 */ "defer_subclause_opt ::=", + /* 87 */ "defer_subclause_opt ::= defer_subclause", + /* 88 */ "onconf ::=", + /* 89 */ "onconf ::= ON CONFLICT resolvetype", + /* 90 */ "orconf ::=", + /* 91 */ "orconf ::= OR resolvetype", + /* 92 */ "resolvetype ::= ROLLBACK", + /* 93 */ "resolvetype ::= ABORT", + /* 94 */ "resolvetype ::= FAIL", + /* 95 */ "resolvetype ::= IGNORE", + /* 96 */ "resolvetype ::= REPLACE", + /* 97 */ "cmd ::= DROP TABLE nm", + /* 98 */ "cmd ::= CREATE temp VIEW nm AS select", + /* 99 */ "cmd ::= DROP VIEW nm", + /* 100 */ "cmd ::= select", + /* 101 */ "select ::= oneselect", + /* 102 */ "select ::= select multiselect_op oneselect", + /* 103 */ "multiselect_op ::= UNION", + /* 104 */ "multiselect_op ::= UNION ALL", + /* 105 */ "multiselect_op ::= INTERSECT", + /* 106 */ "multiselect_op ::= EXCEPT", + /* 107 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", + /* 108 */ "distinct ::= DISTINCT", + /* 109 */ "distinct ::= ALL", + /* 110 */ "distinct ::=", + /* 111 */ "sclp ::= selcollist COMMA", + /* 112 */ "sclp ::=", + /* 113 */ "selcollist ::= sclp expr as", + /* 114 */ "selcollist ::= sclp STAR", + /* 115 */ "selcollist ::= sclp nm DOT STAR", + /* 116 */ "as ::= AS nm", + /* 117 */ "as ::= ids", + /* 118 */ "as ::=", + /* 119 */ "from ::=", + /* 120 */ "from ::= FROM seltablist", + /* 121 */ "stl_prefix ::= seltablist joinop", + /* 122 */ "stl_prefix ::=", + /* 123 */ "seltablist ::= stl_prefix nm dbnm as on_opt using_opt", + /* 124 */ "seltablist ::= stl_prefix LP seltablist_paren RP as on_opt using_opt", + /* 125 */ "seltablist_paren ::= select", + /* 126 */ "seltablist_paren ::= seltablist", + /* 127 */ "dbnm ::=", + /* 128 */ "dbnm ::= DOT nm", + /* 129 */ "joinop ::= COMMA", + /* 130 */ "joinop ::= JOIN", + /* 131 */ "joinop ::= JOIN_KW JOIN", + /* 132 */ "joinop ::= JOIN_KW nm JOIN", + /* 133 */ "joinop ::= JOIN_KW nm nm JOIN", + /* 134 */ "on_opt ::= ON expr", + /* 135 */ "on_opt ::=", + /* 136 */ "using_opt ::= USING LP idxlist RP", + /* 137 */ "using_opt ::=", + /* 138 */ "orderby_opt ::=", + /* 139 */ "orderby_opt ::= ORDER BY sortlist", + /* 140 */ "sortlist ::= sortlist COMMA sortitem collate sortorder", + /* 141 */ "sortlist ::= sortitem collate sortorder", + /* 142 */ "sortitem ::= expr", + /* 143 */ "sortorder ::= ASC", + /* 144 */ "sortorder ::= DESC", + /* 145 */ "sortorder ::=", + /* 146 */ "collate ::=", + /* 147 */ "collate ::= COLLATE id", + /* 148 */ "groupby_opt ::=", + /* 149 */ "groupby_opt ::= GROUP BY exprlist", + /* 150 */ "having_opt ::=", + /* 151 */ "having_opt ::= HAVING expr", + /* 152 */ "limit_opt ::=", + /* 153 */ "limit_opt ::= LIMIT signed", + /* 154 */ "limit_opt ::= LIMIT signed OFFSET signed", + /* 155 */ "limit_opt ::= LIMIT signed COMMA signed", + /* 156 */ "cmd ::= DELETE FROM nm dbnm where_opt", + /* 157 */ "where_opt ::=", + /* 158 */ "where_opt ::= WHERE expr", + /* 159 */ "cmd ::= UPDATE orconf nm dbnm SET setlist where_opt", + /* 160 */ "setlist ::= setlist COMMA nm EQ expr", + /* 161 */ "setlist ::= nm EQ expr", + /* 162 */ "cmd ::= insert_cmd INTO nm dbnm inscollist_opt VALUES LP itemlist RP", + /* 163 */ "cmd ::= insert_cmd INTO nm dbnm inscollist_opt select", + /* 164 */ "insert_cmd ::= INSERT orconf", + /* 165 */ "insert_cmd ::= REPLACE", + /* 166 */ "itemlist ::= itemlist COMMA expr", + /* 167 */ "itemlist ::= expr", + /* 168 */ "inscollist_opt ::=", + /* 169 */ "inscollist_opt ::= LP inscollist RP", + /* 170 */ "inscollist ::= inscollist COMMA nm", + /* 171 */ "inscollist ::= nm", + /* 172 */ "expr ::= LP expr RP", + /* 173 */ "expr ::= NULL", + /* 174 */ "expr ::= ID", + /* 175 */ "expr ::= JOIN_KW", + /* 176 */ "expr ::= nm DOT nm", + /* 177 */ "expr ::= nm DOT nm DOT nm", + /* 178 */ "expr ::= INTEGER", + /* 179 */ "expr ::= FLOAT", + /* 180 */ "expr ::= STRING", + /* 181 */ "expr ::= VARIABLE", + /* 182 */ "expr ::= ID LP exprlist RP", + /* 183 */ "expr ::= ID LP STAR RP", + /* 184 */ "expr ::= expr AND expr", + /* 185 */ "expr ::= expr OR expr", + /* 186 */ "expr ::= expr LT expr", + /* 187 */ "expr ::= expr GT expr", + /* 188 */ "expr ::= expr LE expr", + /* 189 */ "expr ::= expr GE expr", + /* 190 */ "expr ::= expr NE expr", + /* 191 */ "expr ::= expr EQ expr", + /* 192 */ "expr ::= expr BITAND expr", + /* 193 */ "expr ::= expr BITOR expr", + /* 194 */ "expr ::= expr LSHIFT expr", + /* 195 */ "expr ::= expr RSHIFT expr", + /* 196 */ "expr ::= expr likeop expr", + /* 197 */ "expr ::= expr NOT likeop expr", + /* 198 */ "likeop ::= LIKE", + /* 199 */ "likeop ::= GLOB", + /* 200 */ "expr ::= expr PLUS expr", + /* 201 */ "expr ::= expr MINUS expr", + /* 202 */ "expr ::= expr STAR expr", + /* 203 */ "expr ::= expr SLASH expr", + /* 204 */ "expr ::= expr REM expr", + /* 205 */ "expr ::= expr CONCAT expr", + /* 206 */ "expr ::= expr ISNULL", + /* 207 */ "expr ::= expr IS NULL", + /* 208 */ "expr ::= expr NOTNULL", + /* 209 */ "expr ::= expr NOT NULL", + /* 210 */ "expr ::= expr IS NOT NULL", + /* 211 */ "expr ::= NOT expr", + /* 212 */ "expr ::= BITNOT expr", + /* 213 */ "expr ::= MINUS expr", + /* 214 */ "expr ::= PLUS expr", + /* 215 */ "expr ::= LP select RP", + /* 216 */ "expr ::= expr BETWEEN expr AND expr", + /* 217 */ "expr ::= expr NOT BETWEEN expr AND expr", + /* 218 */ "expr ::= expr IN LP exprlist RP", + /* 219 */ "expr ::= expr IN LP select RP", + /* 220 */ "expr ::= expr NOT IN LP exprlist RP", + /* 221 */ "expr ::= expr NOT IN LP select RP", + /* 222 */ "expr ::= expr IN nm dbnm", + /* 223 */ "expr ::= expr NOT IN nm dbnm", + /* 224 */ "expr ::= CASE case_operand case_exprlist case_else END", + /* 225 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 226 */ "case_exprlist ::= WHEN expr THEN expr", + /* 227 */ "case_else ::= ELSE expr", + /* 228 */ "case_else ::=", + /* 229 */ "case_operand ::= expr", + /* 230 */ "case_operand ::=", + /* 231 */ "exprlist ::= exprlist COMMA expritem", + /* 232 */ "exprlist ::= expritem", + /* 233 */ "expritem ::= expr", + /* 234 */ "expritem ::=", + /* 235 */ "cmd ::= CREATE uniqueflag INDEX nm ON nm dbnm LP idxlist RP onconf", + /* 236 */ "uniqueflag ::= UNIQUE", + /* 237 */ "uniqueflag ::=", + /* 238 */ "idxlist_opt ::=", + /* 239 */ "idxlist_opt ::= LP idxlist RP", + /* 240 */ "idxlist ::= idxlist COMMA idxitem", + /* 241 */ "idxlist ::= idxitem", + /* 242 */ "idxitem ::= nm sortorder", + /* 243 */ "cmd ::= DROP INDEX nm dbnm", + /* 244 */ "cmd ::= COPY orconf nm dbnm FROM nm USING DELIMITERS STRING", + /* 245 */ "cmd ::= COPY orconf nm dbnm FROM nm", + /* 246 */ "cmd ::= VACUUM", + /* 247 */ "cmd ::= VACUUM nm", + /* 248 */ "cmd ::= PRAGMA ids EQ nm", + /* 249 */ "cmd ::= PRAGMA ids EQ ON", + /* 250 */ "cmd ::= PRAGMA ids EQ plus_num", + /* 251 */ "cmd ::= PRAGMA ids EQ minus_num", + /* 252 */ "cmd ::= PRAGMA ids LP nm RP", + /* 253 */ "cmd ::= PRAGMA ids", + /* 254 */ "plus_num ::= plus_opt number", + /* 255 */ "minus_num ::= MINUS number", + /* 256 */ "number ::= INTEGER", + /* 257 */ "number ::= FLOAT", + /* 258 */ "plus_opt ::= PLUS", + /* 259 */ "plus_opt ::=", + /* 260 */ "cmd ::= CREATE trigger_decl BEGIN trigger_cmd_list END", + /* 261 */ "trigger_decl ::= temp TRIGGER nm trigger_time trigger_event ON nm dbnm foreach_clause when_clause", + /* 262 */ "trigger_time ::= BEFORE", + /* 263 */ "trigger_time ::= AFTER", + /* 264 */ "trigger_time ::= INSTEAD OF", + /* 265 */ "trigger_time ::=", + /* 266 */ "trigger_event ::= DELETE", + /* 267 */ "trigger_event ::= INSERT", + /* 268 */ "trigger_event ::= UPDATE", + /* 269 */ "trigger_event ::= UPDATE OF inscollist", + /* 270 */ "foreach_clause ::=", + /* 271 */ "foreach_clause ::= FOR EACH ROW", + /* 272 */ "foreach_clause ::= FOR EACH STATEMENT", + /* 273 */ "when_clause ::=", + /* 274 */ "when_clause ::= WHEN expr", + /* 275 */ "trigger_cmd_list ::= trigger_cmd SEMI trigger_cmd_list", + /* 276 */ "trigger_cmd_list ::=", + /* 277 */ "trigger_cmd ::= UPDATE orconf nm SET setlist where_opt", + /* 278 */ "trigger_cmd ::= insert_cmd INTO nm inscollist_opt VALUES LP itemlist RP", + /* 279 */ "trigger_cmd ::= insert_cmd INTO nm inscollist_opt select", + /* 280 */ "trigger_cmd ::= DELETE FROM nm where_opt", + /* 281 */ "trigger_cmd ::= select", + /* 282 */ "expr ::= RAISE LP IGNORE RP", + /* 283 */ "expr ::= RAISE LP ROLLBACK COMMA nm RP", + /* 284 */ "expr ::= RAISE LP ABORT COMMA nm RP", + /* 285 */ "expr ::= RAISE LP FAIL COMMA nm RP", + /* 286 */ "cmd ::= DROP TRIGGER nm dbnm", + /* 287 */ "cmd ::= ATTACH database_kw_opt ids AS nm key_opt", + /* 288 */ "key_opt ::= USING ids", + /* 289 */ "key_opt ::=", + /* 290 */ "database_kw_opt ::= DATABASE", + /* 291 */ "database_kw_opt ::=", + /* 292 */ "cmd ::= DETACH database_kw_opt nm", +}; +#endif /* NDEBUG */ + +/* +** This function returns the symbolic name associated with a token +** value. +*/ +const char *sqliteParserTokenName(int tokenType){ +#ifndef NDEBUG + if( tokenType>0 && tokenType<(sizeof(yyTokenName)/sizeof(yyTokenName[0])) ){ + return yyTokenName[tokenType]; + }else{ + return "Unknown"; + } +#else + return ""; +#endif +} + +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to sqliteParser and sqliteParserFree. +*/ +void *sqliteParserAlloc(void *(*mallocProc)(size_t)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + if( pParser ){ + pParser->yyidx = -1; + } + return pParser; +} + +/* The following function deletes the value associated with a +** symbol. The symbol can be either a terminal or nonterminal. +** "yymajor" is the symbol code, and "yypminor" is a pointer to +** the value. +*/ +static void yy_destructor(YYCODETYPE yymajor, YYMINORTYPE *yypminor){ + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ + case 146: +#line 286 "parse.y" +{sqliteSelectDelete((yypminor->yy179));} +#line 1235 "parse.c" + break; + case 158: +#line 533 "parse.y" +{sqliteExprDelete((yypminor->yy242));} +#line 1240 "parse.c" + break; + case 159: +#line 746 "parse.y" +{sqliteIdListDelete((yypminor->yy320));} +#line 1245 "parse.c" + break; + case 167: +#line 744 "parse.y" +{sqliteIdListDelete((yypminor->yy320));} +#line 1250 "parse.c" + break; + case 171: +#line 288 "parse.y" +{sqliteSelectDelete((yypminor->yy179));} +#line 1255 "parse.c" + break; + case 174: +#line 322 "parse.y" +{sqliteExprListDelete((yypminor->yy322));} +#line 1260 "parse.c" + break; + case 175: +#line 353 "parse.y" +{sqliteSrcListDelete((yypminor->yy307));} +#line 1265 "parse.c" + break; + case 176: +#line 483 "parse.y" +{sqliteExprDelete((yypminor->yy242));} +#line 1270 "parse.c" + break; + case 177: +#line 459 "parse.y" +{sqliteExprListDelete((yypminor->yy322));} +#line 1275 "parse.c" + break; + case 178: +#line 464 "parse.y" +{sqliteExprDelete((yypminor->yy242));} +#line 1280 "parse.c" + break; + case 179: +#line 431 "parse.y" +{sqliteExprListDelete((yypminor->yy322));} +#line 1285 "parse.c" + break; + case 181: +#line 324 "parse.y" +{sqliteExprListDelete((yypminor->yy322));} +#line 1290 "parse.c" + break; + case 183: +#line 349 "parse.y" +{sqliteSrcListDelete((yypminor->yy307));} +#line 1295 "parse.c" + break; + case 184: +#line 351 "parse.y" +{sqliteSrcListDelete((yypminor->yy307));} +#line 1300 "parse.c" + break; + case 187: +#line 420 "parse.y" +{sqliteExprDelete((yypminor->yy242));} +#line 1305 "parse.c" + break; + case 188: +#line 425 "parse.y" +{sqliteIdListDelete((yypminor->yy320));} +#line 1310 "parse.c" + break; + case 189: +#line 400 "parse.y" +{sqliteSelectDelete((yypminor->yy179));} +#line 1315 "parse.c" + break; + case 191: +#line 433 "parse.y" +{sqliteExprListDelete((yypminor->yy322));} +#line 1320 "parse.c" + break; + case 192: +#line 435 "parse.y" +{sqliteExprDelete((yypminor->yy242));} +#line 1325 "parse.c" + break; + case 194: +#line 719 "parse.y" +{sqliteExprListDelete((yypminor->yy322));} +#line 1330 "parse.c" + break; + case 195: +#line 489 "parse.y" +{sqliteExprListDelete((yypminor->yy322));} +#line 1335 "parse.c" + break; + case 197: +#line 520 "parse.y" +{sqliteIdListDelete((yypminor->yy320));} +#line 1340 "parse.c" + break; + case 198: +#line 514 "parse.y" +{sqliteExprListDelete((yypminor->yy322));} +#line 1345 "parse.c" + break; + case 199: +#line 522 "parse.y" +{sqliteIdListDelete((yypminor->yy320));} +#line 1350 "parse.c" + break; + case 202: +#line 702 "parse.y" +{sqliteExprListDelete((yypminor->yy322));} +#line 1355 "parse.c" + break; + case 204: +#line 721 "parse.y" +{sqliteExprDelete((yypminor->yy242));} +#line 1360 "parse.c" + break; + case 212: +#line 828 "parse.y" +{sqliteDeleteTriggerStep((yypminor->yy19));} +#line 1365 "parse.c" + break; + case 214: +#line 812 "parse.y" +{sqliteIdListDelete((yypminor->yy290).b);} +#line 1370 "parse.c" + break; + case 217: +#line 836 "parse.y" +{sqliteDeleteTriggerStep((yypminor->yy19));} +#line 1375 "parse.c" + break; + default: break; /* If no destructor action specified: do nothing */ + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +** +** Return the major token number for the symbol popped. +*/ +static int yy_pop_parser_stack(yyParser *pParser){ + YYCODETYPE yymajor; + yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; + + if( pParser->yyidx<0 ) return 0; +#ifndef NDEBUG + if( yyTraceFILE && pParser->yyidx>=0 ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yymajor = yytos->major; + yy_destructor( yymajor, &yytos->minor); + pParser->yyidx--; + return yymajor; +} + +/* +** Deallocate and destroy a parser. Destructors are all called for +** all stack elements before shutting the parser down. +** +** Inputs: +**
      +**
    • A pointer to the parser. This should be a pointer +** obtained from sqliteParserAlloc. +**
    • A pointer to a function used to reclaim memory obtained +** from malloc. +**
    +*/ +void sqliteParserFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; + if( pParser==0 ) return; + while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); + (*freeProc)((void*)pParser); +} + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_shift_action( + yyParser *pParser, /* The parser */ + int iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + + /* if( pParser->yyidx<0 ) return YY_NO_ACTION; */ + i = yy_shift_ofst[stateno]; + if( i==YY_SHIFT_USE_DFLT ){ + return yy_default[stateno]; + } + if( iLookAhead==YYNOCODE ){ + return YY_NO_ACTION; + } + i += iLookAhead; + if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){ +#ifdef YYFALLBACK + int iFallback; /* Fallback token */ + if( iLookAhead %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + return yy_find_shift_action(pParser, iFallback); + } +#endif + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_reduce_action( + yyParser *pParser, /* The parser */ + int iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + + i = yy_reduce_ofst[stateno]; + if( i==YY_REDUCE_USE_DFLT ){ + return yy_default[stateno]; + } + if( iLookAhead==YYNOCODE ){ + return YY_NO_ACTION; + } + i += iLookAhead; + if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + YYMINORTYPE *yypMinor /* Pointer ot the minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yyidx++; + if( yypParser->yyidx>=YYSTACKDEPTH ){ + sqliteParserARG_FETCH; + yypParser->yyidx--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ + sqliteParserARG_STORE; /* Suppress warning about unused %extra_argument var */ + return; + } + yytos = &yypParser->yystack[yypParser->yyidx]; + yytos->stateno = yyNewState; + yytos->major = yyMajor; + yytos->minor = *yypMinor; +#ifndef NDEBUG + if( yyTraceFILE && yypParser->yyidx>0 ){ + int i; + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + } +#endif +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { + { 132, 1 }, + { 133, 2 }, + { 133, 1 }, + { 134, 3 }, + { 134, 1 }, + { 136, 1 }, + { 135, 1 }, + { 135, 0 }, + { 137, 3 }, + { 138, 0 }, + { 138, 1 }, + { 138, 2 }, + { 137, 2 }, + { 137, 2 }, + { 137, 2 }, + { 137, 2 }, + { 141, 4 }, + { 143, 1 }, + { 143, 0 }, + { 142, 4 }, + { 142, 2 }, + { 144, 3 }, + { 144, 1 }, + { 147, 3 }, + { 148, 1 }, + { 151, 1 }, + { 152, 1 }, + { 152, 1 }, + { 140, 1 }, + { 140, 1 }, + { 140, 1 }, + { 149, 0 }, + { 149, 1 }, + { 149, 4 }, + { 149, 6 }, + { 153, 1 }, + { 153, 2 }, + { 154, 1 }, + { 154, 2 }, + { 154, 2 }, + { 150, 2 }, + { 150, 0 }, + { 155, 3 }, + { 155, 1 }, + { 155, 2 }, + { 155, 2 }, + { 155, 2 }, + { 155, 3 }, + { 155, 3 }, + { 155, 2 }, + { 155, 3 }, + { 155, 3 }, + { 155, 2 }, + { 156, 2 }, + { 156, 3 }, + { 156, 4 }, + { 156, 2 }, + { 156, 5 }, + { 156, 4 }, + { 156, 1 }, + { 156, 2 }, + { 160, 0 }, + { 160, 2 }, + { 162, 2 }, + { 162, 3 }, + { 162, 3 }, + { 162, 3 }, + { 163, 2 }, + { 163, 2 }, + { 163, 1 }, + { 163, 1 }, + { 161, 3 }, + { 161, 2 }, + { 164, 0 }, + { 164, 2 }, + { 164, 2 }, + { 145, 0 }, + { 145, 2 }, + { 165, 3 }, + { 165, 2 }, + { 165, 1 }, + { 166, 2 }, + { 166, 6 }, + { 166, 5 }, + { 166, 3 }, + { 166, 10 }, + { 168, 0 }, + { 168, 1 }, + { 139, 0 }, + { 139, 3 }, + { 169, 0 }, + { 169, 2 }, + { 170, 1 }, + { 170, 1 }, + { 170, 1 }, + { 170, 1 }, + { 170, 1 }, + { 137, 3 }, + { 137, 6 }, + { 137, 3 }, + { 137, 1 }, + { 146, 1 }, + { 146, 3 }, + { 172, 1 }, + { 172, 2 }, + { 172, 1 }, + { 172, 1 }, + { 171, 9 }, + { 173, 1 }, + { 173, 1 }, + { 173, 0 }, + { 181, 2 }, + { 181, 0 }, + { 174, 3 }, + { 174, 2 }, + { 174, 4 }, + { 182, 2 }, + { 182, 1 }, + { 182, 0 }, + { 175, 0 }, + { 175, 2 }, + { 184, 2 }, + { 184, 0 }, + { 183, 6 }, + { 183, 7 }, + { 189, 1 }, + { 189, 1 }, + { 186, 0 }, + { 186, 2 }, + { 185, 1 }, + { 185, 1 }, + { 185, 2 }, + { 185, 3 }, + { 185, 4 }, + { 187, 2 }, + { 187, 0 }, + { 188, 4 }, + { 188, 0 }, + { 179, 0 }, + { 179, 3 }, + { 191, 5 }, + { 191, 3 }, + { 192, 1 }, + { 157, 1 }, + { 157, 1 }, + { 157, 0 }, + { 193, 0 }, + { 193, 2 }, + { 177, 0 }, + { 177, 3 }, + { 178, 0 }, + { 178, 2 }, + { 180, 0 }, + { 180, 2 }, + { 180, 4 }, + { 180, 4 }, + { 137, 5 }, + { 176, 0 }, + { 176, 2 }, + { 137, 7 }, + { 195, 5 }, + { 195, 3 }, + { 137, 9 }, + { 137, 6 }, + { 196, 2 }, + { 196, 1 }, + { 198, 3 }, + { 198, 1 }, + { 197, 0 }, + { 197, 3 }, + { 199, 3 }, + { 199, 1 }, + { 158, 3 }, + { 158, 1 }, + { 158, 1 }, + { 158, 1 }, + { 158, 3 }, + { 158, 5 }, + { 158, 1 }, + { 158, 1 }, + { 158, 1 }, + { 158, 1 }, + { 158, 4 }, + { 158, 4 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 4 }, + { 200, 1 }, + { 200, 1 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 3 }, + { 158, 2 }, + { 158, 3 }, + { 158, 2 }, + { 158, 3 }, + { 158, 4 }, + { 158, 2 }, + { 158, 2 }, + { 158, 2 }, + { 158, 2 }, + { 158, 3 }, + { 158, 5 }, + { 158, 6 }, + { 158, 5 }, + { 158, 5 }, + { 158, 6 }, + { 158, 6 }, + { 158, 4 }, + { 158, 5 }, + { 158, 5 }, + { 202, 5 }, + { 202, 4 }, + { 203, 2 }, + { 203, 0 }, + { 201, 1 }, + { 201, 0 }, + { 194, 3 }, + { 194, 1 }, + { 204, 1 }, + { 204, 0 }, + { 137, 11 }, + { 205, 1 }, + { 205, 0 }, + { 159, 0 }, + { 159, 3 }, + { 167, 3 }, + { 167, 1 }, + { 206, 2 }, + { 137, 4 }, + { 137, 9 }, + { 137, 6 }, + { 137, 1 }, + { 137, 2 }, + { 137, 4 }, + { 137, 4 }, + { 137, 4 }, + { 137, 4 }, + { 137, 5 }, + { 137, 2 }, + { 207, 2 }, + { 208, 2 }, + { 210, 1 }, + { 210, 1 }, + { 209, 1 }, + { 209, 0 }, + { 137, 5 }, + { 211, 10 }, + { 213, 1 }, + { 213, 1 }, + { 213, 2 }, + { 213, 0 }, + { 214, 1 }, + { 214, 1 }, + { 214, 1 }, + { 214, 3 }, + { 215, 0 }, + { 215, 3 }, + { 215, 3 }, + { 216, 0 }, + { 216, 2 }, + { 212, 3 }, + { 212, 0 }, + { 217, 6 }, + { 217, 8 }, + { 217, 5 }, + { 217, 4 }, + { 217, 1 }, + { 158, 4 }, + { 158, 6 }, + { 158, 6 }, + { 158, 6 }, + { 137, 4 }, + { 137, 6 }, + { 219, 2 }, + { 219, 0 }, + { 218, 1 }, + { 218, 0 }, + { 137, 3 }, +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + sqliteParserARG_FETCH; + yymsp = &yypParser->yystack[yypParser->yyidx]; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno>=0 + && yyruleno + ** { ... } // User supplied code + ** #line + ** break; + */ + case 0: + /* No destructor defined for cmdlist */ + break; + case 1: + /* No destructor defined for cmdlist */ + /* No destructor defined for ecmd */ + break; + case 2: + /* No destructor defined for ecmd */ + break; + case 3: + /* No destructor defined for explain */ + /* No destructor defined for cmdx */ + /* No destructor defined for SEMI */ + break; + case 4: + /* No destructor defined for SEMI */ + break; + case 5: +#line 72 "parse.y" +{ sqliteExec(pParse); } +#line 1901 "parse.c" + /* No destructor defined for cmd */ + break; + case 6: +#line 73 "parse.y" +{ sqliteBeginParse(pParse, 1); } +#line 1907 "parse.c" + /* No destructor defined for EXPLAIN */ + break; + case 7: +#line 74 "parse.y" +{ sqliteBeginParse(pParse, 0); } +#line 1913 "parse.c" + break; + case 8: +#line 79 "parse.y" +{sqliteBeginTransaction(pParse,yymsp[0].minor.yy372);} +#line 1918 "parse.c" + /* No destructor defined for BEGIN */ + /* No destructor defined for trans_opt */ + break; + case 9: + break; + case 10: + /* No destructor defined for TRANSACTION */ + break; + case 11: + /* No destructor defined for TRANSACTION */ + /* No destructor defined for nm */ + break; + case 12: +#line 83 "parse.y" +{sqliteCommitTransaction(pParse);} +#line 1934 "parse.c" + /* No destructor defined for COMMIT */ + /* No destructor defined for trans_opt */ + break; + case 13: +#line 84 "parse.y" +{sqliteCommitTransaction(pParse);} +#line 1941 "parse.c" + /* No destructor defined for END */ + /* No destructor defined for trans_opt */ + break; + case 14: +#line 85 "parse.y" +{sqliteRollbackTransaction(pParse);} +#line 1948 "parse.c" + /* No destructor defined for ROLLBACK */ + /* No destructor defined for trans_opt */ + break; + case 15: + /* No destructor defined for create_table */ + /* No destructor defined for create_table_args */ + break; + case 16: +#line 90 "parse.y" +{ + sqliteStartTable(pParse,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy298,yymsp[-2].minor.yy372,0); +} +#line 1961 "parse.c" + /* No destructor defined for TABLE */ + break; + case 17: +#line 94 "parse.y" +{yygotominor.yy372 = 1;} +#line 1967 "parse.c" + /* No destructor defined for TEMP */ + break; + case 18: +#line 95 "parse.y" +{yygotominor.yy372 = 0;} +#line 1973 "parse.c" + break; + case 19: +#line 96 "parse.y" +{ + sqliteEndTable(pParse,&yymsp[0].minor.yy0,0); +} +#line 1980 "parse.c" + /* No destructor defined for LP */ + /* No destructor defined for columnlist */ + /* No destructor defined for conslist_opt */ + break; + case 20: +#line 99 "parse.y" +{ + sqliteEndTable(pParse,0,yymsp[0].minor.yy179); + sqliteSelectDelete(yymsp[0].minor.yy179); +} +#line 1991 "parse.c" + /* No destructor defined for AS */ + break; + case 21: + /* No destructor defined for columnlist */ + /* No destructor defined for COMMA */ + /* No destructor defined for column */ + break; + case 22: + /* No destructor defined for column */ + break; + case 23: + /* No destructor defined for columnid */ + /* No destructor defined for type */ + /* No destructor defined for carglist */ + break; + case 24: +#line 111 "parse.y" +{sqliteAddColumn(pParse,&yymsp[0].minor.yy298);} +#line 2010 "parse.c" + break; + case 25: +#line 117 "parse.y" +{yygotominor.yy298 = yymsp[0].minor.yy0;} +#line 2015 "parse.c" + break; + case 26: +#line 149 "parse.y" +{yygotominor.yy298 = yymsp[0].minor.yy0;} +#line 2020 "parse.c" + break; + case 27: +#line 150 "parse.y" +{yygotominor.yy298 = yymsp[0].minor.yy0;} +#line 2025 "parse.c" + break; + case 28: +#line 155 "parse.y" +{yygotominor.yy298 = yymsp[0].minor.yy0;} +#line 2030 "parse.c" + break; + case 29: +#line 156 "parse.y" +{yygotominor.yy298 = yymsp[0].minor.yy0;} +#line 2035 "parse.c" + break; + case 30: +#line 157 "parse.y" +{yygotominor.yy298 = yymsp[0].minor.yy0;} +#line 2040 "parse.c" + break; + case 31: + break; + case 32: +#line 160 "parse.y" +{sqliteAddColumnType(pParse,&yymsp[0].minor.yy298,&yymsp[0].minor.yy298);} +#line 2047 "parse.c" + break; + case 33: +#line 161 "parse.y" +{sqliteAddColumnType(pParse,&yymsp[-3].minor.yy298,&yymsp[0].minor.yy0);} +#line 2052 "parse.c" + /* No destructor defined for LP */ + /* No destructor defined for signed */ + break; + case 34: +#line 163 "parse.y" +{sqliteAddColumnType(pParse,&yymsp[-5].minor.yy298,&yymsp[0].minor.yy0);} +#line 2059 "parse.c" + /* No destructor defined for LP */ + /* No destructor defined for signed */ + /* No destructor defined for COMMA */ + /* No destructor defined for signed */ + break; + case 35: +#line 165 "parse.y" +{yygotominor.yy298 = yymsp[0].minor.yy298;} +#line 2068 "parse.c" + break; + case 36: +#line 166 "parse.y" +{yygotominor.yy298 = yymsp[-1].minor.yy298;} +#line 2073 "parse.c" + /* No destructor defined for ids */ + break; + case 37: +#line 168 "parse.y" +{ yygotominor.yy372 = atoi(yymsp[0].minor.yy0.z); } +#line 2079 "parse.c" + break; + case 38: +#line 169 "parse.y" +{ yygotominor.yy372 = atoi(yymsp[0].minor.yy0.z); } +#line 2084 "parse.c" + /* No destructor defined for PLUS */ + break; + case 39: +#line 170 "parse.y" +{ yygotominor.yy372 = -atoi(yymsp[0].minor.yy0.z); } +#line 2090 "parse.c" + /* No destructor defined for MINUS */ + break; + case 40: + /* No destructor defined for carglist */ + /* No destructor defined for carg */ + break; + case 41: + break; + case 42: + /* No destructor defined for CONSTRAINT */ + /* No destructor defined for nm */ + /* No destructor defined for ccons */ + break; + case 43: + /* No destructor defined for ccons */ + break; + case 44: +#line 175 "parse.y" +{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,0);} +#line 2110 "parse.c" + /* No destructor defined for DEFAULT */ + break; + case 45: +#line 176 "parse.y" +{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,0);} +#line 2116 "parse.c" + /* No destructor defined for DEFAULT */ + break; + case 46: +#line 177 "parse.y" +{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,0);} +#line 2122 "parse.c" + /* No destructor defined for DEFAULT */ + break; + case 47: +#line 178 "parse.y" +{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,0);} +#line 2128 "parse.c" + /* No destructor defined for DEFAULT */ + /* No destructor defined for PLUS */ + break; + case 48: +#line 179 "parse.y" +{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,1);} +#line 2135 "parse.c" + /* No destructor defined for DEFAULT */ + /* No destructor defined for MINUS */ + break; + case 49: +#line 180 "parse.y" +{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,0);} +#line 2142 "parse.c" + /* No destructor defined for DEFAULT */ + break; + case 50: +#line 181 "parse.y" +{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,0);} +#line 2148 "parse.c" + /* No destructor defined for DEFAULT */ + /* No destructor defined for PLUS */ + break; + case 51: +#line 182 "parse.y" +{sqliteAddDefaultValue(pParse,&yymsp[0].minor.yy0,1);} +#line 2155 "parse.c" + /* No destructor defined for DEFAULT */ + /* No destructor defined for MINUS */ + break; + case 52: + /* No destructor defined for DEFAULT */ + /* No destructor defined for NULL */ + break; + case 53: + /* No destructor defined for NULL */ + /* No destructor defined for onconf */ + break; + case 54: +#line 189 "parse.y" +{sqliteAddNotNull(pParse, yymsp[0].minor.yy372);} +#line 2170 "parse.c" + /* No destructor defined for NOT */ + /* No destructor defined for NULL */ + break; + case 55: +#line 190 "parse.y" +{sqliteAddPrimaryKey(pParse,0,yymsp[0].minor.yy372);} +#line 2177 "parse.c" + /* No destructor defined for PRIMARY */ + /* No destructor defined for KEY */ + /* No destructor defined for sortorder */ + break; + case 56: +#line 191 "parse.y" +{sqliteCreateIndex(pParse,0,0,0,yymsp[0].minor.yy372,0,0);} +#line 2185 "parse.c" + /* No destructor defined for UNIQUE */ + break; + case 57: + /* No destructor defined for CHECK */ + /* No destructor defined for LP */ + yy_destructor(158,&yymsp[-2].minor); + /* No destructor defined for RP */ + /* No destructor defined for onconf */ + break; + case 58: +#line 194 "parse.y" +{sqliteCreateForeignKey(pParse,0,&yymsp[-2].minor.yy298,yymsp[-1].minor.yy320,yymsp[0].minor.yy372);} +#line 2198 "parse.c" + /* No destructor defined for REFERENCES */ + break; + case 59: +#line 195 "parse.y" +{sqliteDeferForeignKey(pParse,yymsp[0].minor.yy372);} +#line 2204 "parse.c" + break; + case 60: +#line 196 "parse.y" +{ + sqliteAddCollateType(pParse, sqliteCollateType(yymsp[0].minor.yy298.z, yymsp[0].minor.yy298.n)); +} +#line 2211 "parse.c" + /* No destructor defined for COLLATE */ + break; + case 61: +#line 206 "parse.y" +{ yygotominor.yy372 = OE_Restrict * 0x010101; } +#line 2217 "parse.c" + break; + case 62: +#line 207 "parse.y" +{ yygotominor.yy372 = (yymsp[-1].minor.yy372 & yymsp[0].minor.yy407.mask) | yymsp[0].minor.yy407.value; } +#line 2222 "parse.c" + break; + case 63: +#line 209 "parse.y" +{ yygotominor.yy407.value = 0; yygotominor.yy407.mask = 0x000000; } +#line 2227 "parse.c" + /* No destructor defined for MATCH */ + /* No destructor defined for nm */ + break; + case 64: +#line 210 "parse.y" +{ yygotominor.yy407.value = yymsp[0].minor.yy372; yygotominor.yy407.mask = 0x0000ff; } +#line 2234 "parse.c" + /* No destructor defined for ON */ + /* No destructor defined for DELETE */ + break; + case 65: +#line 211 "parse.y" +{ yygotominor.yy407.value = yymsp[0].minor.yy372<<8; yygotominor.yy407.mask = 0x00ff00; } +#line 2241 "parse.c" + /* No destructor defined for ON */ + /* No destructor defined for UPDATE */ + break; + case 66: +#line 212 "parse.y" +{ yygotominor.yy407.value = yymsp[0].minor.yy372<<16; yygotominor.yy407.mask = 0xff0000; } +#line 2248 "parse.c" + /* No destructor defined for ON */ + /* No destructor defined for INSERT */ + break; + case 67: +#line 214 "parse.y" +{ yygotominor.yy372 = OE_SetNull; } +#line 2255 "parse.c" + /* No destructor defined for SET */ + /* No destructor defined for NULL */ + break; + case 68: +#line 215 "parse.y" +{ yygotominor.yy372 = OE_SetDflt; } +#line 2262 "parse.c" + /* No destructor defined for SET */ + /* No destructor defined for DEFAULT */ + break; + case 69: +#line 216 "parse.y" +{ yygotominor.yy372 = OE_Cascade; } +#line 2269 "parse.c" + /* No destructor defined for CASCADE */ + break; + case 70: +#line 217 "parse.y" +{ yygotominor.yy372 = OE_Restrict; } +#line 2275 "parse.c" + /* No destructor defined for RESTRICT */ + break; + case 71: +#line 219 "parse.y" +{yygotominor.yy372 = yymsp[0].minor.yy372;} +#line 2281 "parse.c" + /* No destructor defined for NOT */ + /* No destructor defined for DEFERRABLE */ + break; + case 72: +#line 220 "parse.y" +{yygotominor.yy372 = yymsp[0].minor.yy372;} +#line 2288 "parse.c" + /* No destructor defined for DEFERRABLE */ + break; + case 73: +#line 222 "parse.y" +{yygotominor.yy372 = 0;} +#line 2294 "parse.c" + break; + case 74: +#line 223 "parse.y" +{yygotominor.yy372 = 1;} +#line 2299 "parse.c" + /* No destructor defined for INITIALLY */ + /* No destructor defined for DEFERRED */ + break; + case 75: +#line 224 "parse.y" +{yygotominor.yy372 = 0;} +#line 2306 "parse.c" + /* No destructor defined for INITIALLY */ + /* No destructor defined for IMMEDIATE */ + break; + case 76: + break; + case 77: + /* No destructor defined for COMMA */ + /* No destructor defined for conslist */ + break; + case 78: + /* No destructor defined for conslist */ + /* No destructor defined for COMMA */ + /* No destructor defined for tcons */ + break; + case 79: + /* No destructor defined for conslist */ + /* No destructor defined for tcons */ + break; + case 80: + /* No destructor defined for tcons */ + break; + case 81: + /* No destructor defined for CONSTRAINT */ + /* No destructor defined for nm */ + break; + case 82: +#line 236 "parse.y" +{sqliteAddPrimaryKey(pParse,yymsp[-2].minor.yy320,yymsp[0].minor.yy372);} +#line 2335 "parse.c" + /* No destructor defined for PRIMARY */ + /* No destructor defined for KEY */ + /* No destructor defined for LP */ + /* No destructor defined for RP */ + break; + case 83: +#line 238 "parse.y" +{sqliteCreateIndex(pParse,0,0,yymsp[-2].minor.yy320,yymsp[0].minor.yy372,0,0);} +#line 2344 "parse.c" + /* No destructor defined for UNIQUE */ + /* No destructor defined for LP */ + /* No destructor defined for RP */ + break; + case 84: + /* No destructor defined for CHECK */ + yy_destructor(158,&yymsp[-1].minor); + /* No destructor defined for onconf */ + break; + case 85: +#line 241 "parse.y" +{ + sqliteCreateForeignKey(pParse, yymsp[-6].minor.yy320, &yymsp[-3].minor.yy298, yymsp[-2].minor.yy320, yymsp[-1].minor.yy372); + sqliteDeferForeignKey(pParse, yymsp[0].minor.yy372); +} +#line 2360 "parse.c" + /* No destructor defined for FOREIGN */ + /* No destructor defined for KEY */ + /* No destructor defined for LP */ + /* No destructor defined for RP */ + /* No destructor defined for REFERENCES */ + break; + case 86: +#line 246 "parse.y" +{yygotominor.yy372 = 0;} +#line 2370 "parse.c" + break; + case 87: +#line 247 "parse.y" +{yygotominor.yy372 = yymsp[0].minor.yy372;} +#line 2375 "parse.c" + break; + case 88: +#line 255 "parse.y" +{ yygotominor.yy372 = OE_Default; } +#line 2380 "parse.c" + break; + case 89: +#line 256 "parse.y" +{ yygotominor.yy372 = yymsp[0].minor.yy372; } +#line 2385 "parse.c" + /* No destructor defined for ON */ + /* No destructor defined for CONFLICT */ + break; + case 90: +#line 257 "parse.y" +{ yygotominor.yy372 = OE_Default; } +#line 2392 "parse.c" + break; + case 91: +#line 258 "parse.y" +{ yygotominor.yy372 = yymsp[0].minor.yy372; } +#line 2397 "parse.c" + /* No destructor defined for OR */ + break; + case 92: +#line 259 "parse.y" +{ yygotominor.yy372 = OE_Rollback; } +#line 2403 "parse.c" + /* No destructor defined for ROLLBACK */ + break; + case 93: +#line 260 "parse.y" +{ yygotominor.yy372 = OE_Abort; } +#line 2409 "parse.c" + /* No destructor defined for ABORT */ + break; + case 94: +#line 261 "parse.y" +{ yygotominor.yy372 = OE_Fail; } +#line 2415 "parse.c" + /* No destructor defined for FAIL */ + break; + case 95: +#line 262 "parse.y" +{ yygotominor.yy372 = OE_Ignore; } +#line 2421 "parse.c" + /* No destructor defined for IGNORE */ + break; + case 96: +#line 263 "parse.y" +{ yygotominor.yy372 = OE_Replace; } +#line 2427 "parse.c" + /* No destructor defined for REPLACE */ + break; + case 97: +#line 267 "parse.y" +{sqliteDropTable(pParse,&yymsp[0].minor.yy298,0);} +#line 2433 "parse.c" + /* No destructor defined for DROP */ + /* No destructor defined for TABLE */ + break; + case 98: +#line 271 "parse.y" +{ + sqliteCreateView(pParse, &yymsp[-5].minor.yy0, &yymsp[-2].minor.yy298, yymsp[0].minor.yy179, yymsp[-4].minor.yy372); +} +#line 2442 "parse.c" + /* No destructor defined for VIEW */ + /* No destructor defined for AS */ + break; + case 99: +#line 274 "parse.y" +{ + sqliteDropTable(pParse, &yymsp[0].minor.yy298, 1); +} +#line 2451 "parse.c" + /* No destructor defined for DROP */ + /* No destructor defined for VIEW */ + break; + case 100: +#line 280 "parse.y" +{ + sqliteSelect(pParse, yymsp[0].minor.yy179, SRT_Callback, 0, 0, 0, 0); + sqliteSelectDelete(yymsp[0].minor.yy179); +} +#line 2461 "parse.c" + break; + case 101: +#line 290 "parse.y" +{yygotominor.yy179 = yymsp[0].minor.yy179;} +#line 2466 "parse.c" + break; + case 102: +#line 291 "parse.y" +{ + if( yymsp[0].minor.yy179 ){ + yymsp[0].minor.yy179->op = yymsp[-1].minor.yy372; + yymsp[0].minor.yy179->pPrior = yymsp[-2].minor.yy179; + } + yygotominor.yy179 = yymsp[0].minor.yy179; +} +#line 2477 "parse.c" + break; + case 103: +#line 299 "parse.y" +{yygotominor.yy372 = TK_UNION;} +#line 2482 "parse.c" + /* No destructor defined for UNION */ + break; + case 104: +#line 300 "parse.y" +{yygotominor.yy372 = TK_ALL;} +#line 2488 "parse.c" + /* No destructor defined for UNION */ + /* No destructor defined for ALL */ + break; + case 105: +#line 301 "parse.y" +{yygotominor.yy372 = TK_INTERSECT;} +#line 2495 "parse.c" + /* No destructor defined for INTERSECT */ + break; + case 106: +#line 302 "parse.y" +{yygotominor.yy372 = TK_EXCEPT;} +#line 2501 "parse.c" + /* No destructor defined for EXCEPT */ + break; + case 107: +#line 304 "parse.y" +{ + yygotominor.yy179 = sqliteSelectNew(yymsp[-6].minor.yy322,yymsp[-5].minor.yy307,yymsp[-4].minor.yy242,yymsp[-3].minor.yy322,yymsp[-2].minor.yy242,yymsp[-1].minor.yy322,yymsp[-7].minor.yy372,yymsp[0].minor.yy124.limit,yymsp[0].minor.yy124.offset); +} +#line 2509 "parse.c" + /* No destructor defined for SELECT */ + break; + case 108: +#line 312 "parse.y" +{yygotominor.yy372 = 1;} +#line 2515 "parse.c" + /* No destructor defined for DISTINCT */ + break; + case 109: +#line 313 "parse.y" +{yygotominor.yy372 = 0;} +#line 2521 "parse.c" + /* No destructor defined for ALL */ + break; + case 110: +#line 314 "parse.y" +{yygotominor.yy372 = 0;} +#line 2527 "parse.c" + break; + case 111: +#line 325 "parse.y" +{yygotominor.yy322 = yymsp[-1].minor.yy322;} +#line 2532 "parse.c" + /* No destructor defined for COMMA */ + break; + case 112: +#line 326 "parse.y" +{yygotominor.yy322 = 0;} +#line 2538 "parse.c" + break; + case 113: +#line 327 "parse.y" +{ + yygotominor.yy322 = sqliteExprListAppend(yymsp[-2].minor.yy322,yymsp[-1].minor.yy242,yymsp[0].minor.yy298.n?&yymsp[0].minor.yy298:0); +} +#line 2545 "parse.c" + break; + case 114: +#line 330 "parse.y" +{ + yygotominor.yy322 = sqliteExprListAppend(yymsp[-1].minor.yy322, sqliteExpr(TK_ALL, 0, 0, 0), 0); +} +#line 2552 "parse.c" + /* No destructor defined for STAR */ + break; + case 115: +#line 333 "parse.y" +{ + Expr *pRight = sqliteExpr(TK_ALL, 0, 0, 0); + Expr *pLeft = sqliteExpr(TK_ID, 0, 0, &yymsp[-2].minor.yy298); + yygotominor.yy322 = sqliteExprListAppend(yymsp[-3].minor.yy322, sqliteExpr(TK_DOT, pLeft, pRight, 0), 0); +} +#line 2562 "parse.c" + /* No destructor defined for DOT */ + /* No destructor defined for STAR */ + break; + case 116: +#line 343 "parse.y" +{ yygotominor.yy298 = yymsp[0].minor.yy298; } +#line 2569 "parse.c" + /* No destructor defined for AS */ + break; + case 117: +#line 344 "parse.y" +{ yygotominor.yy298 = yymsp[0].minor.yy298; } +#line 2575 "parse.c" + break; + case 118: +#line 345 "parse.y" +{ yygotominor.yy298.n = 0; } +#line 2580 "parse.c" + break; + case 119: +#line 357 "parse.y" +{yygotominor.yy307 = sqliteMalloc(sizeof(*yygotominor.yy307));} +#line 2585 "parse.c" + break; + case 120: +#line 358 "parse.y" +{yygotominor.yy307 = yymsp[0].minor.yy307;} +#line 2590 "parse.c" + /* No destructor defined for FROM */ + break; + case 121: +#line 363 "parse.y" +{ + yygotominor.yy307 = yymsp[-1].minor.yy307; + if( yygotominor.yy307 && yygotominor.yy307->nSrc>0 ) yygotominor.yy307->a[yygotominor.yy307->nSrc-1].jointype = yymsp[0].minor.yy372; +} +#line 2599 "parse.c" + break; + case 122: +#line 367 "parse.y" +{yygotominor.yy307 = 0;} +#line 2604 "parse.c" + break; + case 123: +#line 368 "parse.y" +{ + yygotominor.yy307 = sqliteSrcListAppend(yymsp[-5].minor.yy307,&yymsp[-4].minor.yy298,&yymsp[-3].minor.yy298); + if( yymsp[-2].minor.yy298.n ) sqliteSrcListAddAlias(yygotominor.yy307,&yymsp[-2].minor.yy298); + if( yymsp[-1].minor.yy242 ){ + if( yygotominor.yy307 && yygotominor.yy307->nSrc>1 ){ yygotominor.yy307->a[yygotominor.yy307->nSrc-2].pOn = yymsp[-1].minor.yy242; } + else { sqliteExprDelete(yymsp[-1].minor.yy242); } + } + if( yymsp[0].minor.yy320 ){ + if( yygotominor.yy307 && yygotominor.yy307->nSrc>1 ){ yygotominor.yy307->a[yygotominor.yy307->nSrc-2].pUsing = yymsp[0].minor.yy320; } + else { sqliteIdListDelete(yymsp[0].minor.yy320); } + } +} +#line 2620 "parse.c" + break; + case 124: +#line 381 "parse.y" +{ + yygotominor.yy307 = sqliteSrcListAppend(yymsp[-6].minor.yy307,0,0); + yygotominor.yy307->a[yygotominor.yy307->nSrc-1].pSelect = yymsp[-4].minor.yy179; + if( yymsp[-2].minor.yy298.n ) sqliteSrcListAddAlias(yygotominor.yy307,&yymsp[-2].minor.yy298); + if( yymsp[-1].minor.yy242 ){ + if( yygotominor.yy307 && yygotominor.yy307->nSrc>1 ){ yygotominor.yy307->a[yygotominor.yy307->nSrc-2].pOn = yymsp[-1].minor.yy242; } + else { sqliteExprDelete(yymsp[-1].minor.yy242); } + } + if( yymsp[0].minor.yy320 ){ + if( yygotominor.yy307 && yygotominor.yy307->nSrc>1 ){ yygotominor.yy307->a[yygotominor.yy307->nSrc-2].pUsing = yymsp[0].minor.yy320; } + else { sqliteIdListDelete(yymsp[0].minor.yy320); } + } +} +#line 2637 "parse.c" + /* No destructor defined for LP */ + /* No destructor defined for RP */ + break; + case 125: +#line 401 "parse.y" +{yygotominor.yy179 = yymsp[0].minor.yy179;} +#line 2644 "parse.c" + break; + case 126: +#line 402 "parse.y" +{ + yygotominor.yy179 = sqliteSelectNew(0,yymsp[0].minor.yy307,0,0,0,0,0,-1,0); +} +#line 2651 "parse.c" + break; + case 127: +#line 407 "parse.y" +{yygotominor.yy298.z=0; yygotominor.yy298.n=0;} +#line 2656 "parse.c" + break; + case 128: +#line 408 "parse.y" +{yygotominor.yy298 = yymsp[0].minor.yy298;} +#line 2661 "parse.c" + /* No destructor defined for DOT */ + break; + case 129: +#line 412 "parse.y" +{ yygotominor.yy372 = JT_INNER; } +#line 2667 "parse.c" + /* No destructor defined for COMMA */ + break; + case 130: +#line 413 "parse.y" +{ yygotominor.yy372 = JT_INNER; } +#line 2673 "parse.c" + /* No destructor defined for JOIN */ + break; + case 131: +#line 414 "parse.y" +{ yygotominor.yy372 = sqliteJoinType(pParse,&yymsp[-1].minor.yy0,0,0); } +#line 2679 "parse.c" + /* No destructor defined for JOIN */ + break; + case 132: +#line 415 "parse.y" +{ yygotominor.yy372 = sqliteJoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy298,0); } +#line 2685 "parse.c" + /* No destructor defined for JOIN */ + break; + case 133: +#line 417 "parse.y" +{ yygotominor.yy372 = sqliteJoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy298,&yymsp[-1].minor.yy298); } +#line 2691 "parse.c" + /* No destructor defined for JOIN */ + break; + case 134: +#line 421 "parse.y" +{yygotominor.yy242 = yymsp[0].minor.yy242;} +#line 2697 "parse.c" + /* No destructor defined for ON */ + break; + case 135: +#line 422 "parse.y" +{yygotominor.yy242 = 0;} +#line 2703 "parse.c" + break; + case 136: +#line 426 "parse.y" +{yygotominor.yy320 = yymsp[-1].minor.yy320;} +#line 2708 "parse.c" + /* No destructor defined for USING */ + /* No destructor defined for LP */ + /* No destructor defined for RP */ + break; + case 137: +#line 427 "parse.y" +{yygotominor.yy320 = 0;} +#line 2716 "parse.c" + break; + case 138: +#line 437 "parse.y" +{yygotominor.yy322 = 0;} +#line 2721 "parse.c" + break; + case 139: +#line 438 "parse.y" +{yygotominor.yy322 = yymsp[0].minor.yy322;} +#line 2726 "parse.c" + /* No destructor defined for ORDER */ + /* No destructor defined for BY */ + break; + case 140: +#line 439 "parse.y" +{ + yygotominor.yy322 = sqliteExprListAppend(yymsp[-4].minor.yy322,yymsp[-2].minor.yy242,0); + if( yygotominor.yy322 ) yygotominor.yy322->a[yygotominor.yy322->nExpr-1].sortOrder = yymsp[-1].minor.yy372+yymsp[0].minor.yy372; +} +#line 2736 "parse.c" + /* No destructor defined for COMMA */ + break; + case 141: +#line 443 "parse.y" +{ + yygotominor.yy322 = sqliteExprListAppend(0,yymsp[-2].minor.yy242,0); + if( yygotominor.yy322 ) yygotominor.yy322->a[0].sortOrder = yymsp[-1].minor.yy372+yymsp[0].minor.yy372; +} +#line 2745 "parse.c" + break; + case 142: +#line 447 "parse.y" +{yygotominor.yy242 = yymsp[0].minor.yy242;} +#line 2750 "parse.c" + break; + case 143: +#line 452 "parse.y" +{yygotominor.yy372 = SQLITE_SO_ASC;} +#line 2755 "parse.c" + /* No destructor defined for ASC */ + break; + case 144: +#line 453 "parse.y" +{yygotominor.yy372 = SQLITE_SO_DESC;} +#line 2761 "parse.c" + /* No destructor defined for DESC */ + break; + case 145: +#line 454 "parse.y" +{yygotominor.yy372 = SQLITE_SO_ASC;} +#line 2767 "parse.c" + break; + case 146: +#line 455 "parse.y" +{yygotominor.yy372 = SQLITE_SO_UNK;} +#line 2772 "parse.c" + break; + case 147: +#line 456 "parse.y" +{yygotominor.yy372 = sqliteCollateType(yymsp[0].minor.yy298.z, yymsp[0].minor.yy298.n);} +#line 2777 "parse.c" + /* No destructor defined for COLLATE */ + break; + case 148: +#line 460 "parse.y" +{yygotominor.yy322 = 0;} +#line 2783 "parse.c" + break; + case 149: +#line 461 "parse.y" +{yygotominor.yy322 = yymsp[0].minor.yy322;} +#line 2788 "parse.c" + /* No destructor defined for GROUP */ + /* No destructor defined for BY */ + break; + case 150: +#line 465 "parse.y" +{yygotominor.yy242 = 0;} +#line 2795 "parse.c" + break; + case 151: +#line 466 "parse.y" +{yygotominor.yy242 = yymsp[0].minor.yy242;} +#line 2800 "parse.c" + /* No destructor defined for HAVING */ + break; + case 152: +#line 469 "parse.y" +{yygotominor.yy124.limit = -1; yygotominor.yy124.offset = 0;} +#line 2806 "parse.c" + break; + case 153: +#line 470 "parse.y" +{yygotominor.yy124.limit = yymsp[0].minor.yy372; yygotominor.yy124.offset = 0;} +#line 2811 "parse.c" + /* No destructor defined for LIMIT */ + break; + case 154: +#line 472 "parse.y" +{yygotominor.yy124.limit = yymsp[-2].minor.yy372; yygotominor.yy124.offset = yymsp[0].minor.yy372;} +#line 2817 "parse.c" + /* No destructor defined for LIMIT */ + /* No destructor defined for OFFSET */ + break; + case 155: +#line 474 "parse.y" +{yygotominor.yy124.limit = yymsp[0].minor.yy372; yygotominor.yy124.offset = yymsp[-2].minor.yy372;} +#line 2824 "parse.c" + /* No destructor defined for LIMIT */ + /* No destructor defined for COMMA */ + break; + case 156: +#line 478 "parse.y" +{ + sqliteDeleteFrom(pParse, sqliteSrcListAppend(0,&yymsp[-2].minor.yy298,&yymsp[-1].minor.yy298), yymsp[0].minor.yy242); +} +#line 2833 "parse.c" + /* No destructor defined for DELETE */ + /* No destructor defined for FROM */ + break; + case 157: +#line 485 "parse.y" +{yygotominor.yy242 = 0;} +#line 2840 "parse.c" + break; + case 158: +#line 486 "parse.y" +{yygotominor.yy242 = yymsp[0].minor.yy242;} +#line 2845 "parse.c" + /* No destructor defined for WHERE */ + break; + case 159: +#line 494 "parse.y" +{sqliteUpdate(pParse,sqliteSrcListAppend(0,&yymsp[-4].minor.yy298,&yymsp[-3].minor.yy298),yymsp[-1].minor.yy322,yymsp[0].minor.yy242,yymsp[-5].minor.yy372);} +#line 2851 "parse.c" + /* No destructor defined for UPDATE */ + /* No destructor defined for SET */ + break; + case 160: +#line 497 "parse.y" +{yygotominor.yy322 = sqliteExprListAppend(yymsp[-4].minor.yy322,yymsp[0].minor.yy242,&yymsp[-2].minor.yy298);} +#line 2858 "parse.c" + /* No destructor defined for COMMA */ + /* No destructor defined for EQ */ + break; + case 161: +#line 498 "parse.y" +{yygotominor.yy322 = sqliteExprListAppend(0,yymsp[0].minor.yy242,&yymsp[-2].minor.yy298);} +#line 2865 "parse.c" + /* No destructor defined for EQ */ + break; + case 162: +#line 504 "parse.y" +{sqliteInsert(pParse, sqliteSrcListAppend(0,&yymsp[-6].minor.yy298,&yymsp[-5].minor.yy298), yymsp[-1].minor.yy322, 0, yymsp[-4].minor.yy320, yymsp[-8].minor.yy372);} +#line 2871 "parse.c" + /* No destructor defined for INTO */ + /* No destructor defined for VALUES */ + /* No destructor defined for LP */ + /* No destructor defined for RP */ + break; + case 163: +#line 506 "parse.y" +{sqliteInsert(pParse, sqliteSrcListAppend(0,&yymsp[-3].minor.yy298,&yymsp[-2].minor.yy298), 0, yymsp[0].minor.yy179, yymsp[-1].minor.yy320, yymsp[-5].minor.yy372);} +#line 2880 "parse.c" + /* No destructor defined for INTO */ + break; + case 164: +#line 509 "parse.y" +{yygotominor.yy372 = yymsp[0].minor.yy372;} +#line 2886 "parse.c" + /* No destructor defined for INSERT */ + break; + case 165: +#line 510 "parse.y" +{yygotominor.yy372 = OE_Replace;} +#line 2892 "parse.c" + /* No destructor defined for REPLACE */ + break; + case 166: +#line 516 "parse.y" +{yygotominor.yy322 = sqliteExprListAppend(yymsp[-2].minor.yy322,yymsp[0].minor.yy242,0);} +#line 2898 "parse.c" + /* No destructor defined for COMMA */ + break; + case 167: +#line 517 "parse.y" +{yygotominor.yy322 = sqliteExprListAppend(0,yymsp[0].minor.yy242,0);} +#line 2904 "parse.c" + break; + case 168: +#line 524 "parse.y" +{yygotominor.yy320 = 0;} +#line 2909 "parse.c" + break; + case 169: +#line 525 "parse.y" +{yygotominor.yy320 = yymsp[-1].minor.yy320;} +#line 2914 "parse.c" + /* No destructor defined for LP */ + /* No destructor defined for RP */ + break; + case 170: +#line 526 "parse.y" +{yygotominor.yy320 = sqliteIdListAppend(yymsp[-2].minor.yy320,&yymsp[0].minor.yy298);} +#line 2921 "parse.c" + /* No destructor defined for COMMA */ + break; + case 171: +#line 527 "parse.y" +{yygotominor.yy320 = sqliteIdListAppend(0,&yymsp[0].minor.yy298);} +#line 2927 "parse.c" + break; + case 172: +#line 535 "parse.y" +{yygotominor.yy242 = yymsp[-1].minor.yy242; sqliteExprSpan(yygotominor.yy242,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); } +#line 2932 "parse.c" + break; + case 173: +#line 536 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_NULL, 0, 0, &yymsp[0].minor.yy0);} +#line 2937 "parse.c" + break; + case 174: +#line 537 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_ID, 0, 0, &yymsp[0].minor.yy0);} +#line 2942 "parse.c" + break; + case 175: +#line 538 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_ID, 0, 0, &yymsp[0].minor.yy0);} +#line 2947 "parse.c" + break; + case 176: +#line 539 "parse.y" +{ + Expr *temp1 = sqliteExpr(TK_ID, 0, 0, &yymsp[-2].minor.yy298); + Expr *temp2 = sqliteExpr(TK_ID, 0, 0, &yymsp[0].minor.yy298); + yygotominor.yy242 = sqliteExpr(TK_DOT, temp1, temp2, 0); +} +#line 2956 "parse.c" + /* No destructor defined for DOT */ + break; + case 177: +#line 544 "parse.y" +{ + Expr *temp1 = sqliteExpr(TK_ID, 0, 0, &yymsp[-4].minor.yy298); + Expr *temp2 = sqliteExpr(TK_ID, 0, 0, &yymsp[-2].minor.yy298); + Expr *temp3 = sqliteExpr(TK_ID, 0, 0, &yymsp[0].minor.yy298); + Expr *temp4 = sqliteExpr(TK_DOT, temp2, temp3, 0); + yygotominor.yy242 = sqliteExpr(TK_DOT, temp1, temp4, 0); +} +#line 2968 "parse.c" + /* No destructor defined for DOT */ + /* No destructor defined for DOT */ + break; + case 178: +#line 551 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_INTEGER, 0, 0, &yymsp[0].minor.yy0);} +#line 2975 "parse.c" + break; + case 179: +#line 552 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_FLOAT, 0, 0, &yymsp[0].minor.yy0);} +#line 2980 "parse.c" + break; + case 180: +#line 553 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_STRING, 0, 0, &yymsp[0].minor.yy0);} +#line 2985 "parse.c" + break; + case 181: +#line 554 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_VARIABLE, 0, 0, &yymsp[0].minor.yy0); + if( yygotominor.yy242 ) yygotominor.yy242->iTable = ++pParse->nVar; +} +#line 2993 "parse.c" + break; + case 182: +#line 558 "parse.y" +{ + yygotominor.yy242 = sqliteExprFunction(yymsp[-1].minor.yy322, &yymsp[-3].minor.yy0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); +} +#line 3001 "parse.c" + /* No destructor defined for LP */ + break; + case 183: +#line 562 "parse.y" +{ + yygotominor.yy242 = sqliteExprFunction(0, &yymsp[-3].minor.yy0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); +} +#line 3010 "parse.c" + /* No destructor defined for LP */ + /* No destructor defined for STAR */ + break; + case 184: +#line 566 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_AND, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3017 "parse.c" + /* No destructor defined for AND */ + break; + case 185: +#line 567 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_OR, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3023 "parse.c" + /* No destructor defined for OR */ + break; + case 186: +#line 568 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_LT, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3029 "parse.c" + /* No destructor defined for LT */ + break; + case 187: +#line 569 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_GT, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3035 "parse.c" + /* No destructor defined for GT */ + break; + case 188: +#line 570 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_LE, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3041 "parse.c" + /* No destructor defined for LE */ + break; + case 189: +#line 571 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_GE, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3047 "parse.c" + /* No destructor defined for GE */ + break; + case 190: +#line 572 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_NE, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3053 "parse.c" + /* No destructor defined for NE */ + break; + case 191: +#line 573 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_EQ, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3059 "parse.c" + /* No destructor defined for EQ */ + break; + case 192: +#line 574 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_BITAND, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3065 "parse.c" + /* No destructor defined for BITAND */ + break; + case 193: +#line 575 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_BITOR, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3071 "parse.c" + /* No destructor defined for BITOR */ + break; + case 194: +#line 576 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_LSHIFT, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3077 "parse.c" + /* No destructor defined for LSHIFT */ + break; + case 195: +#line 577 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_RSHIFT, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3083 "parse.c" + /* No destructor defined for RSHIFT */ + break; + case 196: +#line 578 "parse.y" +{ + ExprList *pList = sqliteExprListAppend(0, yymsp[0].minor.yy242, 0); + pList = sqliteExprListAppend(pList, yymsp[-2].minor.yy242, 0); + yygotominor.yy242 = sqliteExprFunction(pList, 0); + if( yygotominor.yy242 ) yygotominor.yy242->op = yymsp[-1].minor.yy372; + sqliteExprSpan(yygotominor.yy242, &yymsp[-2].minor.yy242->span, &yymsp[0].minor.yy242->span); +} +#line 3095 "parse.c" + break; + case 197: +#line 585 "parse.y" +{ + ExprList *pList = sqliteExprListAppend(0, yymsp[0].minor.yy242, 0); + pList = sqliteExprListAppend(pList, yymsp[-3].minor.yy242, 0); + yygotominor.yy242 = sqliteExprFunction(pList, 0); + if( yygotominor.yy242 ) yygotominor.yy242->op = yymsp[-1].minor.yy372; + yygotominor.yy242 = sqliteExpr(TK_NOT, yygotominor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-3].minor.yy242->span,&yymsp[0].minor.yy242->span); +} +#line 3107 "parse.c" + /* No destructor defined for NOT */ + break; + case 198: +#line 594 "parse.y" +{yygotominor.yy372 = TK_LIKE;} +#line 3113 "parse.c" + /* No destructor defined for LIKE */ + break; + case 199: +#line 595 "parse.y" +{yygotominor.yy372 = TK_GLOB;} +#line 3119 "parse.c" + /* No destructor defined for GLOB */ + break; + case 200: +#line 596 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_PLUS, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3125 "parse.c" + /* No destructor defined for PLUS */ + break; + case 201: +#line 597 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_MINUS, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3131 "parse.c" + /* No destructor defined for MINUS */ + break; + case 202: +#line 598 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_STAR, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3137 "parse.c" + /* No destructor defined for STAR */ + break; + case 203: +#line 599 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_SLASH, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3143 "parse.c" + /* No destructor defined for SLASH */ + break; + case 204: +#line 600 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_REM, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3149 "parse.c" + /* No destructor defined for REM */ + break; + case 205: +#line 601 "parse.y" +{yygotominor.yy242 = sqliteExpr(TK_CONCAT, yymsp[-2].minor.yy242, yymsp[0].minor.yy242, 0);} +#line 3155 "parse.c" + /* No destructor defined for CONCAT */ + break; + case 206: +#line 602 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_ISNULL, yymsp[-1].minor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-1].minor.yy242->span,&yymsp[0].minor.yy0); +} +#line 3164 "parse.c" + break; + case 207: +#line 606 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_ISNULL, yymsp[-2].minor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-2].minor.yy242->span,&yymsp[0].minor.yy0); +} +#line 3172 "parse.c" + /* No destructor defined for IS */ + break; + case 208: +#line 610 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_NOTNULL, yymsp[-1].minor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-1].minor.yy242->span,&yymsp[0].minor.yy0); +} +#line 3181 "parse.c" + break; + case 209: +#line 614 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_NOTNULL, yymsp[-2].minor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-2].minor.yy242->span,&yymsp[0].minor.yy0); +} +#line 3189 "parse.c" + /* No destructor defined for NOT */ + break; + case 210: +#line 618 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_NOTNULL, yymsp[-3].minor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-3].minor.yy242->span,&yymsp[0].minor.yy0); +} +#line 3198 "parse.c" + /* No destructor defined for IS */ + /* No destructor defined for NOT */ + break; + case 211: +#line 622 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_NOT, yymsp[0].minor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy242->span); +} +#line 3208 "parse.c" + break; + case 212: +#line 626 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_BITNOT, yymsp[0].minor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy242->span); +} +#line 3216 "parse.c" + break; + case 213: +#line 630 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_UMINUS, yymsp[0].minor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy242->span); +} +#line 3224 "parse.c" + break; + case 214: +#line 634 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_UPLUS, yymsp[0].minor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy242->span); +} +#line 3232 "parse.c" + break; + case 215: +#line 638 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_SELECT, 0, 0, 0); + if( yygotominor.yy242 ) yygotominor.yy242->pSelect = yymsp[-1].minor.yy179; + sqliteExprSpan(yygotominor.yy242,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); +} +#line 3241 "parse.c" + break; + case 216: +#line 643 "parse.y" +{ + ExprList *pList = sqliteExprListAppend(0, yymsp[-2].minor.yy242, 0); + pList = sqliteExprListAppend(pList, yymsp[0].minor.yy242, 0); + yygotominor.yy242 = sqliteExpr(TK_BETWEEN, yymsp[-4].minor.yy242, 0, 0); + if( yygotominor.yy242 ) yygotominor.yy242->pList = pList; + sqliteExprSpan(yygotominor.yy242,&yymsp[-4].minor.yy242->span,&yymsp[0].minor.yy242->span); +} +#line 3252 "parse.c" + /* No destructor defined for BETWEEN */ + /* No destructor defined for AND */ + break; + case 217: +#line 650 "parse.y" +{ + ExprList *pList = sqliteExprListAppend(0, yymsp[-2].minor.yy242, 0); + pList = sqliteExprListAppend(pList, yymsp[0].minor.yy242, 0); + yygotominor.yy242 = sqliteExpr(TK_BETWEEN, yymsp[-5].minor.yy242, 0, 0); + if( yygotominor.yy242 ) yygotominor.yy242->pList = pList; + yygotominor.yy242 = sqliteExpr(TK_NOT, yygotominor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-5].minor.yy242->span,&yymsp[0].minor.yy242->span); +} +#line 3266 "parse.c" + /* No destructor defined for NOT */ + /* No destructor defined for BETWEEN */ + /* No destructor defined for AND */ + break; + case 218: +#line 658 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_IN, yymsp[-4].minor.yy242, 0, 0); + if( yygotominor.yy242 ) yygotominor.yy242->pList = yymsp[-1].minor.yy322; + sqliteExprSpan(yygotominor.yy242,&yymsp[-4].minor.yy242->span,&yymsp[0].minor.yy0); +} +#line 3278 "parse.c" + /* No destructor defined for IN */ + /* No destructor defined for LP */ + break; + case 219: +#line 663 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_IN, yymsp[-4].minor.yy242, 0, 0); + if( yygotominor.yy242 ) yygotominor.yy242->pSelect = yymsp[-1].minor.yy179; + sqliteExprSpan(yygotominor.yy242,&yymsp[-4].minor.yy242->span,&yymsp[0].minor.yy0); +} +#line 3289 "parse.c" + /* No destructor defined for IN */ + /* No destructor defined for LP */ + break; + case 220: +#line 668 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_IN, yymsp[-5].minor.yy242, 0, 0); + if( yygotominor.yy242 ) yygotominor.yy242->pList = yymsp[-1].minor.yy322; + yygotominor.yy242 = sqliteExpr(TK_NOT, yygotominor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-5].minor.yy242->span,&yymsp[0].minor.yy0); +} +#line 3301 "parse.c" + /* No destructor defined for NOT */ + /* No destructor defined for IN */ + /* No destructor defined for LP */ + break; + case 221: +#line 674 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_IN, yymsp[-5].minor.yy242, 0, 0); + if( yygotominor.yy242 ) yygotominor.yy242->pSelect = yymsp[-1].minor.yy179; + yygotominor.yy242 = sqliteExpr(TK_NOT, yygotominor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-5].minor.yy242->span,&yymsp[0].minor.yy0); +} +#line 3314 "parse.c" + /* No destructor defined for NOT */ + /* No destructor defined for IN */ + /* No destructor defined for LP */ + break; + case 222: +#line 680 "parse.y" +{ + SrcList *pSrc = sqliteSrcListAppend(0, &yymsp[-1].minor.yy298, &yymsp[0].minor.yy298); + yygotominor.yy242 = sqliteExpr(TK_IN, yymsp[-3].minor.yy242, 0, 0); + if( yygotominor.yy242 ) yygotominor.yy242->pSelect = sqliteSelectNew(0,pSrc,0,0,0,0,0,-1,0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-3].minor.yy242->span,yymsp[0].minor.yy298.z?&yymsp[0].minor.yy298:&yymsp[-1].minor.yy298); +} +#line 3327 "parse.c" + /* No destructor defined for IN */ + break; + case 223: +#line 686 "parse.y" +{ + SrcList *pSrc = sqliteSrcListAppend(0, &yymsp[-1].minor.yy298, &yymsp[0].minor.yy298); + yygotominor.yy242 = sqliteExpr(TK_IN, yymsp[-4].minor.yy242, 0, 0); + if( yygotominor.yy242 ) yygotominor.yy242->pSelect = sqliteSelectNew(0,pSrc,0,0,0,0,0,-1,0); + yygotominor.yy242 = sqliteExpr(TK_NOT, yygotominor.yy242, 0, 0); + sqliteExprSpan(yygotominor.yy242,&yymsp[-4].minor.yy242->span,yymsp[0].minor.yy298.z?&yymsp[0].minor.yy298:&yymsp[-1].minor.yy298); +} +#line 3339 "parse.c" + /* No destructor defined for NOT */ + /* No destructor defined for IN */ + break; + case 224: +#line 696 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_CASE, yymsp[-3].minor.yy242, yymsp[-1].minor.yy242, 0); + if( yygotominor.yy242 ) yygotominor.yy242->pList = yymsp[-2].minor.yy322; + sqliteExprSpan(yygotominor.yy242, &yymsp[-4].minor.yy0, &yymsp[0].minor.yy0); +} +#line 3350 "parse.c" + break; + case 225: +#line 703 "parse.y" +{ + yygotominor.yy322 = sqliteExprListAppend(yymsp[-4].minor.yy322, yymsp[-2].minor.yy242, 0); + yygotominor.yy322 = sqliteExprListAppend(yygotominor.yy322, yymsp[0].minor.yy242, 0); +} +#line 3358 "parse.c" + /* No destructor defined for WHEN */ + /* No destructor defined for THEN */ + break; + case 226: +#line 707 "parse.y" +{ + yygotominor.yy322 = sqliteExprListAppend(0, yymsp[-2].minor.yy242, 0); + yygotominor.yy322 = sqliteExprListAppend(yygotominor.yy322, yymsp[0].minor.yy242, 0); +} +#line 3368 "parse.c" + /* No destructor defined for WHEN */ + /* No destructor defined for THEN */ + break; + case 227: +#line 712 "parse.y" +{yygotominor.yy242 = yymsp[0].minor.yy242;} +#line 3375 "parse.c" + /* No destructor defined for ELSE */ + break; + case 228: +#line 713 "parse.y" +{yygotominor.yy242 = 0;} +#line 3381 "parse.c" + break; + case 229: +#line 715 "parse.y" +{yygotominor.yy242 = yymsp[0].minor.yy242;} +#line 3386 "parse.c" + break; + case 230: +#line 716 "parse.y" +{yygotominor.yy242 = 0;} +#line 3391 "parse.c" + break; + case 231: +#line 724 "parse.y" +{yygotominor.yy322 = sqliteExprListAppend(yymsp[-2].minor.yy322,yymsp[0].minor.yy242,0);} +#line 3396 "parse.c" + /* No destructor defined for COMMA */ + break; + case 232: +#line 725 "parse.y" +{yygotominor.yy322 = sqliteExprListAppend(0,yymsp[0].minor.yy242,0);} +#line 3402 "parse.c" + break; + case 233: +#line 726 "parse.y" +{yygotominor.yy242 = yymsp[0].minor.yy242;} +#line 3407 "parse.c" + break; + case 234: +#line 727 "parse.y" +{yygotominor.yy242 = 0;} +#line 3412 "parse.c" + break; + case 235: +#line 732 "parse.y" +{ + SrcList *pSrc = sqliteSrcListAppend(0, &yymsp[-5].minor.yy298, &yymsp[-4].minor.yy298); + if( yymsp[-9].minor.yy372!=OE_None ) yymsp[-9].minor.yy372 = yymsp[0].minor.yy372; + if( yymsp[-9].minor.yy372==OE_Default) yymsp[-9].minor.yy372 = OE_Abort; + sqliteCreateIndex(pParse, &yymsp[-7].minor.yy298, pSrc, yymsp[-2].minor.yy320, yymsp[-9].minor.yy372, &yymsp[-10].minor.yy0, &yymsp[-1].minor.yy0); +} +#line 3422 "parse.c" + /* No destructor defined for INDEX */ + /* No destructor defined for ON */ + /* No destructor defined for LP */ + break; + case 236: +#line 740 "parse.y" +{ yygotominor.yy372 = OE_Abort; } +#line 3430 "parse.c" + /* No destructor defined for UNIQUE */ + break; + case 237: +#line 741 "parse.y" +{ yygotominor.yy372 = OE_None; } +#line 3436 "parse.c" + break; + case 238: +#line 749 "parse.y" +{yygotominor.yy320 = 0;} +#line 3441 "parse.c" + break; + case 239: +#line 750 "parse.y" +{yygotominor.yy320 = yymsp[-1].minor.yy320;} +#line 3446 "parse.c" + /* No destructor defined for LP */ + /* No destructor defined for RP */ + break; + case 240: +#line 751 "parse.y" +{yygotominor.yy320 = sqliteIdListAppend(yymsp[-2].minor.yy320,&yymsp[0].minor.yy298);} +#line 3453 "parse.c" + /* No destructor defined for COMMA */ + break; + case 241: +#line 752 "parse.y" +{yygotominor.yy320 = sqliteIdListAppend(0,&yymsp[0].minor.yy298);} +#line 3459 "parse.c" + break; + case 242: +#line 753 "parse.y" +{yygotominor.yy298 = yymsp[-1].minor.yy298;} +#line 3464 "parse.c" + /* No destructor defined for sortorder */ + break; + case 243: +#line 758 "parse.y" +{ + sqliteDropIndex(pParse, sqliteSrcListAppend(0,&yymsp[-1].minor.yy298,&yymsp[0].minor.yy298)); +} +#line 3472 "parse.c" + /* No destructor defined for DROP */ + /* No destructor defined for INDEX */ + break; + case 244: +#line 766 "parse.y" +{sqliteCopy(pParse,sqliteSrcListAppend(0,&yymsp[-6].minor.yy298,&yymsp[-5].minor.yy298),&yymsp[-3].minor.yy298,&yymsp[0].minor.yy0,yymsp[-7].minor.yy372);} +#line 3479 "parse.c" + /* No destructor defined for COPY */ + /* No destructor defined for FROM */ + /* No destructor defined for USING */ + /* No destructor defined for DELIMITERS */ + break; + case 245: +#line 768 "parse.y" +{sqliteCopy(pParse,sqliteSrcListAppend(0,&yymsp[-3].minor.yy298,&yymsp[-2].minor.yy298),&yymsp[0].minor.yy298,0,yymsp[-4].minor.yy372);} +#line 3488 "parse.c" + /* No destructor defined for COPY */ + /* No destructor defined for FROM */ + break; + case 246: +#line 772 "parse.y" +{sqliteVacuum(pParse,0);} +#line 3495 "parse.c" + /* No destructor defined for VACUUM */ + break; + case 247: +#line 773 "parse.y" +{sqliteVacuum(pParse,&yymsp[0].minor.yy298);} +#line 3501 "parse.c" + /* No destructor defined for VACUUM */ + break; + case 248: +#line 777 "parse.y" +{sqlitePragma(pParse,&yymsp[-2].minor.yy298,&yymsp[0].minor.yy298,0);} +#line 3507 "parse.c" + /* No destructor defined for PRAGMA */ + /* No destructor defined for EQ */ + break; + case 249: +#line 778 "parse.y" +{sqlitePragma(pParse,&yymsp[-2].minor.yy298,&yymsp[0].minor.yy0,0);} +#line 3514 "parse.c" + /* No destructor defined for PRAGMA */ + /* No destructor defined for EQ */ + break; + case 250: +#line 779 "parse.y" +{sqlitePragma(pParse,&yymsp[-2].minor.yy298,&yymsp[0].minor.yy298,0);} +#line 3521 "parse.c" + /* No destructor defined for PRAGMA */ + /* No destructor defined for EQ */ + break; + case 251: +#line 780 "parse.y" +{sqlitePragma(pParse,&yymsp[-2].minor.yy298,&yymsp[0].minor.yy298,1);} +#line 3528 "parse.c" + /* No destructor defined for PRAGMA */ + /* No destructor defined for EQ */ + break; + case 252: +#line 781 "parse.y" +{sqlitePragma(pParse,&yymsp[-3].minor.yy298,&yymsp[-1].minor.yy298,0);} +#line 3535 "parse.c" + /* No destructor defined for PRAGMA */ + /* No destructor defined for LP */ + /* No destructor defined for RP */ + break; + case 253: +#line 782 "parse.y" +{sqlitePragma(pParse,&yymsp[0].minor.yy298,&yymsp[0].minor.yy298,0);} +#line 3543 "parse.c" + /* No destructor defined for PRAGMA */ + break; + case 254: +#line 783 "parse.y" +{yygotominor.yy298 = yymsp[0].minor.yy298;} +#line 3549 "parse.c" + /* No destructor defined for plus_opt */ + break; + case 255: +#line 784 "parse.y" +{yygotominor.yy298 = yymsp[0].minor.yy298;} +#line 3555 "parse.c" + /* No destructor defined for MINUS */ + break; + case 256: +#line 785 "parse.y" +{yygotominor.yy298 = yymsp[0].minor.yy0;} +#line 3561 "parse.c" + break; + case 257: +#line 786 "parse.y" +{yygotominor.yy298 = yymsp[0].minor.yy0;} +#line 3566 "parse.c" + break; + case 258: + /* No destructor defined for PLUS */ + break; + case 259: + break; + case 260: +#line 792 "parse.y" +{ + Token all; + all.z = yymsp[-4].minor.yy0.z; + all.n = (yymsp[0].minor.yy0.z - yymsp[-4].minor.yy0.z) + yymsp[0].minor.yy0.n; + sqliteFinishTrigger(pParse, yymsp[-1].minor.yy19, &all); +} +#line 3581 "parse.c" + /* No destructor defined for trigger_decl */ + /* No destructor defined for BEGIN */ + break; + case 261: +#line 800 "parse.y" +{ + SrcList *pTab = sqliteSrcListAppend(0, &yymsp[-3].minor.yy298, &yymsp[-2].minor.yy298); + sqliteBeginTrigger(pParse, &yymsp[-7].minor.yy298, yymsp[-6].minor.yy372, yymsp[-5].minor.yy290.a, yymsp[-5].minor.yy290.b, pTab, yymsp[-1].minor.yy372, yymsp[0].minor.yy182, yymsp[-9].minor.yy372); +} +#line 3591 "parse.c" + /* No destructor defined for TRIGGER */ + /* No destructor defined for ON */ + break; + case 262: +#line 806 "parse.y" +{ yygotominor.yy372 = TK_BEFORE; } +#line 3598 "parse.c" + /* No destructor defined for BEFORE */ + break; + case 263: +#line 807 "parse.y" +{ yygotominor.yy372 = TK_AFTER; } +#line 3604 "parse.c" + /* No destructor defined for AFTER */ + break; + case 264: +#line 808 "parse.y" +{ yygotominor.yy372 = TK_INSTEAD;} +#line 3610 "parse.c" + /* No destructor defined for INSTEAD */ + /* No destructor defined for OF */ + break; + case 265: +#line 809 "parse.y" +{ yygotominor.yy372 = TK_BEFORE; } +#line 3617 "parse.c" + break; + case 266: +#line 813 "parse.y" +{ yygotominor.yy290.a = TK_DELETE; yygotominor.yy290.b = 0; } +#line 3622 "parse.c" + /* No destructor defined for DELETE */ + break; + case 267: +#line 814 "parse.y" +{ yygotominor.yy290.a = TK_INSERT; yygotominor.yy290.b = 0; } +#line 3628 "parse.c" + /* No destructor defined for INSERT */ + break; + case 268: +#line 815 "parse.y" +{ yygotominor.yy290.a = TK_UPDATE; yygotominor.yy290.b = 0;} +#line 3634 "parse.c" + /* No destructor defined for UPDATE */ + break; + case 269: +#line 816 "parse.y" +{yygotominor.yy290.a = TK_UPDATE; yygotominor.yy290.b = yymsp[0].minor.yy320; } +#line 3640 "parse.c" + /* No destructor defined for UPDATE */ + /* No destructor defined for OF */ + break; + case 270: +#line 819 "parse.y" +{ yygotominor.yy372 = TK_ROW; } +#line 3647 "parse.c" + break; + case 271: +#line 820 "parse.y" +{ yygotominor.yy372 = TK_ROW; } +#line 3652 "parse.c" + /* No destructor defined for FOR */ + /* No destructor defined for EACH */ + /* No destructor defined for ROW */ + break; + case 272: +#line 821 "parse.y" +{ yygotominor.yy372 = TK_STATEMENT; } +#line 3660 "parse.c" + /* No destructor defined for FOR */ + /* No destructor defined for EACH */ + /* No destructor defined for STATEMENT */ + break; + case 273: +#line 824 "parse.y" +{ yygotominor.yy182 = 0; } +#line 3668 "parse.c" + break; + case 274: +#line 825 "parse.y" +{ yygotominor.yy182 = yymsp[0].minor.yy242; } +#line 3673 "parse.c" + /* No destructor defined for WHEN */ + break; + case 275: +#line 829 "parse.y" +{ + yymsp[-2].minor.yy19->pNext = yymsp[0].minor.yy19; + yygotominor.yy19 = yymsp[-2].minor.yy19; +} +#line 3682 "parse.c" + /* No destructor defined for SEMI */ + break; + case 276: +#line 833 "parse.y" +{ yygotominor.yy19 = 0; } +#line 3688 "parse.c" + break; + case 277: +#line 839 "parse.y" +{ yygotominor.yy19 = sqliteTriggerUpdateStep(&yymsp[-3].minor.yy298, yymsp[-1].minor.yy322, yymsp[0].minor.yy242, yymsp[-4].minor.yy372); } +#line 3693 "parse.c" + /* No destructor defined for UPDATE */ + /* No destructor defined for SET */ + break; + case 278: +#line 844 "parse.y" +{yygotominor.yy19 = sqliteTriggerInsertStep(&yymsp[-5].minor.yy298, yymsp[-4].minor.yy320, yymsp[-1].minor.yy322, 0, yymsp[-7].minor.yy372);} +#line 3700 "parse.c" + /* No destructor defined for INTO */ + /* No destructor defined for VALUES */ + /* No destructor defined for LP */ + /* No destructor defined for RP */ + break; + case 279: +#line 847 "parse.y" +{yygotominor.yy19 = sqliteTriggerInsertStep(&yymsp[-2].minor.yy298, yymsp[-1].minor.yy320, 0, yymsp[0].minor.yy179, yymsp[-4].minor.yy372);} +#line 3709 "parse.c" + /* No destructor defined for INTO */ + break; + case 280: +#line 851 "parse.y" +{yygotominor.yy19 = sqliteTriggerDeleteStep(&yymsp[-1].minor.yy298, yymsp[0].minor.yy242);} +#line 3715 "parse.c" + /* No destructor defined for DELETE */ + /* No destructor defined for FROM */ + break; + case 281: +#line 854 "parse.y" +{yygotominor.yy19 = sqliteTriggerSelectStep(yymsp[0].minor.yy179); } +#line 3722 "parse.c" + break; + case 282: +#line 857 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_RAISE, 0, 0, 0); + yygotominor.yy242->iColumn = OE_Ignore; + sqliteExprSpan(yygotominor.yy242, &yymsp[-3].minor.yy0, &yymsp[0].minor.yy0); +} +#line 3731 "parse.c" + /* No destructor defined for LP */ + /* No destructor defined for IGNORE */ + break; + case 283: +#line 862 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_RAISE, 0, 0, &yymsp[-1].minor.yy298); + yygotominor.yy242->iColumn = OE_Rollback; + sqliteExprSpan(yygotominor.yy242, &yymsp[-5].minor.yy0, &yymsp[0].minor.yy0); +} +#line 3742 "parse.c" + /* No destructor defined for LP */ + /* No destructor defined for ROLLBACK */ + /* No destructor defined for COMMA */ + break; + case 284: +#line 867 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_RAISE, 0, 0, &yymsp[-1].minor.yy298); + yygotominor.yy242->iColumn = OE_Abort; + sqliteExprSpan(yygotominor.yy242, &yymsp[-5].minor.yy0, &yymsp[0].minor.yy0); +} +#line 3754 "parse.c" + /* No destructor defined for LP */ + /* No destructor defined for ABORT */ + /* No destructor defined for COMMA */ + break; + case 285: +#line 872 "parse.y" +{ + yygotominor.yy242 = sqliteExpr(TK_RAISE, 0, 0, &yymsp[-1].minor.yy298); + yygotominor.yy242->iColumn = OE_Fail; + sqliteExprSpan(yygotominor.yy242, &yymsp[-5].minor.yy0, &yymsp[0].minor.yy0); +} +#line 3766 "parse.c" + /* No destructor defined for LP */ + /* No destructor defined for FAIL */ + /* No destructor defined for COMMA */ + break; + case 286: +#line 879 "parse.y" +{ + sqliteDropTrigger(pParse,sqliteSrcListAppend(0,&yymsp[-1].minor.yy298,&yymsp[0].minor.yy298)); +} +#line 3776 "parse.c" + /* No destructor defined for DROP */ + /* No destructor defined for TRIGGER */ + break; + case 287: +#line 884 "parse.y" +{ + sqliteAttach(pParse, &yymsp[-3].minor.yy298, &yymsp[-1].minor.yy298, &yymsp[0].minor.yy298); +} +#line 3785 "parse.c" + /* No destructor defined for ATTACH */ + /* No destructor defined for database_kw_opt */ + /* No destructor defined for AS */ + break; + case 288: +#line 888 "parse.y" +{ yygotominor.yy298 = yymsp[0].minor.yy298; } +#line 3793 "parse.c" + /* No destructor defined for USING */ + break; + case 289: +#line 889 "parse.y" +{ yygotominor.yy298.z = 0; yygotominor.yy298.n = 0; } +#line 3799 "parse.c" + break; + case 290: + /* No destructor defined for DATABASE */ + break; + case 291: + break; + case 292: +#line 895 "parse.y" +{ + sqliteDetach(pParse, &yymsp[0].minor.yy298); +} +#line 3811 "parse.c" + /* No destructor defined for DETACH */ + /* No destructor defined for database_kw_opt */ + break; + }; + yygoto = yyRuleInfo[yyruleno].lhs; + yysize = yyRuleInfo[yyruleno].nrhs; + yypParser->yyidx -= yysize; + yyact = yy_find_reduce_action(yypParser,yygoto); + if( yyact < YYNSTATE ){ + yy_shift(yypParser,yyact,yygoto,&yygotominor); + }else if( yyact == YYNSTATE + YYNRULE + 1 ){ + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + sqliteParserARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ + sqliteParserARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + YYMINORTYPE yyminor /* The minor type of the error token */ +){ + sqliteParserARG_FETCH; +#define TOKEN (yyminor.yy0) +#line 23 "parse.y" + + if( pParse->zErrMsg==0 ){ + if( TOKEN.z[0] ){ + sqliteErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN); + }else{ + sqliteErrorMsg(pParse, "incomplete SQL statement"); + } + } + +#line 3865 "parse.c" + sqliteParserARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + sqliteParserARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ + sqliteParserARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "sqliteParserAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +**
      +**
    • A pointer to the parser (an opaque structure.) +**
    • The major token number. +**
    • The minor token number. +**
    • An option argument of a grammar-specified type. +**
    +** +** Outputs: +** None. +*/ +void sqliteParser( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + sqliteParserTOKENTYPE yyminor /* The value for the token */ + sqliteParserARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + int yyact; /* The parser action. */ + int yyendofinput; /* True if we are at the end of input */ + int yyerrorhit = 0; /* True if yymajor has invoked an error */ + yyParser *yypParser; /* The parser */ + + /* (re)initialize the parser, if necessary */ + yypParser = (yyParser*)yyp; + if( yypParser->yyidx<0 ){ + if( yymajor==0 ) return; + yypParser->yyidx = 0; + yypParser->yyerrcnt = -1; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; + } + yyminorunion.yy0 = yyminor; + yyendofinput = (yymajor==0); + sqliteParserARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]); + } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,yymajor); + if( yyactyyerrcnt--; + if( yyendofinput && yypParser->yyidx>=0 ){ + yymajor = 0; + }else{ + yymajor = YYNOCODE; + } + }else if( yyact < YYNSTATE + YYNRULE ){ + yy_reduce(yypParser,yyact-YYNSTATE); + }else if( yyact == YY_ERROR_ACTION ){ + int yymx; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yymx = yypParser->yystack[yypParser->yyidx].major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yymajor,&yyminorunion); + yymajor = YYNOCODE; + }else{ + while( + yypParser->yyidx >= 0 && + yymx != YYERRORSYMBOL && + (yyact = yy_find_shift_action(yypParser,YYERRORSYMBOL)) >= YYNSTATE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yyidx < 0 || yymajor==0 ){ + yy_destructor(yymajor,&yyminorunion); + yy_parse_failed(yypParser); + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + YYMINORTYPE u2; + u2.YYERRSYMDT = 0; + yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + } + } + yypParser->yyerrcnt = 3; + yyerrorhit = 1; +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yypParser->yyerrcnt = 3; + yy_destructor(yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); + } + yymajor = YYNOCODE; +#endif + }else{ + yy_accept(yypParser); + yymajor = YYNOCODE; + } + }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); + return; +} diff --git a/src/libs/sqlite2/parse.h b/src/libs/sqlite2/parse.h new file mode 100644 index 00000000..188a336c --- /dev/null +++ b/src/libs/sqlite2/parse.h @@ -0,0 +1,130 @@ +#define TK_END_OF_FILE 1 +#define TK_ILLEGAL 2 +#define TK_SPACE 3 +#define TK_UNCLOSED_STRING 4 +#define TK_COMMENT 5 +#define TK_FUNCTION 6 +#define TK_COLUMN 7 +#define TK_AGG_FUNCTION 8 +#define TK_SEMI 9 +#define TK_EXPLAIN 10 +#define TK_BEGIN 11 +#define TK_TRANSACTION 12 +#define TK_COMMIT 13 +#define TK_END 14 +#define TK_ROLLBACK 15 +#define TK_CREATE 16 +#define TK_TABLE 17 +#define TK_TEMP 18 +#define TK_LP 19 +#define TK_RP 20 +#define TK_AS 21 +#define TK_COMMA 22 +#define TK_ID 23 +#define TK_ABORT 24 +#define TK_AFTER 25 +#define TK_ASC 26 +#define TK_ATTACH 27 +#define TK_BEFORE 28 +#define TK_CASCADE 29 +#define TK_CLUSTER 30 +#define TK_CONFLICT 31 +#define TK_COPY 32 +#define TK_DATABASE 33 +#define TK_DEFERRED 34 +#define TK_DELIMITERS 35 +#define TK_DESC 36 +#define TK_DETACH 37 +#define TK_EACH 38 +#define TK_FAIL 39 +#define TK_FOR 40 +#define TK_GLOB 41 +#define TK_IGNORE 42 +#define TK_IMMEDIATE 43 +#define TK_INITIALLY 44 +#define TK_INSTEAD 45 +#define TK_LIKE 46 +#define TK_MATCH 47 +#define TK_KEY 48 +#define TK_OF 49 +#define TK_OFFSET 50 +#define TK_PRAGMA 51 +#define TK_RAISE 52 +#define TK_REPLACE 53 +#define TK_RESTRICT 54 +#define TK_ROW 55 +#define TK_STATEMENT 56 +#define TK_TRIGGER 57 +#define TK_VACUUM 58 +#define TK_VIEW 59 +#define TK_OR 60 +#define TK_AND 61 +#define TK_NOT 62 +#define TK_EQ 63 +#define TK_NE 64 +#define TK_ISNULL 65 +#define TK_NOTNULL 66 +#define TK_IS 67 +#define TK_BETWEEN 68 +#define TK_IN 69 +#define TK_GT 70 +#define TK_GE 71 +#define TK_LT 72 +#define TK_LE 73 +#define TK_BITAND 74 +#define TK_BITOR 75 +#define TK_LSHIFT 76 +#define TK_RSHIFT 77 +#define TK_PLUS 78 +#define TK_MINUS 79 +#define TK_STAR 80 +#define TK_SLASH 81 +#define TK_REM 82 +#define TK_CONCAT 83 +#define TK_UMINUS 84 +#define TK_UPLUS 85 +#define TK_BITNOT 86 +#define TK_STRING 87 +#define TK_JOIN_KW 88 +#define TK_INTEGER 89 +#define TK_CONSTRAINT 90 +#define TK_DEFAULT 91 +#define TK_FLOAT 92 +#define TK_NULL 93 +#define TK_PRIMARY 94 +#define TK_UNIQUE 95 +#define TK_CHECK 96 +#define TK_REFERENCES 97 +#define TK_COLLATE 98 +#define TK_ON 99 +#define TK_DELETE 100 +#define TK_UPDATE 101 +#define TK_INSERT 102 +#define TK_SET 103 +#define TK_DEFERRABLE 104 +#define TK_FOREIGN 105 +#define TK_DROP 106 +#define TK_UNION 107 +#define TK_ALL 108 +#define TK_INTERSECT 109 +#define TK_EXCEPT 110 +#define TK_SELECT 111 +#define TK_DISTINCT 112 +#define TK_DOT 113 +#define TK_FROM 114 +#define TK_JOIN 115 +#define TK_USING 116 +#define TK_ORDER 117 +#define TK_BY 118 +#define TK_GROUP 119 +#define TK_HAVING 120 +#define TK_LIMIT 121 +#define TK_WHERE 122 +#define TK_INTO 123 +#define TK_VALUES 124 +#define TK_VARIABLE 125 +#define TK_CASE 126 +#define TK_WHEN 127 +#define TK_THEN 128 +#define TK_ELSE 129 +#define TK_INDEX 130 diff --git a/src/libs/sqlite2/pragma.c b/src/libs/sqlite2/pragma.c new file mode 100644 index 00000000..7cb637fd --- /dev/null +++ b/src/libs/sqlite2/pragma.c @@ -0,0 +1,712 @@ +/* +** 2003 April 6 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code used to implement the PRAGMA command. +** +** $Id: pragma.c 326789 2004-07-07 21:25:56Z pahlibar $ +*/ +#include "sqliteInt.h" +#include + +/* +** Interpret the given string as a boolean value. +*/ +static int getBoolean(const char *z){ + static char *azTrue[] = { "yes", "on", "true" }; + int i; + if( z[0]==0 ) return 0; + if( isdigit(z[0]) || (z[0]=='-' && isdigit(z[1])) ){ + return atoi(z); + } + for(i=0; i='0' && z[0]<='2' ){ + return z[0] - '0'; + }else if( sqliteStrICmp(z, "file")==0 ){ + return 1; + }else if( sqliteStrICmp(z, "memory")==0 ){ + return 2; + }else{ + return 0; + } +} + +/* +** If the TEMP database is open, close it and mark the database schema +** as needing reloading. This must be done when using the TEMP_STORE +** or DEFAULT_TEMP_STORE pragmas. +*/ +static int changeTempStorage(Parse *pParse, const char *zStorageType){ + int ts = getTempStore(zStorageType); + sqlite *db = pParse->db; + if( db->temp_store==ts ) return SQLITE_OK; + if( db->aDb[1].pBt!=0 ){ + if( db->flags & SQLITE_InTrans ){ + sqliteErrorMsg(pParse, "temporary storage cannot be changed " + "from within a transaction"); + return SQLITE_ERROR; + } + sqliteBtreeClose(db->aDb[1].pBt); + db->aDb[1].pBt = 0; + sqliteResetInternalSchema(db, 0); + } + db->temp_store = ts; + return SQLITE_OK; +} + +/* +** Check to see if zRight and zLeft refer to a pragma that queries +** or changes one of the flags in db->flags. Return 1 if so and 0 if not. +** Also, implement the pragma. +*/ +static int flagPragma(Parse *pParse, const char *zLeft, const char *zRight){ + static const struct { + const char *zName; /* Name of the pragma */ + int mask; /* Mask for the db->flags value */ + } aPragma[] = { + { "vdbe_trace", SQLITE_VdbeTrace }, + { "full_column_names", SQLITE_FullColNames }, + { "short_column_names", SQLITE_ShortColNames }, + { "show_datatypes", SQLITE_ReportTypes }, + { "count_changes", SQLITE_CountRows }, + { "empty_result_callbacks", SQLITE_NullCallback }, + }; + int i; + for(i=0; idb; + Vdbe *v; + if( strcmp(zLeft,zRight)==0 && (v = sqliteGetVdbe(pParse))!=0 ){ + sqliteVdbeOp3(v, OP_ColumnName, 0, 1, aPragma[i].zName, P3_STATIC); + sqliteVdbeOp3(v, OP_ColumnName, 1, 0, "boolean", P3_STATIC); + sqliteVdbeCode(v, OP_Integer, (db->flags & aPragma[i].mask)!=0, 0, + OP_Callback, 1, 0, + 0); + }else if( getBoolean(zRight) ){ + db->flags |= aPragma[i].mask; + }else{ + db->flags &= ~aPragma[i].mask; + } + return 1; + } + } + return 0; +} + +/* +** Process a pragma statement. +** +** Pragmas are of this form: +** +** PRAGMA id = value +** +** The identifier might also be a string. The value is a string, and +** identifier, or a number. If minusFlag is true, then the value is +** a number that was preceded by a minus sign. +*/ +void sqlitePragma(Parse *pParse, Token *pLeft, Token *pRight, int minusFlag){ + char *zLeft = 0; + char *zRight = 0; + sqlite *db = pParse->db; + Vdbe *v = sqliteGetVdbe(pParse); + if( v==0 ) return; + + zLeft = sqliteStrNDup(pLeft->z, pLeft->n); + sqliteDequote(zLeft); + if( minusFlag ){ + zRight = 0; + sqliteSetNString(&zRight, "-", 1, pRight->z, pRight->n, 0); + }else{ + zRight = sqliteStrNDup(pRight->z, pRight->n); + sqliteDequote(zRight); + } + if( sqliteAuthCheck(pParse, SQLITE_PRAGMA, zLeft, zRight, 0) ){ + sqliteFree(zLeft); + sqliteFree(zRight); + return; + } + + /* + ** PRAGMA default_cache_size + ** PRAGMA default_cache_size=N + ** + ** The first form reports the current persistent setting for the + ** page cache size. The value returned is the maximum number of + ** pages in the page cache. The second form sets both the current + ** page cache size value and the persistent page cache size value + ** stored in the database file. + ** + ** The default cache size is stored in meta-value 2 of page 1 of the + ** database file. The cache size is actually the absolute value of + ** this memory location. The sign of meta-value 2 determines the + ** synchronous setting. A negative value means synchronous is off + ** and a positive value means synchronous is on. + */ + if( sqliteStrICmp(zLeft,"default_cache_size")==0 ){ + static VdbeOpList getCacheSize[] = { + { OP_ReadCookie, 0, 2, 0}, + { OP_AbsValue, 0, 0, 0}, + { OP_Dup, 0, 0, 0}, + { OP_Integer, 0, 0, 0}, + { OP_Ne, 0, 6, 0}, + { OP_Integer, 0, 0, 0}, /* 5 */ + { OP_ColumnName, 0, 1, "cache_size"}, + { OP_Callback, 1, 0, 0}, + }; + int addr; + if( pRight->z==pLeft->z ){ + addr = sqliteVdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize); + sqliteVdbeChangeP1(v, addr+5, MAX_PAGES); + }else{ + int size = atoi(zRight); + if( size<0 ) size = -size; + sqliteBeginWriteOperation(pParse, 0, 0); + sqliteVdbeAddOp(v, OP_Integer, size, 0); + sqliteVdbeAddOp(v, OP_ReadCookie, 0, 2); + addr = sqliteVdbeAddOp(v, OP_Integer, 0, 0); + sqliteVdbeAddOp(v, OP_Ge, 0, addr+3); + sqliteVdbeAddOp(v, OP_Negative, 0, 0); + sqliteVdbeAddOp(v, OP_SetCookie, 0, 2); + sqliteEndWriteOperation(pParse); + db->cache_size = db->cache_size<0 ? -size : size; + sqliteBtreeSetCacheSize(db->aDb[0].pBt, db->cache_size); + } + }else + + /* + ** PRAGMA cache_size + ** PRAGMA cache_size=N + ** + ** The first form reports the current local setting for the + ** page cache size. The local setting can be different from + ** the persistent cache size value that is stored in the database + ** file itself. The value returned is the maximum number of + ** pages in the page cache. The second form sets the local + ** page cache size value. It does not change the persistent + ** cache size stored on the disk so the cache size will revert + ** to its default value when the database is closed and reopened. + ** N should be a positive integer. + */ + if( sqliteStrICmp(zLeft,"cache_size")==0 ){ + static VdbeOpList getCacheSize[] = { + { OP_ColumnName, 0, 1, "cache_size"}, + { OP_Callback, 1, 0, 0}, + }; + if( pRight->z==pLeft->z ){ + int size = db->cache_size;; + if( size<0 ) size = -size; + sqliteVdbeAddOp(v, OP_Integer, size, 0); + sqliteVdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize); + }else{ + int size = atoi(zRight); + if( size<0 ) size = -size; + if( db->cache_size<0 ) size = -size; + db->cache_size = size; + sqliteBtreeSetCacheSize(db->aDb[0].pBt, db->cache_size); + } + }else + + /* + ** PRAGMA default_synchronous + ** PRAGMA default_synchronous=ON|OFF|NORMAL|FULL + ** + ** The first form returns the persistent value of the "synchronous" setting + ** that is stored in the database. This is the synchronous setting that + ** is used whenever the database is opened unless overridden by a separate + ** "synchronous" pragma. The second form changes the persistent and the + ** local synchronous setting to the value given. + ** + ** If synchronous is OFF, SQLite does not attempt any fsync() systems calls + ** to make sure data is committed to disk. Write operations are very fast, + ** but a power failure can leave the database in an inconsistent state. + ** If synchronous is ON or NORMAL, SQLite will do an fsync() system call to + ** make sure data is being written to disk. The risk of corruption due to + ** a power loss in this mode is negligible but non-zero. If synchronous + ** is FULL, extra fsync()s occur to reduce the risk of corruption to near + ** zero, but with a write performance penalty. The default mode is NORMAL. + */ + if( sqliteStrICmp(zLeft,"default_synchronous")==0 ){ + static VdbeOpList getSync[] = { + { OP_ColumnName, 0, 1, "synchronous"}, + { OP_ReadCookie, 0, 3, 0}, + { OP_Dup, 0, 0, 0}, + { OP_If, 0, 0, 0}, /* 3 */ + { OP_ReadCookie, 0, 2, 0}, + { OP_Integer, 0, 0, 0}, + { OP_Lt, 0, 5, 0}, + { OP_AddImm, 1, 0, 0}, + { OP_Callback, 1, 0, 0}, + { OP_Halt, 0, 0, 0}, + { OP_AddImm, -1, 0, 0}, /* 10 */ + { OP_Callback, 1, 0, 0} + }; + if( pRight->z==pLeft->z ){ + int addr = sqliteVdbeAddOpList(v, ArraySize(getSync), getSync); + sqliteVdbeChangeP2(v, addr+3, addr+10); + }else{ + int addr; + int size = db->cache_size; + if( size<0 ) size = -size; + sqliteBeginWriteOperation(pParse, 0, 0); + sqliteVdbeAddOp(v, OP_ReadCookie, 0, 2); + sqliteVdbeAddOp(v, OP_Dup, 0, 0); + addr = sqliteVdbeAddOp(v, OP_Integer, 0, 0); + sqliteVdbeAddOp(v, OP_Ne, 0, addr+3); + sqliteVdbeAddOp(v, OP_AddImm, MAX_PAGES, 0); + sqliteVdbeAddOp(v, OP_AbsValue, 0, 0); + db->safety_level = getSafetyLevel(zRight)+1; + if( db->safety_level==1 ){ + sqliteVdbeAddOp(v, OP_Negative, 0, 0); + size = -size; + } + sqliteVdbeAddOp(v, OP_SetCookie, 0, 2); + sqliteVdbeAddOp(v, OP_Integer, db->safety_level, 0); + sqliteVdbeAddOp(v, OP_SetCookie, 0, 3); + sqliteEndWriteOperation(pParse); + db->cache_size = size; + sqliteBtreeSetCacheSize(db->aDb[0].pBt, db->cache_size); + sqliteBtreeSetSafetyLevel(db->aDb[0].pBt, db->safety_level); + } + }else + + /* + ** PRAGMA synchronous + ** PRAGMA synchronous=OFF|ON|NORMAL|FULL + ** + ** Return or set the local value of the synchronous flag. Changing + ** the local value does not make changes to the disk file and the + ** default value will be restored the next time the database is + ** opened. + */ + if( sqliteStrICmp(zLeft,"synchronous")==0 ){ + static VdbeOpList getSync[] = { + { OP_ColumnName, 0, 1, "synchronous"}, + { OP_Callback, 1, 0, 0}, + }; + if( pRight->z==pLeft->z ){ + sqliteVdbeAddOp(v, OP_Integer, db->safety_level-1, 0); + sqliteVdbeAddOpList(v, ArraySize(getSync), getSync); + }else{ + int size = db->cache_size; + if( size<0 ) size = -size; + db->safety_level = getSafetyLevel(zRight)+1; + if( db->safety_level==1 ) size = -size; + db->cache_size = size; + sqliteBtreeSetCacheSize(db->aDb[0].pBt, db->cache_size); + sqliteBtreeSetSafetyLevel(db->aDb[0].pBt, db->safety_level); + } + }else + +#ifndef NDEBUG + if( sqliteStrICmp(zLeft, "trigger_overhead_test")==0 ){ + if( getBoolean(zRight) ){ + always_code_trigger_setup = 1; + }else{ + always_code_trigger_setup = 0; + } + }else +#endif + + if( flagPragma(pParse, zLeft, zRight) ){ + /* The flagPragma() call also generates any necessary code */ + }else + + if( sqliteStrICmp(zLeft, "table_info")==0 ){ + Table *pTab; + pTab = sqliteFindTable(db, zRight, 0); + if( pTab ){ + static VdbeOpList tableInfoPreface[] = { + { OP_ColumnName, 0, 0, "cid"}, + { OP_ColumnName, 1, 0, "name"}, + { OP_ColumnName, 2, 0, "type"}, + { OP_ColumnName, 3, 0, "notnull"}, + { OP_ColumnName, 4, 0, "dflt_value"}, + { OP_ColumnName, 5, 1, "pk"}, + }; + int i; + sqliteVdbeAddOpList(v, ArraySize(tableInfoPreface), tableInfoPreface); + sqliteViewGetColumnNames(pParse, pTab); + for(i=0; inCol; i++){ + sqliteVdbeAddOp(v, OP_Integer, i, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, pTab->aCol[i].zName, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, + pTab->aCol[i].zType ? pTab->aCol[i].zType : "numeric", 0); + sqliteVdbeAddOp(v, OP_Integer, pTab->aCol[i].notNull, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, + pTab->aCol[i].zDflt, P3_STATIC); + sqliteVdbeAddOp(v, OP_Integer, pTab->aCol[i].isPrimKey, 0); + sqliteVdbeAddOp(v, OP_Callback, 6, 0); + } + } + }else + + if( sqliteStrICmp(zLeft, "index_info")==0 ){ + Index *pIdx; + Table *pTab; + pIdx = sqliteFindIndex(db, zRight, 0); + if( pIdx ){ + static VdbeOpList tableInfoPreface[] = { + { OP_ColumnName, 0, 0, "seqno"}, + { OP_ColumnName, 1, 0, "cid"}, + { OP_ColumnName, 2, 1, "name"}, + }; + int i; + pTab = pIdx->pTable; + sqliteVdbeAddOpList(v, ArraySize(tableInfoPreface), tableInfoPreface); + for(i=0; inColumn; i++){ + int cnum = pIdx->aiColumn[i]; + sqliteVdbeAddOp(v, OP_Integer, i, 0); + sqliteVdbeAddOp(v, OP_Integer, cnum, 0); + assert( pTab->nCol>cnum ); + sqliteVdbeOp3(v, OP_String, 0, 0, pTab->aCol[cnum].zName, 0); + sqliteVdbeAddOp(v, OP_Callback, 3, 0); + } + } + }else + + if( sqliteStrICmp(zLeft, "index_list")==0 ){ + Index *pIdx; + Table *pTab; + pTab = sqliteFindTable(db, zRight, 0); + if( pTab ){ + v = sqliteGetVdbe(pParse); + pIdx = pTab->pIndex; + } + if( pTab && pIdx ){ + int i = 0; + static VdbeOpList indexListPreface[] = { + { OP_ColumnName, 0, 0, "seq"}, + { OP_ColumnName, 1, 0, "name"}, + { OP_ColumnName, 2, 1, "unique"}, + }; + + sqliteVdbeAddOpList(v, ArraySize(indexListPreface), indexListPreface); + while(pIdx){ + sqliteVdbeAddOp(v, OP_Integer, i, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, pIdx->zName, 0); + sqliteVdbeAddOp(v, OP_Integer, pIdx->onError!=OE_None, 0); + sqliteVdbeAddOp(v, OP_Callback, 3, 0); + ++i; + pIdx = pIdx->pNext; + } + } + }else + + if( sqliteStrICmp(zLeft, "foreign_key_list")==0 ){ + FKey *pFK; + Table *pTab; + pTab = sqliteFindTable(db, zRight, 0); + if( pTab ){ + v = sqliteGetVdbe(pParse); + pFK = pTab->pFKey; + } + if( pTab && pFK ){ + int i = 0; + static VdbeOpList indexListPreface[] = { + { OP_ColumnName, 0, 0, "id"}, + { OP_ColumnName, 1, 0, "seq"}, + { OP_ColumnName, 2, 0, "table"}, + { OP_ColumnName, 3, 0, "from"}, + { OP_ColumnName, 4, 1, "to"}, + }; + + sqliteVdbeAddOpList(v, ArraySize(indexListPreface), indexListPreface); + while(pFK){ + int j; + for(j=0; jnCol; j++){ + sqliteVdbeAddOp(v, OP_Integer, i, 0); + sqliteVdbeAddOp(v, OP_Integer, j, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, pFK->zTo, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, + pTab->aCol[pFK->aCol[j].iFrom].zName, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, pFK->aCol[j].zCol, 0); + sqliteVdbeAddOp(v, OP_Callback, 5, 0); + } + ++i; + pFK = pFK->pNextFrom; + } + } + }else + + if( sqliteStrICmp(zLeft, "database_list")==0 ){ + int i; + static VdbeOpList indexListPreface[] = { + { OP_ColumnName, 0, 0, "seq"}, + { OP_ColumnName, 1, 0, "name"}, + { OP_ColumnName, 2, 1, "file"}, + }; + + sqliteVdbeAddOpList(v, ArraySize(indexListPreface), indexListPreface); + for(i=0; inDb; i++){ + if( db->aDb[i].pBt==0 ) continue; + assert( db->aDb[i].zName!=0 ); + sqliteVdbeAddOp(v, OP_Integer, i, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, db->aDb[i].zName, 0); + sqliteVdbeOp3(v, OP_String, 0, 0, + sqliteBtreeGetFilename(db->aDb[i].pBt), 0); + sqliteVdbeAddOp(v, OP_Callback, 3, 0); + } + }else + + + /* + ** PRAGMA temp_store + ** PRAGMA temp_store = "default"|"memory"|"file" + ** + ** Return or set the local value of the temp_store flag. Changing + ** the local value does not make changes to the disk file and the default + ** value will be restored the next time the database is opened. + ** + ** Note that it is possible for the library compile-time options to + ** override this setting + */ + if( sqliteStrICmp(zLeft, "temp_store")==0 ){ + static VdbeOpList getTmpDbLoc[] = { + { OP_ColumnName, 0, 1, "temp_store"}, + { OP_Callback, 1, 0, 0}, + }; + if( pRight->z==pLeft->z ){ + sqliteVdbeAddOp(v, OP_Integer, db->temp_store, 0); + sqliteVdbeAddOpList(v, ArraySize(getTmpDbLoc), getTmpDbLoc); + }else{ + changeTempStorage(pParse, zRight); + } + }else + + /* + ** PRAGMA default_temp_store + ** PRAGMA default_temp_store = "default"|"memory"|"file" + ** + ** Return or set the value of the persistent temp_store flag. Any + ** change does not take effect until the next time the database is + ** opened. + ** + ** Note that it is possible for the library compile-time options to + ** override this setting + */ + if( sqliteStrICmp(zLeft, "default_temp_store")==0 ){ + static VdbeOpList getTmpDbLoc[] = { + { OP_ColumnName, 0, 1, "temp_store"}, + { OP_ReadCookie, 0, 5, 0}, + { OP_Callback, 1, 0, 0}}; + if( pRight->z==pLeft->z ){ + sqliteVdbeAddOpList(v, ArraySize(getTmpDbLoc), getTmpDbLoc); + }else{ + sqliteBeginWriteOperation(pParse, 0, 0); + sqliteVdbeAddOp(v, OP_Integer, getTempStore(zRight), 0); + sqliteVdbeAddOp(v, OP_SetCookie, 0, 5); + sqliteEndWriteOperation(pParse); + } + }else + +#ifndef NDEBUG + if( sqliteStrICmp(zLeft, "parser_trace")==0 ){ + extern void sqliteParserTrace(FILE*, char *); + if( getBoolean(zRight) ){ + sqliteParserTrace(stdout, "parser: "); + }else{ + sqliteParserTrace(0, 0); + } + }else +#endif + + if( sqliteStrICmp(zLeft, "integrity_check")==0 ){ + int i, j, addr; + + /* Code that initializes the integrity check program. Set the + ** error count 0 + */ + static VdbeOpList initCode[] = { + { OP_Integer, 0, 0, 0}, + { OP_MemStore, 0, 1, 0}, + { OP_ColumnName, 0, 1, "integrity_check"}, + }; + + /* Code to do an BTree integrity check on a single database file. + */ + static VdbeOpList checkDb[] = { + { OP_SetInsert, 0, 0, "2"}, + { OP_Integer, 0, 0, 0}, /* 1 */ + { OP_OpenRead, 0, 2, 0}, + { OP_Rewind, 0, 7, 0}, /* 3 */ + { OP_Column, 0, 3, 0}, /* 4 */ + { OP_SetInsert, 0, 0, 0}, + { OP_Next, 0, 4, 0}, /* 6 */ + { OP_IntegrityCk, 0, 0, 0}, /* 7 */ + { OP_Dup, 0, 1, 0}, + { OP_String, 0, 0, "ok"}, + { OP_StrEq, 0, 12, 0}, /* 10 */ + { OP_MemIncr, 0, 0, 0}, + { OP_String, 0, 0, "*** in database "}, + { OP_String, 0, 0, 0}, /* 13 */ + { OP_String, 0, 0, " ***\n"}, + { OP_Pull, 3, 0, 0}, + { OP_Concat, 4, 1, 0}, + { OP_Callback, 1, 0, 0}, + }; + + /* Code that appears at the end of the integrity check. If no error + ** messages have been generated, output OK. Otherwise output the + ** error message + */ + static VdbeOpList endCode[] = { + { OP_MemLoad, 0, 0, 0}, + { OP_Integer, 0, 0, 0}, + { OP_Ne, 0, 0, 0}, /* 2 */ + { OP_String, 0, 0, "ok"}, + { OP_Callback, 1, 0, 0}, + }; + + /* Initialize the VDBE program */ + sqliteVdbeAddOpList(v, ArraySize(initCode), initCode); + + /* Do an integrity check on each database file */ + for(i=0; inDb; i++){ + HashElem *x; + + /* Do an integrity check of the B-Tree + */ + addr = sqliteVdbeAddOpList(v, ArraySize(checkDb), checkDb); + sqliteVdbeChangeP1(v, addr+1, i); + sqliteVdbeChangeP2(v, addr+3, addr+7); + sqliteVdbeChangeP2(v, addr+6, addr+4); + sqliteVdbeChangeP2(v, addr+7, i); + sqliteVdbeChangeP2(v, addr+10, addr+ArraySize(checkDb)); + sqliteVdbeChangeP3(v, addr+13, db->aDb[i].zName, P3_STATIC); + + /* Make sure all the indices are constructed correctly. + */ + sqliteCodeVerifySchema(pParse, i); + for(x=sqliteHashFirst(&db->aDb[i].tblHash); x; x=sqliteHashNext(x)){ + Table *pTab = sqliteHashData(x); + Index *pIdx; + int loopTop; + + if( pTab->pIndex==0 ) continue; + sqliteVdbeAddOp(v, OP_Integer, i, 0); + sqliteVdbeOp3(v, OP_OpenRead, 1, pTab->tnum, pTab->zName, 0); + for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + if( pIdx->tnum==0 ) continue; + sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0); + sqliteVdbeOp3(v, OP_OpenRead, j+2, pIdx->tnum, pIdx->zName, 0); + } + sqliteVdbeAddOp(v, OP_Integer, 0, 0); + sqliteVdbeAddOp(v, OP_MemStore, 1, 1); + loopTop = sqliteVdbeAddOp(v, OP_Rewind, 1, 0); + sqliteVdbeAddOp(v, OP_MemIncr, 1, 0); + for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + int k, jmp2; + static VdbeOpList idxErr[] = { + { OP_MemIncr, 0, 0, 0}, + { OP_String, 0, 0, "rowid "}, + { OP_Recno, 1, 0, 0}, + { OP_String, 0, 0, " missing from index "}, + { OP_String, 0, 0, 0}, /* 4 */ + { OP_Concat, 4, 0, 0}, + { OP_Callback, 1, 0, 0}, + }; + sqliteVdbeAddOp(v, OP_Recno, 1, 0); + for(k=0; knColumn; k++){ + int idx = pIdx->aiColumn[k]; + if( idx==pTab->iPKey ){ + sqliteVdbeAddOp(v, OP_Recno, 1, 0); + }else{ + sqliteVdbeAddOp(v, OP_Column, 1, idx); + } + } + sqliteVdbeAddOp(v, OP_MakeIdxKey, pIdx->nColumn, 0); + if( db->file_format>=4 ) sqliteAddIdxKeyType(v, pIdx); + jmp2 = sqliteVdbeAddOp(v, OP_Found, j+2, 0); + addr = sqliteVdbeAddOpList(v, ArraySize(idxErr), idxErr); + sqliteVdbeChangeP3(v, addr+4, pIdx->zName, P3_STATIC); + sqliteVdbeChangeP2(v, jmp2, sqliteVdbeCurrentAddr(v)); + } + sqliteVdbeAddOp(v, OP_Next, 1, loopTop+1); + sqliteVdbeChangeP2(v, loopTop, sqliteVdbeCurrentAddr(v)); + for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + static VdbeOpList cntIdx[] = { + { OP_Integer, 0, 0, 0}, + { OP_MemStore, 2, 1, 0}, + { OP_Rewind, 0, 0, 0}, /* 2 */ + { OP_MemIncr, 2, 0, 0}, + { OP_Next, 0, 0, 0}, /* 4 */ + { OP_MemLoad, 1, 0, 0}, + { OP_MemLoad, 2, 0, 0}, + { OP_Eq, 0, 0, 0}, /* 7 */ + { OP_MemIncr, 0, 0, 0}, + { OP_String, 0, 0, "wrong # of entries in index "}, + { OP_String, 0, 0, 0}, /* 10 */ + { OP_Concat, 2, 0, 0}, + { OP_Callback, 1, 0, 0}, + }; + if( pIdx->tnum==0 ) continue; + addr = sqliteVdbeAddOpList(v, ArraySize(cntIdx), cntIdx); + sqliteVdbeChangeP1(v, addr+2, j+2); + sqliteVdbeChangeP2(v, addr+2, addr+5); + sqliteVdbeChangeP1(v, addr+4, j+2); + sqliteVdbeChangeP2(v, addr+4, addr+3); + sqliteVdbeChangeP2(v, addr+7, addr+ArraySize(cntIdx)); + sqliteVdbeChangeP3(v, addr+10, pIdx->zName, P3_STATIC); + } + } + } + addr = sqliteVdbeAddOpList(v, ArraySize(endCode), endCode); + sqliteVdbeChangeP2(v, addr+2, addr+ArraySize(endCode)); + }else + + {} + sqliteFree(zLeft); + sqliteFree(zRight); +} diff --git a/src/libs/sqlite2/printf.c b/src/libs/sqlite2/printf.c new file mode 100644 index 00000000..a5445f60 --- /dev/null +++ b/src/libs/sqlite2/printf.c @@ -0,0 +1,858 @@ +/* +** The "printf" code that follows dates from the 1980's. It is in +** the public domain. The original comments are included here for +** completeness. They are very out-of-date but might be useful as +** an historical reference. Most of the "enhancements" have been backed +** out so that the functionality is now the same as standard printf(). +** +************************************************************************** +** +** The following modules is an enhanced replacement for the "printf" subroutines +** found in the standard C library. The following enhancements are +** supported: +** +** + Additional functions. The standard set of "printf" functions +** includes printf, fprintf, sprintf, vprintf, vfprintf, and +** vsprintf. This module adds the following: +** +** * snprintf -- Works like sprintf, but has an extra argument +** which is the size of the buffer written to. +** +** * mprintf -- Similar to sprintf. Writes output to memory +** obtained from malloc. +** +** * xprintf -- Calls a function to dispose of output. +** +** * nprintf -- No output, but returns the number of characters +** that would have been output by printf. +** +** * A v- version (ex: vsnprintf) of every function is also +** supplied. +** +** + A few extensions to the formatting notation are supported: +** +** * The "=" flag (similar to "-") causes the output to be +** be centered in the appropriately sized field. +** +** * The %b field outputs an integer in binary notation. +** +** * The %c field now accepts a precision. The character output +** is repeated by the number of times the precision specifies. +** +** * The %' field works like %c, but takes as its character the +** next character of the format string, instead of the next +** argument. For example, printf("%.78'-") prints 78 minus +** signs, the same as printf("%.78c",'-'). +** +** + When compiled using GCC on a SPARC, this version of printf is +** faster than the library printf for SUN OS 4.1. +** +** + All functions are fully reentrant. +** +*/ +#include "sqliteInt.h" + +/* +** Conversion types fall into various categories as defined by the +** following enumeration. +*/ +#define etRADIX 1 /* Integer types. %d, %x, %o, and so forth */ +#define etFLOAT 2 /* Floating point. %f */ +#define etEXP 3 /* Exponentional notation. %e and %E */ +#define etGENERIC 4 /* Floating or exponential, depending on exponent. %g */ +#define etSIZE 5 /* Return number of characters processed so far. %n */ +#define etSTRING 6 /* Strings. %s */ +#define etDYNSTRING 7 /* Dynamically allocated strings. %z */ +#define etPERCENT 8 /* Percent symbol. %% */ +#define etCHARX 9 /* Characters. %c */ +#define etERROR 10 /* Used to indicate no such conversion type */ +/* The rest are extensions, not normally found in printf() */ +#define etCHARLIT 11 /* Literal characters. %' */ +#define etSQLESCAPE 12 /* Strings with '\'' doubled. %q */ +#define etSQLESCAPE2 13 /* Strings with '\'' doubled and enclosed in '', + NULL pointers replaced by SQL NULL. %Q */ +#define etTOKEN 14 /* a pointer to a Token structure */ +#define etSRCLIST 15 /* a pointer to a SrcList */ + + +/* +** An "etByte" is an 8-bit unsigned value. +*/ +typedef unsigned char etByte; + +/* +** Each builtin conversion character (ex: the 'd' in "%d") is described +** by an instance of the following structure +*/ +typedef struct et_info { /* Information about each format field */ + char fmttype; /* The format field code letter */ + etByte base; /* The base for radix conversion */ + etByte flags; /* One or more of FLAG_ constants below */ + etByte type; /* Conversion paradigm */ + char *charset; /* The character set for conversion */ + char *prefix; /* Prefix on non-zero values in alt format */ +} et_info; + +/* +** Allowed values for et_info.flags +*/ +#define FLAG_SIGNED 1 /* True if the value to convert is signed */ +#define FLAG_INTERN 2 /* True if for internal use only */ + + +/* +** The following table is searched linearly, so it is good to put the +** most frequently used conversion types first. +*/ +static et_info fmtinfo[] = { + { 'd', 10, 1, etRADIX, "0123456789", 0 }, + { 's', 0, 0, etSTRING, 0, 0 }, + { 'z', 0, 2, etDYNSTRING, 0, 0 }, + { 'q', 0, 0, etSQLESCAPE, 0, 0 }, + { 'Q', 0, 0, etSQLESCAPE2, 0, 0 }, + { 'c', 0, 0, etCHARX, 0, 0 }, + { 'o', 8, 0, etRADIX, "01234567", "0" }, + { 'u', 10, 0, etRADIX, "0123456789", 0 }, + { 'x', 16, 0, etRADIX, "0123456789abcdef", "x0" }, + { 'X', 16, 0, etRADIX, "0123456789ABCDEF", "X0" }, + { 'f', 0, 1, etFLOAT, 0, 0 }, + { 'e', 0, 1, etEXP, "e", 0 }, + { 'E', 0, 1, etEXP, "E", 0 }, + { 'g', 0, 1, etGENERIC, "e", 0 }, + { 'G', 0, 1, etGENERIC, "E", 0 }, + { 'i', 10, 1, etRADIX, "0123456789", 0 }, + { 'n', 0, 0, etSIZE, 0, 0 }, + { '%', 0, 0, etPERCENT, 0, 0 }, + { 'p', 10, 0, etRADIX, "0123456789", 0 }, + { 'T', 0, 2, etTOKEN, 0, 0 }, + { 'S', 0, 2, etSRCLIST, 0, 0 }, +}; +#define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0])) + +/* +** If NOFLOATINGPOINT is defined, then none of the floating point +** conversions will work. +*/ +#ifndef etNOFLOATINGPOINT +/* +** "*val" is a double such that 0.1 <= *val < 10.0 +** Return the ascii code for the leading digit of *val, then +** multiply "*val" by 10.0 to renormalize. +** +** Example: +** input: *val = 3.14159 +** output: *val = 1.4159 function return = '3' +** +** The counter *cnt is incremented each time. After counter exceeds +** 16 (the number of significant digits in a 64-bit float) '0' is +** always returned. +*/ +static int et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ + int digit; + LONGDOUBLE_TYPE d; + if( (*cnt)++ >= 16 ) return '0'; + digit = (int)*val; + d = digit; + digit += '0'; + *val = (*val - d)*10.0; + return digit; +} +#endif + +#define etBUFSIZE 1000 /* Size of the output buffer */ + +/* +** The root program. All variations call this core. +** +** INPUTS: +** func This is a pointer to a function taking three arguments +** 1. A pointer to anything. Same as the "arg" parameter. +** 2. A pointer to the list of characters to be output +** (Note, this list is NOT null terminated.) +** 3. An integer number of characters to be output. +** (Note: This number might be zero.) +** +** arg This is the pointer to anything which will be passed as the +** first argument to "func". Use it for whatever you like. +** +** fmt This is the format string, as in the usual print. +** +** ap This is a pointer to a list of arguments. Same as in +** vfprint. +** +** OUTPUTS: +** The return value is the total number of characters sent to +** the function "func". Returns -1 on a error. +** +** Note that the order in which automatic variables are declared below +** seems to make a big difference in determining how fast this beast +** will run. +*/ +static int vxprintf( + void (*func)(void*,const char*,int), /* Consumer of text */ + void *arg, /* First argument to the consumer */ + int useExtended, /* Allow extended %-conversions */ + const char *fmt, /* Format string */ + va_list ap /* arguments */ +){ + int c; /* Next character in the format string */ + char *bufpt; /* Pointer to the conversion buffer */ + int precision; /* Precision of the current field */ + int length; /* Length of the field */ + int idx; /* A general purpose loop counter */ + int count; /* Total number of characters output */ + int width; /* Width of the current field */ + etByte flag_leftjustify; /* True if "-" flag is present */ + etByte flag_plussign; /* True if "+" flag is present */ + etByte flag_blanksign; /* True if " " flag is present */ + etByte flag_alternateform; /* True if "#" flag is present */ + etByte flag_zeropad; /* True if field width constant starts with zero */ + etByte flag_long; /* True if "l" flag is present */ + unsigned long longvalue; /* Value for integer types */ + LONGDOUBLE_TYPE realvalue; /* Value for real types */ + et_info *infop; /* Pointer to the appropriate info structure */ + char buf[etBUFSIZE]; /* Conversion buffer */ + char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ + etByte errorflag = 0; /* True if an error is encountered */ + etByte xtype; /* Conversion paradigm */ + char *zExtra; /* Extra memory used for etTCLESCAPE conversions */ + static char spaces[] = " "; +#define etSPACESIZE (sizeof(spaces)-1) +#ifndef etNOFLOATINGPOINT + int exp; /* exponent of real numbers */ + double rounder; /* Used for rounding floating point values */ + etByte flag_dp; /* True if decimal point should be shown */ + etByte flag_rtz; /* True if trailing zeros should be removed */ + etByte flag_exp; /* True to force display of the exponent */ + int nsd; /* Number of significant digits returned */ +#endif + + func(arg,"",0); + count = length = 0; + bufpt = 0; + for(; (c=(*fmt))!=0; ++fmt){ + if( c!='%' ){ + int amt; + bufpt = (char *)fmt; + amt = 1; + while( (c=(*++fmt))!='%' && c!=0 ) amt++; + (*func)(arg,bufpt,amt); + count += amt; + if( c==0 ) break; + } + if( (c=(*++fmt))==0 ){ + errorflag = 1; + (*func)(arg,"%",1); + count++; + break; + } + /* Find out what flags are present */ + flag_leftjustify = flag_plussign = flag_blanksign = + flag_alternateform = flag_zeropad = 0; + do{ + switch( c ){ + case '-': flag_leftjustify = 1; c = 0; break; + case '+': flag_plussign = 1; c = 0; break; + case ' ': flag_blanksign = 1; c = 0; break; + case '#': flag_alternateform = 1; c = 0; break; + case '0': flag_zeropad = 1; c = 0; break; + default: break; + } + }while( c==0 && (c=(*++fmt))!=0 ); + /* Get the field width */ + width = 0; + if( c=='*' ){ + width = va_arg(ap,int); + if( width<0 ){ + flag_leftjustify = 1; + width = -width; + } + c = *++fmt; + }else{ + while( c>='0' && c<='9' ){ + width = width*10 + c - '0'; + c = *++fmt; + } + } + if( width > etBUFSIZE-10 ){ + width = etBUFSIZE-10; + } + /* Get the precision */ + if( c=='.' ){ + precision = 0; + c = *++fmt; + if( c=='*' ){ + precision = va_arg(ap,int); + if( precision<0 ) precision = -precision; + c = *++fmt; + }else{ + while( c>='0' && c<='9' ){ + precision = precision*10 + c - '0'; + c = *++fmt; + } + } + /* Limit the precision to prevent overflowing buf[] during conversion */ + if( precision>etBUFSIZE-40 ) precision = etBUFSIZE-40; + }else{ + precision = -1; + } + /* Get the conversion type modifier */ + if( c=='l' ){ + flag_long = 1; + c = *++fmt; + }else{ + flag_long = 0; + } + /* Fetch the info entry for the field */ + infop = 0; + xtype = etERROR; + for(idx=0; idxflags & FLAG_INTERN)==0 ){ + xtype = infop->type; + } + break; + } + } + zExtra = 0; + + /* + ** At this point, variables are initialized as follows: + ** + ** flag_alternateform TRUE if a '#' is present. + ** flag_plussign TRUE if a '+' is present. + ** flag_leftjustify TRUE if a '-' is present or if the + ** field width was negative. + ** flag_zeropad TRUE if the width began with 0. + ** flag_long TRUE if the letter 'l' (ell) prefixed + ** the conversion character. + ** flag_blanksign TRUE if a ' ' is present. + ** width The specified field width. This is + ** always non-negative. Zero is the default. + ** precision The specified precision. The default + ** is -1. + ** xtype The class of the conversion. + ** infop Pointer to the appropriate info struct. + */ + switch( xtype ){ + case etRADIX: + if( flag_long ) longvalue = va_arg(ap,long); + else longvalue = va_arg(ap,int); +#if 1 + /* For the format %#x, the value zero is printed "0" not "0x0". + ** I think this is stupid. */ + if( longvalue==0 ) flag_alternateform = 0; +#else + /* More sensible: turn off the prefix for octal (to prevent "00"), + ** but leave the prefix for hex. */ + if( longvalue==0 && infop->base==8 ) flag_alternateform = 0; +#endif + if( infop->flags & FLAG_SIGNED ){ + if( *(long*)&longvalue<0 ){ + longvalue = -*(long*)&longvalue; + prefix = '-'; + }else if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + }else prefix = 0; + if( flag_zeropad && precisioncharset; + base = infop->base; + do{ /* Convert to ascii */ + *(--bufpt) = cset[longvalue%base]; + longvalue = longvalue/base; + }while( longvalue>0 ); + } + length = &buf[etBUFSIZE-1]-bufpt; + for(idx=precision-length; idx>0; idx--){ + *(--bufpt) = '0'; /* Zero pad */ + } + if( prefix ) *(--bufpt) = prefix; /* Add sign */ + if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ + char *pre, x; + pre = infop->prefix; + if( *bufpt!=pre[0] ){ + for(pre=infop->prefix; (x=(*pre))!=0; pre++) *(--bufpt) = x; + } + } + length = &buf[etBUFSIZE-1]-bufpt; + break; + case etFLOAT: + case etEXP: + case etGENERIC: + realvalue = va_arg(ap,double); +#ifndef etNOFLOATINGPOINT + if( precision<0 ) precision = 6; /* Set default precision */ + if( precision>etBUFSIZE-10 ) precision = etBUFSIZE-10; + if( realvalue<0.0 ){ + realvalue = -realvalue; + prefix = '-'; + }else{ + if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + } + if( infop->type==etGENERIC && precision>0 ) precision--; + rounder = 0.0; +#if 0 + /* Rounding works like BSD when the constant 0.4999 is used. Wierd! */ + for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); +#else + /* It makes more sense to use 0.5 */ + for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1); +#endif + if( infop->type==etFLOAT ) realvalue += rounder; + /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ + exp = 0; + if( realvalue>0.0 ){ + while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } + while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } + while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } + while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } + if( exp>350 || exp<-350 ){ + bufpt = "NaN"; + length = 3; + break; + } + } + bufpt = buf; + /* + ** If the field type is etGENERIC, then convert to either etEXP + ** or etFLOAT, as appropriate. + */ + flag_exp = xtype==etEXP; + if( xtype!=etFLOAT ){ + realvalue += rounder; + if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } + } + if( xtype==etGENERIC ){ + flag_rtz = !flag_alternateform; + if( exp<-4 || exp>precision ){ + xtype = etEXP; + }else{ + precision = precision - exp; + xtype = etFLOAT; + } + }else{ + flag_rtz = 0; + } + /* + ** The "exp+precision" test causes output to be of type etEXP if + ** the precision is too large to fit in buf[]. + */ + nsd = 0; + if( xtype==etFLOAT && exp+precision0 || flag_alternateform); + if( prefix ) *(bufpt++) = prefix; /* Sign */ + if( exp<0 ) *(bufpt++) = '0'; /* Digits before "." */ + else for(; exp>=0; exp--) *(bufpt++) = et_getdigit(&realvalue,&nsd); + if( flag_dp ) *(bufpt++) = '.'; /* The decimal point */ + for(exp++; exp<0 && precision>0; precision--, exp++){ + *(bufpt++) = '0'; + } + while( (precision--)>0 ) *(bufpt++) = et_getdigit(&realvalue,&nsd); + *(bufpt--) = 0; /* Null terminate */ + if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */ + while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; + if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; + } + bufpt++; /* point to next free slot */ + }else{ /* etEXP or etGENERIC */ + flag_dp = (precision>0 || flag_alternateform); + if( prefix ) *(bufpt++) = prefix; /* Sign */ + *(bufpt++) = et_getdigit(&realvalue,&nsd); /* First digit */ + if( flag_dp ) *(bufpt++) = '.'; /* Decimal point */ + while( (precision--)>0 ) *(bufpt++) = et_getdigit(&realvalue,&nsd); + bufpt--; /* point to last digit */ + if( flag_rtz && flag_dp ){ /* Remove tail zeros */ + while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; + if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; + } + bufpt++; /* point to next free slot */ + if( exp || flag_exp ){ + *(bufpt++) = infop->charset[0]; + if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; } /* sign of exp */ + else { *(bufpt++) = '+'; } + if( exp>=100 ){ + *(bufpt++) = (exp/100)+'0'; /* 100's digit */ + exp %= 100; + } + *(bufpt++) = exp/10+'0'; /* 10's digit */ + *(bufpt++) = exp%10+'0'; /* 1's digit */ + } + } + /* The converted number is in buf[] and zero terminated. Output it. + ** Note that the number is in the usual order, not reversed as with + ** integer conversions. */ + length = bufpt-buf; + bufpt = buf; + + /* Special case: Add leading zeros if the flag_zeropad flag is + ** set and we are not left justified */ + if( flag_zeropad && !flag_leftjustify && length < width){ + int i; + int nPad = width - length; + for(i=width; i>=nPad; i--){ + bufpt[i] = bufpt[i-nPad]; + } + i = prefix!=0; + while( nPad-- ) bufpt[i++] = '0'; + length = width; + } +#endif + break; + case etSIZE: + *(va_arg(ap,int*)) = count; + length = width = 0; + break; + case etPERCENT: + buf[0] = '%'; + bufpt = buf; + length = 1; + break; + case etCHARLIT: + case etCHARX: + c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt); + if( precision>=0 ){ + for(idx=1; idx=0 && precisionetBUFSIZE ){ + bufpt = zExtra = sqliteMalloc( n ); + if( bufpt==0 ) return -1; + }else{ + bufpt = buf; + } + j = 0; + if( !isnull && xtype==etSQLESCAPE2 ) bufpt[j++] = '\''; + for(i=0; (c=arg[i])!=0; i++){ + bufpt[j++] = c; + if( c=='\'' ) bufpt[j++] = c; + } + if( !isnull && xtype==etSQLESCAPE2 ) bufpt[j++] = '\''; + bufpt[j] = 0; + length = j; + if( precision>=0 && precisionz, pToken->n); + length = width = 0; + break; + } + case etSRCLIST: { + SrcList *pSrc = va_arg(ap, SrcList*); + int k = va_arg(ap, int); + struct SrcList_item *pItem = &pSrc->a[k]; + assert( k>=0 && knSrc ); + if( pItem->zDatabase && pItem->zDatabase[0] ){ + (*func)(arg, pItem->zDatabase, strlen(pItem->zDatabase)); + (*func)(arg, ".", 1); + } + (*func)(arg, pItem->zName, strlen(pItem->zName)); + length = width = 0; + break; + } + case etERROR: + buf[0] = '%'; + buf[1] = c; + errorflag = 0; + idx = 1+(c!=0); + (*func)(arg,"%",idx); + count += idx; + if( c==0 ) fmt--; + break; + }/* End switch over the format type */ + /* + ** The text of the conversion is pointed to by "bufpt" and is + ** "length" characters long. The field width is "width". Do + ** the output. + */ + if( !flag_leftjustify ){ + int nspace; + nspace = width-length; + if( nspace>0 ){ + count += nspace; + while( nspace>=etSPACESIZE ){ + (*func)(arg,spaces,etSPACESIZE); + nspace -= etSPACESIZE; + } + if( nspace>0 ) (*func)(arg,spaces,nspace); + } + } + if( length>0 ){ + (*func)(arg,bufpt,length); + count += length; + } + if( flag_leftjustify ){ + int nspace; + nspace = width-length; + if( nspace>0 ){ + count += nspace; + while( nspace>=etSPACESIZE ){ + (*func)(arg,spaces,etSPACESIZE); + nspace -= etSPACESIZE; + } + if( nspace>0 ) (*func)(arg,spaces,nspace); + } + } + if( zExtra ){ + sqliteFree(zExtra); + } + }/* End for loop over the format string */ + return errorflag ? -1 : count; +} /* End of function */ + + +/* This structure is used to store state information about the +** write to memory that is currently in progress. +*/ +struct sgMprintf { + char *zBase; /* A base allocation */ + char *zText; /* The string collected so far */ + int nChar; /* Length of the string so far */ + int nTotal; /* Output size if unconstrained */ + int nAlloc; /* Amount of space allocated in zText */ + void *(*xRealloc)(void*,int); /* Function used to realloc memory */ +}; + +/* +** This function implements the callback from vxprintf. +** +** This routine add nNewChar characters of text in zNewText to +** the sgMprintf structure pointed to by "arg". +*/ +static void mout(void *arg, const char *zNewText, int nNewChar){ + struct sgMprintf *pM = (struct sgMprintf*)arg; + pM->nTotal += nNewChar; + if( pM->nChar + nNewChar + 1 > pM->nAlloc ){ + if( pM->xRealloc==0 ){ + nNewChar = pM->nAlloc - pM->nChar - 1; + }else{ + pM->nAlloc = pM->nChar + nNewChar*2 + 1; + if( pM->zText==pM->zBase ){ + pM->zText = pM->xRealloc(0, pM->nAlloc); + if( pM->zText && pM->nChar ){ + memcpy(pM->zText, pM->zBase, pM->nChar); + } + }else{ + pM->zText = pM->xRealloc(pM->zText, pM->nAlloc); + } + } + } + if( pM->zText ){ + if( nNewChar>0 ){ + memcpy(&pM->zText[pM->nChar], zNewText, nNewChar); + pM->nChar += nNewChar; + } + pM->zText[pM->nChar] = 0; + } +} + +/* +** This routine is a wrapper around xprintf() that invokes mout() as +** the consumer. +*/ +static char *base_vprintf( + void *(*xRealloc)(void*,int), /* Routine to realloc memory. May be NULL */ + int useInternal, /* Use internal %-conversions if true */ + char *zInitBuf, /* Initially write here, before mallocing */ + int nInitBuf, /* Size of zInitBuf[] */ + const char *zFormat, /* format string */ + va_list ap /* arguments */ +){ + struct sgMprintf sM; + sM.zBase = sM.zText = zInitBuf; + sM.nChar = sM.nTotal = 0; + sM.nAlloc = nInitBuf; + sM.xRealloc = xRealloc; + vxprintf(mout, &sM, useInternal, zFormat, ap); + if( xRealloc ){ + if( sM.zText==sM.zBase ){ + sM.zText = xRealloc(0, sM.nChar+1); + memcpy(sM.zText, sM.zBase, sM.nChar+1); + }else if( sM.nAlloc>sM.nChar+10 ){ + sM.zText = xRealloc(sM.zText, sM.nChar+1); + } + } + return sM.zText; +} + +/* +** Realloc that is a real function, not a macro. +*/ +static void *printf_realloc(void *old, int size){ + return sqliteRealloc(old,size); +} + +/* +** Print into memory obtained from sqliteMalloc(). Use the internal +** %-conversion extensions. +*/ +char *sqliteVMPrintf(const char *zFormat, va_list ap){ + char zBase[1000]; + return base_vprintf(printf_realloc, 1, zBase, sizeof(zBase), zFormat, ap); +} + +/* +** Print into memory obtained from sqliteMalloc(). Use the internal +** %-conversion extensions. +*/ +char *sqliteMPrintf(const char *zFormat, ...){ + va_list ap; + char *z; + char zBase[1000]; + va_start(ap, zFormat); + z = base_vprintf(printf_realloc, 1, zBase, sizeof(zBase), zFormat, ap); + va_end(ap); + return z; +} + +/* +** Print into memory obtained from malloc(). Do not use the internal +** %-conversion extensions. This routine is for use by external users. +*/ +char *sqlite_mprintf(const char *zFormat, ...){ + va_list ap; + char *z; + char zBuf[200]; + + va_start(ap,zFormat); + z = base_vprintf((void*(*)(void*,int))realloc, 0, + zBuf, sizeof(zBuf), zFormat, ap); + va_end(ap); + return z; +} + +/* This is the varargs version of sqlite_mprintf. +*/ +char *sqlite_vmprintf(const char *zFormat, va_list ap){ + char zBuf[200]; + return base_vprintf((void*(*)(void*,int))realloc, 0, + zBuf, sizeof(zBuf), zFormat, ap); +} + +/* +** sqlite_snprintf() works like snprintf() except that it ignores the +** current locale settings. This is important for SQLite because we +** are not able to use a "," as the decimal point in place of "." as +** specified by some locales. +*/ +char *sqlite_snprintf(int n, char *zBuf, const char *zFormat, ...){ + char *z; + va_list ap; + + va_start(ap,zFormat); + z = base_vprintf(0, 0, zBuf, n, zFormat, ap); + va_end(ap); + return z; +} + +/* +** The following four routines implement the varargs versions of the +** sqlite_exec() and sqlite_get_table() interfaces. See the sqlite.h +** header files for a more detailed description of how these interfaces +** work. +** +** These routines are all just simple wrappers. +*/ +int sqlite_exec_printf( + sqlite *db, /* An open database */ + const char *sqlFormat, /* printf-style format string for the SQL */ + sqlite_callback xCallback, /* Callback function */ + void *pArg, /* 1st argument to callback function */ + char **errmsg, /* Error msg written here */ + ... /* Arguments to the format string. */ +){ + va_list ap; + int rc; + + va_start(ap, errmsg); + rc = sqlite_exec_vprintf(db, sqlFormat, xCallback, pArg, errmsg, ap); + va_end(ap); + return rc; +} +int sqlite_exec_vprintf( + sqlite *db, /* An open database */ + const char *sqlFormat, /* printf-style format string for the SQL */ + sqlite_callback xCallback, /* Callback function */ + void *pArg, /* 1st argument to callback function */ + char **errmsg, /* Error msg written here */ + va_list ap /* Arguments to the format string. */ +){ + char *zSql; + int rc; + + zSql = sqlite_vmprintf(sqlFormat, ap); + rc = sqlite_exec(db, zSql, xCallback, pArg, errmsg); + free(zSql); + return rc; +} +int sqlite_get_table_printf( + sqlite *db, /* An open database */ + const char *sqlFormat, /* printf-style format string for the SQL */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncol, /* Number of result columns written here */ + char **errmsg, /* Error msg written here */ + ... /* Arguments to the format string */ +){ + va_list ap; + int rc; + + va_start(ap, errmsg); + rc = sqlite_get_table_vprintf(db, sqlFormat, resultp, nrow, ncol, errmsg, ap); + va_end(ap); + return rc; +} +int sqlite_get_table_vprintf( + sqlite *db, /* An open database */ + const char *sqlFormat, /* printf-style format string for the SQL */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncolumn, /* Number of result columns written here */ + char **errmsg, /* Error msg written here */ + va_list ap /* Arguments to the format string */ +){ + char *zSql; + int rc; + + zSql = sqlite_vmprintf(sqlFormat, ap); + rc = sqlite_get_table(db, zSql, resultp, nrow, ncolumn, errmsg); + free(zSql); + return rc; +} diff --git a/src/libs/sqlite2/random.c b/src/libs/sqlite2/random.c new file mode 100644 index 00000000..0d0a5447 --- /dev/null +++ b/src/libs/sqlite2/random.c @@ -0,0 +1,97 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement a pseudo-random number +** generator (PRNG) for SQLite. +** +** Random numbers are used by some of the database backends in order +** to generate random integer keys for tables or random filenames. +** +** $Id: random.c 326789 2004-07-07 21:25:56Z pahlibar $ +*/ +#include "sqliteInt.h" +#include "os.h" + + +/* +** Get a single 8-bit random value from the RC4 PRNG. The Mutex +** must be held while executing this routine. +** +** Why not just use a library random generator like lrand48() for this? +** Because the OP_NewRecno opcode in the VDBE depends on having a very +** good source of random numbers. The lrand48() library function may +** well be good enough. But maybe not. Or maybe lrand48() has some +** subtle problems on some systems that could cause problems. It is hard +** to know. To minimize the risk of problems due to bad lrand48() +** implementations, SQLite uses this random number generator based +** on RC4, which we know works very well. +*/ +static int randomByte(){ + unsigned char t; + + /* All threads share a single random number generator. + ** This structure is the current state of the generator. + */ + static struct { + unsigned char isInit; /* True if initialized */ + unsigned char i, j; /* State variables */ + unsigned char s[256]; /* State variables */ + } prng; + + /* Initialize the state of the random number generator once, + ** the first time this routine is called. The seed value does + ** not need to contain a lot of randomness since we are not + ** trying to do secure encryption or anything like that... + ** + ** Nothing in this file or anywhere else in SQLite does any kind of + ** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random + ** number generator) not as an encryption device. + */ + if( !prng.isInit ){ + int i; + char k[256]; + prng.j = 0; + prng.i = 0; + sqliteOsRandomSeed(k); + for(i=0; i<256; i++){ + prng.s[i] = i; + } + for(i=0; i<256; i++){ + prng.j += prng.s[i] + k[i]; + t = prng.s[prng.j]; + prng.s[prng.j] = prng.s[i]; + prng.s[i] = t; + } + prng.isInit = 1; + } + + /* Generate and return single random byte + */ + prng.i++; + t = prng.s[prng.i]; + prng.j += t; + prng.s[prng.i] = prng.s[prng.j]; + prng.s[prng.j] = t; + t += prng.s[prng.i]; + return prng.s[t]; +} + +/* +** Return N random bytes. +*/ +void sqliteRandomness(int N, void *pBuf){ + unsigned char *zBuf = pBuf; + sqliteOsEnterMutex(); + while( N-- ){ + *(zBuf++) = randomByte(); + } + sqliteOsLeaveMutex(); +} diff --git a/src/libs/sqlite2/select.c b/src/libs/sqlite2/select.c new file mode 100644 index 00000000..4cf03606 --- /dev/null +++ b/src/libs/sqlite2/select.c @@ -0,0 +1,2434 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains C code routines that are called by the parser +** to handle SELECT statements in SQLite. +** +** $Id: select.c 875429 2008-10-24 12:20:41Z cgilles $ +*/ +#include "sqliteInt.h" + + +/* +** Allocate a new Select structure and return a pointer to that +** structure. +*/ +Select *sqliteSelectNew( + ExprList *pEList, /* which columns to include in the result */ + SrcList *pSrc, /* the FROM clause -- which tables to scan */ + Expr *pWhere, /* the WHERE clause */ + ExprList *pGroupBy, /* the GROUP BY clause */ + Expr *pHaving, /* the HAVING clause */ + ExprList *pOrderBy, /* the ORDER BY clause */ + int isDistinct, /* true if the DISTINCT keyword is present */ + int nLimit, /* LIMIT value. -1 means not used */ + int nOffset /* OFFSET value. 0 means no offset */ +){ + Select *pNew; + pNew = sqliteMalloc( sizeof(*pNew) ); + if( pNew==0 ){ + sqliteExprListDelete(pEList); + sqliteSrcListDelete(pSrc); + sqliteExprDelete(pWhere); + sqliteExprListDelete(pGroupBy); + sqliteExprDelete(pHaving); + sqliteExprListDelete(pOrderBy); + }else{ + if( pEList==0 ){ + pEList = sqliteExprListAppend(0, sqliteExpr(TK_ALL,0,0,0), 0); + } + pNew->pEList = pEList; + pNew->pSrc = pSrc; + pNew->pWhere = pWhere; + pNew->pGroupBy = pGroupBy; + pNew->pHaving = pHaving; + pNew->pOrderBy = pOrderBy; + pNew->isDistinct = isDistinct; + pNew->op = TK_SELECT; + pNew->nLimit = nLimit; + pNew->nOffset = nOffset; + pNew->iLimit = -1; + pNew->iOffset = -1; + } + return pNew; +} + +/* +** Given 1 to 3 identifiers preceeding the JOIN keyword, determine the +** type of join. Return an integer constant that expresses that type +** in terms of the following bit values: +** +** JT_INNER +** JT_OUTER +** JT_NATURAL +** JT_LEFT +** JT_RIGHT +** +** A full outer join is the combination of JT_LEFT and JT_RIGHT. +** +** If an illegal or unsupported join type is seen, then still return +** a join type, but put an error in the pParse structure. +*/ +int sqliteJoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){ + int jointype = 0; + Token *apAll[3]; + Token *p; + static struct { + const char *zKeyword; + int nChar; + int code; + } keywords[] = { + { "natural", 7, JT_NATURAL }, + { "left", 4, JT_LEFT|JT_OUTER }, + { "right", 5, JT_RIGHT|JT_OUTER }, + { "full", 4, JT_LEFT|JT_RIGHT|JT_OUTER }, + { "outer", 5, JT_OUTER }, + { "inner", 5, JT_INNER }, + { "cross", 5, JT_INNER }, + }; + int i, j; + apAll[0] = pA; + apAll[1] = pB; + apAll[2] = pC; + for(i=0; i<3 && apAll[i]; i++){ + p = apAll[i]; + for(j=0; jn==keywords[j].nChar + && sqliteStrNICmp(p->z, keywords[j].zKeyword, p->n)==0 ){ + jointype |= keywords[j].code; + break; + } + } + if( j>=sizeof(keywords)/sizeof(keywords[0]) ){ + jointype |= JT_ERROR; + break; + } + } + if( + (jointype & (JT_INNER|JT_OUTER))==(JT_INNER|JT_OUTER) || + (jointype & JT_ERROR)!=0 + ){ + static Token dummy = { 0, 0 }; + char *zSp1 = " ", *zSp2 = " "; + if( pB==0 ){ pB = &dummy; zSp1 = 0; } + if( pC==0 ){ pC = &dummy; zSp2 = 0; } + sqliteSetNString(&pParse->zErrMsg, "unknown or unsupported join type: ", 0, + pA->z, pA->n, zSp1, 1, pB->z, pB->n, zSp2, 1, pC->z, pC->n, 0); + pParse->nErr++; + jointype = JT_INNER; + }else if( jointype & JT_RIGHT ){ + sqliteErrorMsg(pParse, + "RIGHT and FULL OUTER JOINs are not currently supported"); + jointype = JT_INNER; + } + return jointype; +} + +/* +** Return the index of a column in a table. Return -1 if the column +** is not contained in the table. +*/ +static int columnIndex(Table *pTab, const char *zCol){ + int i; + for(i=0; inCol; i++){ + if( sqliteStrICmp(pTab->aCol[i].zName, zCol)==0 ) return i; + } + return -1; +} + +/* +** Add a term to the WHERE expression in *ppExpr that requires the +** zCol column to be equal in the two tables pTab1 and pTab2. +*/ +static void addWhereTerm( + const char *zCol, /* Name of the column */ + const Table *pTab1, /* First table */ + const Table *pTab2, /* Second table */ + Expr **ppExpr /* Add the equality term to this expression */ +){ + Token dummy; + Expr *pE1a, *pE1b, *pE1c; + Expr *pE2a, *pE2b, *pE2c; + Expr *pE; + + dummy.z = zCol; + dummy.n = strlen(zCol); + dummy.dyn = 0; + pE1a = sqliteExpr(TK_ID, 0, 0, &dummy); + pE2a = sqliteExpr(TK_ID, 0, 0, &dummy); + dummy.z = pTab1->zName; + dummy.n = strlen(dummy.z); + pE1b = sqliteExpr(TK_ID, 0, 0, &dummy); + dummy.z = pTab2->zName; + dummy.n = strlen(dummy.z); + pE2b = sqliteExpr(TK_ID, 0, 0, &dummy); + pE1c = sqliteExpr(TK_DOT, pE1b, pE1a, 0); + pE2c = sqliteExpr(TK_DOT, pE2b, pE2a, 0); + pE = sqliteExpr(TK_EQ, pE1c, pE2c, 0); + ExprSetProperty(pE, EP_FromJoin); + if( *ppExpr ){ + *ppExpr = sqliteExpr(TK_AND, *ppExpr, pE, 0); + }else{ + *ppExpr = pE; + } +} + +/* +** Set the EP_FromJoin property on all terms of the given expression. +** +** The EP_FromJoin property is used on terms of an expression to tell +** the LEFT OUTER JOIN processing logic that this term is part of the +** join restriction specified in the ON or USING clause and not a part +** of the more general WHERE clause. These terms are moved over to the +** WHERE clause during join processing but we need to remember that they +** originated in the ON or USING clause. +*/ +static void setJoinExpr(Expr *p){ + while( p ){ + ExprSetProperty(p, EP_FromJoin); + setJoinExpr(p->pLeft); + p = p->pRight; + } +} + +/* +** This routine processes the join information for a SELECT statement. +** ON and USING clauses are converted into extra terms of the WHERE clause. +** NATURAL joins also create extra WHERE clause terms. +** +** This routine returns the number of errors encountered. +*/ +static int sqliteProcessJoin(Parse *pParse, Select *p){ + SrcList *pSrc; + int i, j; + pSrc = p->pSrc; + for(i=0; inSrc-1; i++){ + struct SrcList_item *pTerm = &pSrc->a[i]; + struct SrcList_item *pOther = &pSrc->a[i+1]; + + if( pTerm->pTab==0 || pOther->pTab==0 ) continue; + + /* When the NATURAL keyword is present, add WHERE clause terms for + ** every column that the two tables have in common. + */ + if( pTerm->jointype & JT_NATURAL ){ + Table *pTab; + if( pTerm->pOn || pTerm->pUsing ){ + sqliteErrorMsg(pParse, "a NATURAL join may not have " + "an ON or USING clause", 0); + return 1; + } + pTab = pTerm->pTab; + for(j=0; jnCol; j++){ + if( columnIndex(pOther->pTab, pTab->aCol[j].zName)>=0 ){ + addWhereTerm(pTab->aCol[j].zName, pTab, pOther->pTab, &p->pWhere); + } + } + } + + /* Disallow both ON and USING clauses in the same join + */ + if( pTerm->pOn && pTerm->pUsing ){ + sqliteErrorMsg(pParse, "cannot have both ON and USING " + "clauses in the same join"); + return 1; + } + + /* Add the ON clause to the end of the WHERE clause, connected by + ** and AND operator. + */ + if( pTerm->pOn ){ + setJoinExpr(pTerm->pOn); + if( p->pWhere==0 ){ + p->pWhere = pTerm->pOn; + }else{ + p->pWhere = sqliteExpr(TK_AND, p->pWhere, pTerm->pOn, 0); + } + pTerm->pOn = 0; + } + + /* Create extra terms on the WHERE clause for each column named + ** in the USING clause. Example: If the two tables to be joined are + ** A and B and the USING clause names X, Y, and Z, then add this + ** to the WHERE clause: A.X=B.X AND A.Y=B.Y AND A.Z=B.Z + ** Report an error if any column mentioned in the USING clause is + ** not contained in both tables to be joined. + */ + if( pTerm->pUsing ){ + IdList *pList; + int j; + assert( inSrc-1 ); + pList = pTerm->pUsing; + for(j=0; jnId; j++){ + if( columnIndex(pTerm->pTab, pList->a[j].zName)<0 || + columnIndex(pOther->pTab, pList->a[j].zName)<0 ){ + sqliteErrorMsg(pParse, "cannot join using column %s - column " + "not present in both tables", pList->a[j].zName); + return 1; + } + addWhereTerm(pList->a[j].zName, pTerm->pTab, pOther->pTab, &p->pWhere); + } + } + } + return 0; +} + +/* +** Delete the given Select structure and all of its substructures. +*/ +void sqliteSelectDelete(Select *p){ + if( p==0 ) return; + sqliteExprListDelete(p->pEList); + sqliteSrcListDelete(p->pSrc); + sqliteExprDelete(p->pWhere); + sqliteExprListDelete(p->pGroupBy); + sqliteExprDelete(p->pHaving); + sqliteExprListDelete(p->pOrderBy); + sqliteSelectDelete(p->pPrior); + sqliteFree(p->zSelect); + sqliteFree(p); +} + +/* +** Delete the aggregate information from the parse structure. +*/ +static void sqliteAggregateInfoReset(Parse *pParse){ + sqliteFree(pParse->aAgg); + pParse->aAgg = 0; + pParse->nAgg = 0; + pParse->useAgg = 0; +} + +/* +** Insert code into "v" that will push the record on the top of the +** stack into the sorter. +*/ +static void pushOntoSorter(Parse *pParse, Vdbe *v, ExprList *pOrderBy){ + char *zSortOrder; + int i; + zSortOrder = sqliteMalloc( pOrderBy->nExpr + 1 ); + if( zSortOrder==0 ) return; + for(i=0; inExpr; i++){ + int order = pOrderBy->a[i].sortOrder; + int type; + int c; + if( (order & SQLITE_SO_TYPEMASK)==SQLITE_SO_TEXT ){ + type = SQLITE_SO_TEXT; + }else if( (order & SQLITE_SO_TYPEMASK)==SQLITE_SO_NUM ){ + type = SQLITE_SO_NUM; + }else if( pParse->db->file_format>=4 ){ + type = sqliteExprType(pOrderBy->a[i].pExpr); + }else{ + type = SQLITE_SO_NUM; + } + if( (order & SQLITE_SO_DIRMASK)==SQLITE_SO_ASC ){ + c = type==SQLITE_SO_TEXT ? 'A' : '+'; + }else{ + c = type==SQLITE_SO_TEXT ? 'D' : '-'; + } + zSortOrder[i] = c; + sqliteExprCode(pParse, pOrderBy->a[i].pExpr); + } + zSortOrder[pOrderBy->nExpr] = 0; + sqliteVdbeOp3(v, OP_SortMakeKey, pOrderBy->nExpr, 0, zSortOrder, P3_DYNAMIC); + sqliteVdbeAddOp(v, OP_SortPut, 0, 0); +} + +/* +** This routine adds a P3 argument to the last VDBE opcode that was +** inserted. The P3 argument added is a string suitable for the +** OP_MakeKey or OP_MakeIdxKey opcodes. The string consists of +** characters 't' or 'n' depending on whether or not the various +** fields of the key to be generated should be treated as numeric +** or as text. See the OP_MakeKey and OP_MakeIdxKey opcode +** documentation for additional information about the P3 string. +** See also the sqliteAddIdxKeyType() routine. +*/ +void sqliteAddKeyType(Vdbe *v, ExprList *pEList){ + int nColumn = pEList->nExpr; + char *zType = sqliteMalloc( nColumn+1 ); + int i; + if( zType==0 ) return; + for(i=0; ia[i].pExpr)==SQLITE_SO_NUM ? 'n' : 't'; + } + zType[i] = 0; + sqliteVdbeChangeP3(v, -1, zType, P3_DYNAMIC); +} + +/* +** Add code to implement the OFFSET and LIMIT +*/ +static void codeLimiter( + Vdbe *v, /* Generate code into this VM */ + Select *p, /* The SELECT statement being coded */ + int iContinue, /* Jump here to skip the current record */ + int iBreak, /* Jump here to end the loop */ + int nPop /* Number of times to pop stack when jumping */ +){ + if( p->iOffset>=0 ){ + int addr = sqliteVdbeCurrentAddr(v) + 2; + if( nPop>0 ) addr++; + sqliteVdbeAddOp(v, OP_MemIncr, p->iOffset, addr); + if( nPop>0 ){ + sqliteVdbeAddOp(v, OP_Pop, nPop, 0); + } + sqliteVdbeAddOp(v, OP_Goto, 0, iContinue); + } + if( p->iLimit>=0 ){ + sqliteVdbeAddOp(v, OP_MemIncr, p->iLimit, iBreak); + } +} + +/* +** This routine generates the code for the inside of the inner loop +** of a SELECT. +** +** If srcTab and nColumn are both zero, then the pEList expressions +** are evaluated in order to get the data for this row. If nColumn>0 +** then data is pulled from srcTab and pEList is used only to get the +** datatypes for each column. +*/ +static int selectInnerLoop( + Parse *pParse, /* The parser context */ + Select *p, /* The complete select statement being coded */ + ExprList *pEList, /* List of values being extracted */ + int srcTab, /* Pull data from this table */ + int nColumn, /* Number of columns in the source table */ + ExprList *pOrderBy, /* If not NULL, sort results using this key */ + int distinct, /* If >=0, make sure results are distinct */ + int eDest, /* How to dispose of the results */ + int iParm, /* An argument to the disposal method */ + int iContinue, /* Jump here to continue with next row */ + int iBreak /* Jump here to break out of the inner loop */ +){ + Vdbe *v = pParse->pVdbe; + int i; + int hasDistinct; /* True if the DISTINCT keyword is present */ + + if( v==0 ) return 0; + assert( pEList!=0 ); + + /* If there was a LIMIT clause on the SELECT statement, then do the check + ** to see if this row should be output. + */ + hasDistinct = distinct>=0 && pEList && pEList->nExpr>0; + if( pOrderBy==0 && !hasDistinct ){ + codeLimiter(v, p, iContinue, iBreak, 0); + } + + /* Pull the requested columns. + */ + if( nColumn>0 ){ + for(i=0; inExpr; + for(i=0; inExpr; i++){ + sqliteExprCode(pParse, pEList->a[i].pExpr); + } + } + + /* If the DISTINCT keyword was present on the SELECT statement + ** and this row has been seen before, then do not make this row + ** part of the result. + */ + if( hasDistinct ){ +#if NULL_ALWAYS_DISTINCT + sqliteVdbeAddOp(v, OP_IsNull, -pEList->nExpr, sqliteVdbeCurrentAddr(v)+7); +#endif + sqliteVdbeAddOp(v, OP_MakeKey, pEList->nExpr, 1); + if( pParse->db->file_format>=4 ) sqliteAddKeyType(v, pEList); + sqliteVdbeAddOp(v, OP_Distinct, distinct, sqliteVdbeCurrentAddr(v)+3); + sqliteVdbeAddOp(v, OP_Pop, pEList->nExpr+1, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, iContinue); + sqliteVdbeAddOp(v, OP_String, 0, 0); + sqliteVdbeAddOp(v, OP_PutStrKey, distinct, 0); + if( pOrderBy==0 ){ + codeLimiter(v, p, iContinue, iBreak, nColumn); + } + } + + switch( eDest ){ + /* In this mode, write each query result to the key of the temporary + ** table iParm. + */ + case SRT_Union: { + sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, NULL_ALWAYS_DISTINCT); + sqliteVdbeAddOp(v, OP_String, 0, 0); + sqliteVdbeAddOp(v, OP_PutStrKey, iParm, 0); + break; + } + + /* Store the result as data using a unique key. + */ + case SRT_Table: + case SRT_TempTable: { + sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 0); + if( pOrderBy ){ + pushOntoSorter(pParse, v, pOrderBy); + }else{ + sqliteVdbeAddOp(v, OP_NewRecno, iParm, 0); + sqliteVdbeAddOp(v, OP_Pull, 1, 0); + sqliteVdbeAddOp(v, OP_PutIntKey, iParm, 0); + } + break; + } + + /* Construct a record from the query result, but instead of + ** saving that record, use it as a key to delete elements from + ** the temporary table iParm. + */ + case SRT_Except: { + int addr; + addr = sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, NULL_ALWAYS_DISTINCT); + sqliteVdbeAddOp(v, OP_NotFound, iParm, addr+3); + sqliteVdbeAddOp(v, OP_Delete, iParm, 0); + break; + } + + /* If we are creating a set for an "expr IN (SELECT ...)" construct, + ** then there should be a single item on the stack. Write this + ** item into the set table with bogus data. + */ + case SRT_Set: { + int addr1 = sqliteVdbeCurrentAddr(v); + int addr2; + assert( nColumn==1 ); + sqliteVdbeAddOp(v, OP_NotNull, -1, addr1+3); + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + addr2 = sqliteVdbeAddOp(v, OP_Goto, 0, 0); + if( pOrderBy ){ + pushOntoSorter(pParse, v, pOrderBy); + }else{ + sqliteVdbeAddOp(v, OP_String, 0, 0); + sqliteVdbeAddOp(v, OP_PutStrKey, iParm, 0); + } + sqliteVdbeChangeP2(v, addr2, sqliteVdbeCurrentAddr(v)); + break; + } + + /* If this is a scalar select that is part of an expression, then + ** store the results in the appropriate memory cell and break out + ** of the scan loop. + */ + case SRT_Mem: { + assert( nColumn==1 ); + if( pOrderBy ){ + pushOntoSorter(pParse, v, pOrderBy); + }else{ + sqliteVdbeAddOp(v, OP_MemStore, iParm, 1); + sqliteVdbeAddOp(v, OP_Goto, 0, iBreak); + } + break; + } + + /* Send the data to the callback function. + */ + case SRT_Callback: + case SRT_Sorter: { + if( pOrderBy ){ + sqliteVdbeAddOp(v, OP_SortMakeRec, nColumn, 0); + pushOntoSorter(pParse, v, pOrderBy); + }else{ + assert( eDest==SRT_Callback ); + sqliteVdbeAddOp(v, OP_Callback, nColumn, 0); + } + break; + } + + /* Invoke a subroutine to handle the results. The subroutine itself + ** is responsible for popping the results off of the stack. + */ + case SRT_Subroutine: { + if( pOrderBy ){ + sqliteVdbeAddOp(v, OP_MakeRecord, nColumn, 0); + pushOntoSorter(pParse, v, pOrderBy); + }else{ + sqliteVdbeAddOp(v, OP_Gosub, 0, iParm); + } + break; + } + + /* Discard the results. This is used for SELECT statements inside + ** the body of a TRIGGER. The purpose of such selects is to call + ** user-defined functions that have side effects. We do not care + ** about the actual results of the select. + */ + default: { + assert( eDest==SRT_Discard ); + sqliteVdbeAddOp(v, OP_Pop, nColumn, 0); + break; + } + } + return 0; +} + +/* +** If the inner loop was generated using a non-null pOrderBy argument, +** then the results were placed in a sorter. After the loop is terminated +** we need to run the sorter and output the results. The following +** routine generates the code needed to do that. +*/ +static void generateSortTail( + Select *p, /* The SELECT statement */ + Vdbe *v, /* Generate code into this VDBE */ + int nColumn, /* Number of columns of data */ + int eDest, /* Write the sorted results here */ + int iParm /* Optional parameter associated with eDest */ +){ + int end1 = sqliteVdbeMakeLabel(v); + int end2 = sqliteVdbeMakeLabel(v); + int addr; + if( eDest==SRT_Sorter ) return; + sqliteVdbeAddOp(v, OP_Sort, 0, 0); + addr = sqliteVdbeAddOp(v, OP_SortNext, 0, end1); + codeLimiter(v, p, addr, end2, 1); + switch( eDest ){ + case SRT_Callback: { + sqliteVdbeAddOp(v, OP_SortCallback, nColumn, 0); + break; + } + case SRT_Table: + case SRT_TempTable: { + sqliteVdbeAddOp(v, OP_NewRecno, iParm, 0); + sqliteVdbeAddOp(v, OP_Pull, 1, 0); + sqliteVdbeAddOp(v, OP_PutIntKey, iParm, 0); + break; + } + case SRT_Set: { + assert( nColumn==1 ); + sqliteVdbeAddOp(v, OP_NotNull, -1, sqliteVdbeCurrentAddr(v)+3); + sqliteVdbeAddOp(v, OP_Pop, 1, 0); + sqliteVdbeAddOp(v, OP_Goto, 0, sqliteVdbeCurrentAddr(v)+3); + sqliteVdbeAddOp(v, OP_String, 0, 0); + sqliteVdbeAddOp(v, OP_PutStrKey, iParm, 0); + break; + } + case SRT_Mem: { + assert( nColumn==1 ); + sqliteVdbeAddOp(v, OP_MemStore, iParm, 1); + sqliteVdbeAddOp(v, OP_Goto, 0, end1); + break; + } + case SRT_Subroutine: { + int i; + for(i=0; ipVdbe; + int i, j; + for(i=0; inExpr; i++){ + Expr *p = pEList->a[i].pExpr; + char *zType = 0; + if( p==0 ) continue; + if( p->op==TK_COLUMN && pTabList ){ + Table *pTab; + int iCol = p->iColumn; + for(j=0; jnSrc && pTabList->a[j].iCursor!=p->iTable; j++){} + assert( jnSrc ); + pTab = pTabList->a[j].pTab; + if( iCol<0 ) iCol = pTab->iPKey; + assert( iCol==-1 || (iCol>=0 && iColnCol) ); + if( iCol<0 ){ + zType = "INTEGER"; + }else{ + zType = pTab->aCol[iCol].zType; + } + }else{ + if( sqliteExprType(p)==SQLITE_SO_TEXT ){ + zType = "TEXT"; + }else{ + zType = "NUMERIC"; + } + } + sqliteVdbeOp3(v, OP_ColumnName, i + pEList->nExpr, 0, zType, 0); + } +} + +/* +** Generate code that will tell the VDBE the names of columns +** in the result set. This information is used to provide the +** azCol[] values in the callback. +*/ +static void generateColumnNames( + Parse *pParse, /* Parser context */ + SrcList *pTabList, /* List of tables */ + ExprList *pEList /* Expressions defining the result set */ +){ + Vdbe *v = pParse->pVdbe; + int i, j; + sqlite *db = pParse->db; + int fullNames, shortNames; + + assert( v!=0 ); + if( pParse->colNamesSet || v==0 || sqlite_malloc_failed ) return; + pParse->colNamesSet = 1; + fullNames = (db->flags & SQLITE_FullColNames)!=0; + shortNames = (db->flags & SQLITE_ShortColNames)!=0; + for(i=0; inExpr; i++){ + Expr *p; + int p2 = i==pEList->nExpr-1; + p = pEList->a[i].pExpr; + if( p==0 ) continue; + if( pEList->a[i].zName ){ + char *zName = pEList->a[i].zName; + sqliteVdbeOp3(v, OP_ColumnName, i, p2, zName, 0); + continue; + } + if( p->op==TK_COLUMN && pTabList ){ + Table *pTab; + char *zCol; + int iCol = p->iColumn; + for(j=0; jnSrc && pTabList->a[j].iCursor!=p->iTable; j++){} + assert( jnSrc ); + pTab = pTabList->a[j].pTab; + if( iCol<0 ) iCol = pTab->iPKey; + assert( iCol==-1 || (iCol>=0 && iColnCol) ); + if( iCol<0 ){ + zCol = "_ROWID_"; + }else{ + zCol = pTab->aCol[iCol].zName; + } + if( !shortNames && !fullNames && p->span.z && p->span.z[0] ){ + int addr = sqliteVdbeOp3(v,OP_ColumnName, i, p2, p->span.z, p->span.n); + sqliteVdbeCompressSpace(v, addr); + }else if( fullNames || (!shortNames && pTabList->nSrc>1) ){ + char *zName = 0; + char *zTab; + + zTab = pTabList->a[j].zAlias; + if( fullNames || zTab==0 ) zTab = pTab->zName; + sqliteSetString(&zName, zTab, ".", zCol, 0); + sqliteVdbeOp3(v, OP_ColumnName, i, p2, zName, P3_DYNAMIC); + }else{ + sqliteVdbeOp3(v, OP_ColumnName, i, p2, zCol, 0); + } + }else if( p->span.z && p->span.z[0] ){ + int addr = sqliteVdbeOp3(v,OP_ColumnName, i, p2, p->span.z, p->span.n); + sqliteVdbeCompressSpace(v, addr); + }else{ + char zName[30]; + assert( p->op!=TK_COLUMN || pTabList==0 ); + sprintf(zName, "column%d", i+1); + sqliteVdbeOp3(v, OP_ColumnName, i, p2, zName, 0); + } + } +} + +/* +** Name of the connection operator, used for error messages. +*/ +static const char *selectOpName(int id){ + char *z; + switch( id ){ + case TK_ALL: z = "UNION ALL"; break; + case TK_INTERSECT: z = "INTERSECT"; break; + case TK_EXCEPT: z = "EXCEPT"; break; + default: z = "UNION"; break; + } + return z; +} + +/* +** Forward declaration +*/ +static int fillInColumnList(Parse*, Select*); + +/* +** Given a SELECT statement, generate a Table structure that describes +** the result set of that SELECT. +*/ +Table *sqliteResultSetOfSelect(Parse *pParse, char *zTabName, Select *pSelect){ + Table *pTab; + int i, j; + ExprList *pEList; + Column *aCol; + + if( fillInColumnList(pParse, pSelect) ){ + return 0; + } + pTab = sqliteMalloc( sizeof(Table) ); + if( pTab==0 ){ + return 0; + } + pTab->zName = zTabName ? sqliteStrDup(zTabName) : 0; + pEList = pSelect->pEList; + pTab->nCol = pEList->nExpr; + assert( pTab->nCol>0 ); + pTab->aCol = aCol = sqliteMalloc( sizeof(pTab->aCol[0])*pTab->nCol ); + for(i=0; inCol; i++){ + Expr *p, *pR; + if( pEList->a[i].zName ){ + aCol[i].zName = sqliteStrDup(pEList->a[i].zName); + }else if( (p=pEList->a[i].pExpr)->op==TK_DOT + && (pR=p->pRight)!=0 && pR->token.z && pR->token.z[0] ){ + int cnt; + sqliteSetNString(&aCol[i].zName, pR->token.z, pR->token.n, 0); + for(j=cnt=0; jtoken.z, pR->token.n, zBuf, n,0); + j = -1; + } + } + }else if( p->span.z && p->span.z[0] ){ + sqliteSetNString(&pTab->aCol[i].zName, p->span.z, p->span.n, 0); + }else{ + char zBuf[30]; + sprintf(zBuf, "column%d", i+1); + aCol[i].zName = sqliteStrDup(zBuf); + } + sqliteDequote(aCol[i].zName); + } + pTab->iPKey = -1; + return pTab; +} + +/* +** For the given SELECT statement, do three things. +** +** (1) Fill in the pTabList->a[].pTab fields in the SrcList that +** defines the set of tables that should be scanned. For views, +** fill pTabList->a[].pSelect with a copy of the SELECT statement +** that implements the view. A copy is made of the view's SELECT +** statement so that we can freely modify or delete that statement +** without worrying about messing up the presistent representation +** of the view. +** +** (2) Add terms to the WHERE clause to accomodate the NATURAL keyword +** on joins and the ON and USING clause of joins. +** +** (3) Scan the list of columns in the result set (pEList) looking +** for instances of the "*" operator or the TABLE.* operator. +** If found, expand each "*" to be every column in every table +** and TABLE.* to be every column in TABLE. +** +** Return 0 on success. If there are problems, leave an error message +** in pParse and return non-zero. +*/ +static int fillInColumnList(Parse *pParse, Select *p){ + int i, j, k, rc; + SrcList *pTabList; + ExprList *pEList; + Table *pTab; + + if( p==0 || p->pSrc==0 ) return 1; + pTabList = p->pSrc; + pEList = p->pEList; + + /* Look up every table in the table list. + */ + for(i=0; inSrc; i++){ + if( pTabList->a[i].pTab ){ + /* This routine has run before! No need to continue */ + return 0; + } + if( pTabList->a[i].zName==0 ){ + /* A sub-query in the FROM clause of a SELECT */ + assert( pTabList->a[i].pSelect!=0 ); + if( pTabList->a[i].zAlias==0 ){ + char zFakeName[60]; + sprintf(zFakeName, "sqlite_subquery_%p_", + (void*)pTabList->a[i].pSelect); + sqliteSetString(&pTabList->a[i].zAlias, zFakeName, 0); + } + pTabList->a[i].pTab = pTab = + sqliteResultSetOfSelect(pParse, pTabList->a[i].zAlias, + pTabList->a[i].pSelect); + if( pTab==0 ){ + return 1; + } + /* The isTransient flag indicates that the Table structure has been + ** dynamically allocated and may be freed at any time. In other words, + ** pTab is not pointing to a persistent table structure that defines + ** part of the schema. */ + pTab->isTransient = 1; + }else{ + /* An ordinary table or view name in the FROM clause */ + pTabList->a[i].pTab = pTab = + sqliteLocateTable(pParse,pTabList->a[i].zName,pTabList->a[i].zDatabase); + if( pTab==0 ){ + return 1; + } + if( pTab->pSelect ){ + /* We reach here if the named table is a really a view */ + if( sqliteViewGetColumnNames(pParse, pTab) ){ + return 1; + } + /* If pTabList->a[i].pSelect!=0 it means we are dealing with a + ** view within a view. The SELECT structure has already been + ** copied by the outer view so we can skip the copy step here + ** in the inner view. + */ + if( pTabList->a[i].pSelect==0 ){ + pTabList->a[i].pSelect = sqliteSelectDup(pTab->pSelect); + } + } + } + } + + /* Process NATURAL keywords, and ON and USING clauses of joins. + */ + if( sqliteProcessJoin(pParse, p) ) return 1; + + /* For every "*" that occurs in the column list, insert the names of + ** all columns in all tables. And for every TABLE.* insert the names + ** of all columns in TABLE. The parser inserted a special expression + ** with the TK_ALL operator for each "*" that it found in the column list. + ** The following code just has to locate the TK_ALL expressions and expand + ** each one to the list of all columns in all tables. + ** + ** The first loop just checks to see if there are any "*" operators + ** that need expanding. + */ + for(k=0; knExpr; k++){ + Expr *pE = pEList->a[k].pExpr; + if( pE->op==TK_ALL ) break; + if( pE->op==TK_DOT && pE->pRight && pE->pRight->op==TK_ALL + && pE->pLeft && pE->pLeft->op==TK_ID ) break; + } + rc = 0; + if( knExpr ){ + /* + ** If we get here it means the result set contains one or more "*" + ** operators that need to be expanded. Loop through each expression + ** in the result set and expand them one by one. + */ + struct ExprList_item *a = pEList->a; + ExprList *pNew = 0; + for(k=0; knExpr; k++){ + Expr *pE = a[k].pExpr; + if( pE->op!=TK_ALL && + (pE->op!=TK_DOT || pE->pRight==0 || pE->pRight->op!=TK_ALL) ){ + /* This particular expression does not need to be expanded. + */ + pNew = sqliteExprListAppend(pNew, a[k].pExpr, 0); + pNew->a[pNew->nExpr-1].zName = a[k].zName; + a[k].pExpr = 0; + a[k].zName = 0; + }else{ + /* This expression is a "*" or a "TABLE.*" and needs to be + ** expanded. */ + int tableSeen = 0; /* Set to 1 when TABLE matches */ + char *zTName; /* text of name of TABLE */ + if( pE->op==TK_DOT && pE->pLeft ){ + zTName = sqliteTableNameFromToken(&pE->pLeft->token); + }else{ + zTName = 0; + } + for(i=0; inSrc; i++){ + Table *pTab = pTabList->a[i].pTab; + char *zTabName = pTabList->a[i].zAlias; + if( zTabName==0 || zTabName[0]==0 ){ + zTabName = pTab->zName; + } + if( zTName && (zTabName==0 || zTabName[0]==0 || + sqliteStrICmp(zTName, zTabName)!=0) ){ + continue; + } + tableSeen = 1; + for(j=0; jnCol; j++){ + Expr *pExpr, *pLeft, *pRight; + char *zName = pTab->aCol[j].zName; + + if( i>0 && (pTabList->a[i-1].jointype & JT_NATURAL)!=0 && + columnIndex(pTabList->a[i-1].pTab, zName)>=0 ){ + /* In a NATURAL join, omit the join columns from the + ** table on the right */ + continue; + } + if( i>0 && sqliteIdListIndex(pTabList->a[i-1].pUsing, zName)>=0 ){ + /* In a join with a USING clause, omit columns in the + ** using clause from the table on the right. */ + continue; + } + pRight = sqliteExpr(TK_ID, 0, 0, 0); + if( pRight==0 ) break; + pRight->token.z = zName; + pRight->token.n = strlen(zName); + pRight->token.dyn = 0; + if( zTabName && pTabList->nSrc>1 ){ + pLeft = sqliteExpr(TK_ID, 0, 0, 0); + pExpr = sqliteExpr(TK_DOT, pLeft, pRight, 0); + if( pExpr==0 ) break; + pLeft->token.z = zTabName; + pLeft->token.n = strlen(zTabName); + pLeft->token.dyn = 0; + sqliteSetString((char**)&pExpr->span.z, zTabName, ".", zName, 0); + pExpr->span.n = strlen(pExpr->span.z); + pExpr->span.dyn = 1; + pExpr->token.z = 0; + pExpr->token.n = 0; + pExpr->token.dyn = 0; + }else{ + pExpr = pRight; + pExpr->span = pExpr->token; + } + pNew = sqliteExprListAppend(pNew, pExpr, 0); + } + } + if( !tableSeen ){ + if( zTName ){ + sqliteErrorMsg(pParse, "no such table: %s", zTName); + }else{ + sqliteErrorMsg(pParse, "no tables specified"); + } + rc = 1; + } + sqliteFree(zTName); + } + } + sqliteExprListDelete(pEList); + p->pEList = pNew; + } + return rc; +} + +/* +** This routine recursively unlinks the Select.pSrc.a[].pTab pointers +** in a select structure. It just sets the pointers to NULL. This +** routine is recursive in the sense that if the Select.pSrc.a[].pSelect +** pointer is not NULL, this routine is called recursively on that pointer. +** +** This routine is called on the Select structure that defines a +** VIEW in order to undo any bindings to tables. This is necessary +** because those tables might be DROPed by a subsequent SQL command. +** If the bindings are not removed, then the Select.pSrc->a[].pTab field +** will be left pointing to a deallocated Table structure after the +** DROP and a coredump will occur the next time the VIEW is used. +*/ +void sqliteSelectUnbind(Select *p){ + int i; + SrcList *pSrc = p->pSrc; + Table *pTab; + if( p==0 ) return; + for(i=0; inSrc; i++){ + if( (pTab = pSrc->a[i].pTab)!=0 ){ + if( pTab->isTransient ){ + sqliteDeleteTable(0, pTab); + } + pSrc->a[i].pTab = 0; + if( pSrc->a[i].pSelect ){ + sqliteSelectUnbind(pSrc->a[i].pSelect); + } + } + } +} + +/* +** This routine associates entries in an ORDER BY expression list with +** columns in a result. For each ORDER BY expression, the opcode of +** the top-level node is changed to TK_COLUMN and the iColumn value of +** the top-level node is filled in with column number and the iTable +** value of the top-level node is filled with iTable parameter. +** +** If there are prior SELECT clauses, they are processed first. A match +** in an earlier SELECT takes precedence over a later SELECT. +** +** Any entry that does not match is flagged as an error. The number +** of errors is returned. +** +** This routine does NOT correctly initialize the Expr.dataType field +** of the ORDER BY expressions. The multiSelectSortOrder() routine +** must be called to do that after the individual select statements +** have all been analyzed. This routine is unable to compute Expr.dataType +** because it must be called before the individual select statements +** have been analyzed. +*/ +static int matchOrderbyToColumn( + Parse *pParse, /* A place to leave error messages */ + Select *pSelect, /* Match to result columns of this SELECT */ + ExprList *pOrderBy, /* The ORDER BY values to match against columns */ + int iTable, /* Insert this value in iTable */ + int mustComplete /* If TRUE all ORDER BYs must match */ +){ + int nErr = 0; + int i, j; + ExprList *pEList; + + if( pSelect==0 || pOrderBy==0 ) return 1; + if( mustComplete ){ + for(i=0; inExpr; i++){ pOrderBy->a[i].done = 0; } + } + if( fillInColumnList(pParse, pSelect) ){ + return 1; + } + if( pSelect->pPrior ){ + if( matchOrderbyToColumn(pParse, pSelect->pPrior, pOrderBy, iTable, 0) ){ + return 1; + } + } + pEList = pSelect->pEList; + for(i=0; inExpr; i++){ + Expr *pE = pOrderBy->a[i].pExpr; + int iCol = -1; + if( pOrderBy->a[i].done ) continue; + if( sqliteExprIsInteger(pE, &iCol) ){ + if( iCol<=0 || iCol>pEList->nExpr ){ + sqliteErrorMsg(pParse, + "ORDER BY position %d should be between 1 and %d", + iCol, pEList->nExpr); + nErr++; + break; + } + if( !mustComplete ) continue; + iCol--; + } + for(j=0; iCol<0 && jnExpr; j++){ + if( pEList->a[j].zName && (pE->op==TK_ID || pE->op==TK_STRING) ){ + char *zName, *zLabel; + zName = pEList->a[j].zName; + assert( pE->token.z ); + zLabel = sqliteStrNDup(pE->token.z, pE->token.n); + sqliteDequote(zLabel); + if( sqliteStrICmp(zName, zLabel)==0 ){ + iCol = j; + } + sqliteFree(zLabel); + } + if( iCol<0 && sqliteExprCompare(pE, pEList->a[j].pExpr) ){ + iCol = j; + } + } + if( iCol>=0 ){ + pE->op = TK_COLUMN; + pE->iColumn = iCol; + pE->iTable = iTable; + pOrderBy->a[i].done = 1; + } + if( iCol<0 && mustComplete ){ + sqliteErrorMsg(pParse, + "ORDER BY term number %d does not match any result column", i+1); + nErr++; + break; + } + } + return nErr; +} + +/* +** Get a VDBE for the given parser context. Create a new one if necessary. +** If an error occurs, return NULL and leave a message in pParse. +*/ +Vdbe *sqliteGetVdbe(Parse *pParse){ + Vdbe *v = pParse->pVdbe; + if( v==0 ){ + v = pParse->pVdbe = sqliteVdbeCreate(pParse->db); + } + return v; +} + +/* +** This routine sets the Expr.dataType field on all elements of +** the pOrderBy expression list. The pOrderBy list will have been +** set up by matchOrderbyToColumn(). Hence each expression has +** a TK_COLUMN as its root node. The Expr.iColumn refers to a +** column in the result set. The datatype is set to SQLITE_SO_TEXT +** if the corresponding column in p and every SELECT to the left of +** p has a datatype of SQLITE_SO_TEXT. If the cooressponding column +** in p or any of the left SELECTs is SQLITE_SO_NUM, then the datatype +** of the order-by expression is set to SQLITE_SO_NUM. +** +** Examples: +** +** CREATE TABLE one(a INTEGER, b TEXT); +** CREATE TABLE two(c VARCHAR(5), d FLOAT); +** +** SELECT b, b FROM one UNION SELECT d, c FROM two ORDER BY 1, 2; +** +** The primary sort key will use SQLITE_SO_NUM because the "d" in +** the second SELECT is numeric. The 1st column of the first SELECT +** is text but that does not matter because a numeric always overrides +** a text. +** +** The secondary key will use the SQLITE_SO_TEXT sort order because +** both the (second) "b" in the first SELECT and the "c" in the second +** SELECT have a datatype of text. +*/ +static void multiSelectSortOrder(Select *p, ExprList *pOrderBy){ + int i; + ExprList *pEList; + if( pOrderBy==0 ) return; + if( p==0 ){ + for(i=0; inExpr; i++){ + pOrderBy->a[i].pExpr->dataType = SQLITE_SO_TEXT; + } + return; + } + multiSelectSortOrder(p->pPrior, pOrderBy); + pEList = p->pEList; + for(i=0; inExpr; i++){ + Expr *pE = pOrderBy->a[i].pExpr; + if( pE->dataType==SQLITE_SO_NUM ) continue; + assert( pE->iColumn>=0 ); + if( pEList->nExpr>pE->iColumn ){ + pE->dataType = sqliteExprType(pEList->a[pE->iColumn].pExpr); + } + } +} + +/* +** Compute the iLimit and iOffset fields of the SELECT based on the +** nLimit and nOffset fields. nLimit and nOffset hold the integers +** that appear in the original SQL statement after the LIMIT and OFFSET +** keywords. Or that hold -1 and 0 if those keywords are omitted. +** iLimit and iOffset are the integer memory register numbers for +** counters used to compute the limit and offset. If there is no +** limit and/or offset, then iLimit and iOffset are negative. +** +** This routine changes the values if iLimit and iOffset only if +** a limit or offset is defined by nLimit and nOffset. iLimit and +** iOffset should have been preset to appropriate default values +** (usually but not always -1) prior to calling this routine. +** Only if nLimit>=0 or nOffset>0 do the limit registers get +** redefined. The UNION ALL operator uses this property to force +** the reuse of the same limit and offset registers across multiple +** SELECT statements. +*/ +static void computeLimitRegisters(Parse *pParse, Select *p){ + /* + ** If the comparison is p->nLimit>0 then "LIMIT 0" shows + ** all rows. It is the same as no limit. If the comparision is + ** p->nLimit>=0 then "LIMIT 0" show no rows at all. + ** "LIMIT -1" always shows all rows. There is some + ** contraversy about what the correct behavior should be. + ** The current implementation interprets "LIMIT 0" to mean + ** no rows. + */ + if( p->nLimit>=0 ){ + int iMem = pParse->nMem++; + Vdbe *v = sqliteGetVdbe(pParse); + if( v==0 ) return; + sqliteVdbeAddOp(v, OP_Integer, -p->nLimit, 0); + sqliteVdbeAddOp(v, OP_MemStore, iMem, 1); + p->iLimit = iMem; + } + if( p->nOffset>0 ){ + int iMem = pParse->nMem++; + Vdbe *v = sqliteGetVdbe(pParse); + if( v==0 ) return; + sqliteVdbeAddOp(v, OP_Integer, -p->nOffset, 0); + sqliteVdbeAddOp(v, OP_MemStore, iMem, 1); + p->iOffset = iMem; + } +} + +/* +** This routine is called to process a query that is really the union +** or intersection of two or more separate queries. +** +** "p" points to the right-most of the two queries. the query on the +** left is p->pPrior. The left query could also be a compound query +** in which case this routine will be called recursively. +** +** The results of the total query are to be written into a destination +** of type eDest with parameter iParm. +** +** Example 1: Consider a three-way compound SQL statement. +** +** SELECT a FROM t1 UNION SELECT b FROM t2 UNION SELECT c FROM t3 +** +** This statement is parsed up as follows: +** +** SELECT c FROM t3 +** | +** `-----> SELECT b FROM t2 +** | +** `------> SELECT a FROM t1 +** +** The arrows in the diagram above represent the Select.pPrior pointer. +** So if this routine is called with p equal to the t3 query, then +** pPrior will be the t2 query. p->op will be TK_UNION in this case. +** +** Notice that because of the way SQLite parses compound SELECTs, the +** individual selects always group from left to right. +*/ +static int multiSelect(Parse *pParse, Select *p, int eDest, int iParm){ + int rc; /* Success code from a subroutine */ + Select *pPrior; /* Another SELECT immediately to our left */ + Vdbe *v; /* Generate code to this VDBE */ + + /* Make sure there is no ORDER BY or LIMIT clause on prior SELECTs. Only + ** the last SELECT in the series may have an ORDER BY or LIMIT. + */ + if( p==0 || p->pPrior==0 ) return 1; + pPrior = p->pPrior; + if( pPrior->pOrderBy ){ + sqliteErrorMsg(pParse,"ORDER BY clause should come after %s not before", + selectOpName(p->op)); + return 1; + } + if( pPrior->nLimit>=0 || pPrior->nOffset>0 ){ + sqliteErrorMsg(pParse,"LIMIT clause should come after %s not before", + selectOpName(p->op)); + return 1; + } + + /* Make sure we have a valid query engine. If not, create a new one. + */ + v = sqliteGetVdbe(pParse); + if( v==0 ) return 1; + + /* Create the destination temporary table if necessary + */ + if( eDest==SRT_TempTable ){ + sqliteVdbeAddOp(v, OP_OpenTemp, iParm, 0); + eDest = SRT_Table; + } + + /* Generate code for the left and right SELECT statements. + */ + switch( p->op ){ + case TK_ALL: { + if( p->pOrderBy==0 ){ + pPrior->nLimit = p->nLimit; + pPrior->nOffset = p->nOffset; + rc = sqliteSelect(pParse, pPrior, eDest, iParm, 0, 0, 0); + if( rc ) return rc; + p->pPrior = 0; + p->iLimit = pPrior->iLimit; + p->iOffset = pPrior->iOffset; + p->nLimit = -1; + p->nOffset = 0; + rc = sqliteSelect(pParse, p, eDest, iParm, 0, 0, 0); + p->pPrior = pPrior; + if( rc ) return rc; + break; + } + /* For UNION ALL ... ORDER BY fall through to the next case */ + } + case TK_EXCEPT: + case TK_UNION: { + int unionTab; /* Cursor number of the temporary table holding result */ + int op; /* One of the SRT_ operations to apply to self */ + int priorOp; /* The SRT_ operation to apply to prior selects */ + int nLimit, nOffset; /* Saved values of p->nLimit and p->nOffset */ + ExprList *pOrderBy; /* The ORDER BY clause for the right SELECT */ + + priorOp = p->op==TK_ALL ? SRT_Table : SRT_Union; + if( eDest==priorOp && p->pOrderBy==0 && p->nLimit<0 && p->nOffset==0 ){ + /* We can reuse a temporary table generated by a SELECT to our + ** right. + */ + unionTab = iParm; + }else{ + /* We will need to create our own temporary table to hold the + ** intermediate results. + */ + unionTab = pParse->nTab++; + if( p->pOrderBy + && matchOrderbyToColumn(pParse, p, p->pOrderBy, unionTab, 1) ){ + return 1; + } + if( p->op!=TK_ALL ){ + sqliteVdbeAddOp(v, OP_OpenTemp, unionTab, 1); + sqliteVdbeAddOp(v, OP_KeyAsData, unionTab, 1); + }else{ + sqliteVdbeAddOp(v, OP_OpenTemp, unionTab, 0); + } + } + + /* Code the SELECT statements to our left + */ + rc = sqliteSelect(pParse, pPrior, priorOp, unionTab, 0, 0, 0); + if( rc ) return rc; + + /* Code the current SELECT statement + */ + switch( p->op ){ + case TK_EXCEPT: op = SRT_Except; break; + case TK_UNION: op = SRT_Union; break; + case TK_ALL: op = SRT_Table; break; + } + p->pPrior = 0; + pOrderBy = p->pOrderBy; + p->pOrderBy = 0; + nLimit = p->nLimit; + p->nLimit = -1; + nOffset = p->nOffset; + p->nOffset = 0; + rc = sqliteSelect(pParse, p, op, unionTab, 0, 0, 0); + p->pPrior = pPrior; + p->pOrderBy = pOrderBy; + p->nLimit = nLimit; + p->nOffset = nOffset; + if( rc ) return rc; + + /* Convert the data in the temporary table into whatever form + ** it is that we currently need. + */ + if( eDest!=priorOp || unionTab!=iParm ){ + int iCont, iBreak, iStart; + assert( p->pEList ); + if( eDest==SRT_Callback ){ + generateColumnNames(pParse, 0, p->pEList); + generateColumnTypes(pParse, p->pSrc, p->pEList); + } + iBreak = sqliteVdbeMakeLabel(v); + iCont = sqliteVdbeMakeLabel(v); + sqliteVdbeAddOp(v, OP_Rewind, unionTab, iBreak); + computeLimitRegisters(pParse, p); + iStart = sqliteVdbeCurrentAddr(v); + multiSelectSortOrder(p, p->pOrderBy); + rc = selectInnerLoop(pParse, p, p->pEList, unionTab, p->pEList->nExpr, + p->pOrderBy, -1, eDest, iParm, + iCont, iBreak); + if( rc ) return 1; + sqliteVdbeResolveLabel(v, iCont); + sqliteVdbeAddOp(v, OP_Next, unionTab, iStart); + sqliteVdbeResolveLabel(v, iBreak); + sqliteVdbeAddOp(v, OP_Close, unionTab, 0); + if( p->pOrderBy ){ + generateSortTail(p, v, p->pEList->nExpr, eDest, iParm); + } + } + break; + } + case TK_INTERSECT: { + int tab1, tab2; + int iCont, iBreak, iStart; + int nLimit, nOffset; + + /* INTERSECT is different from the others since it requires + ** two temporary tables. Hence it has its own case. Begin + ** by allocating the tables we will need. + */ + tab1 = pParse->nTab++; + tab2 = pParse->nTab++; + if( p->pOrderBy && matchOrderbyToColumn(pParse,p,p->pOrderBy,tab1,1) ){ + return 1; + } + sqliteVdbeAddOp(v, OP_OpenTemp, tab1, 1); + sqliteVdbeAddOp(v, OP_KeyAsData, tab1, 1); + + /* Code the SELECTs to our left into temporary table "tab1". + */ + rc = sqliteSelect(pParse, pPrior, SRT_Union, tab1, 0, 0, 0); + if( rc ) return rc; + + /* Code the current SELECT into temporary table "tab2" + */ + sqliteVdbeAddOp(v, OP_OpenTemp, tab2, 1); + sqliteVdbeAddOp(v, OP_KeyAsData, tab2, 1); + p->pPrior = 0; + nLimit = p->nLimit; + p->nLimit = -1; + nOffset = p->nOffset; + p->nOffset = 0; + rc = sqliteSelect(pParse, p, SRT_Union, tab2, 0, 0, 0); + p->pPrior = pPrior; + p->nLimit = nLimit; + p->nOffset = nOffset; + if( rc ) return rc; + + /* Generate code to take the intersection of the two temporary + ** tables. + */ + assert( p->pEList ); + if( eDest==SRT_Callback ){ + generateColumnNames(pParse, 0, p->pEList); + generateColumnTypes(pParse, p->pSrc, p->pEList); + } + iBreak = sqliteVdbeMakeLabel(v); + iCont = sqliteVdbeMakeLabel(v); + sqliteVdbeAddOp(v, OP_Rewind, tab1, iBreak); + computeLimitRegisters(pParse, p); + iStart = sqliteVdbeAddOp(v, OP_FullKey, tab1, 0); + sqliteVdbeAddOp(v, OP_NotFound, tab2, iCont); + multiSelectSortOrder(p, p->pOrderBy); + rc = selectInnerLoop(pParse, p, p->pEList, tab1, p->pEList->nExpr, + p->pOrderBy, -1, eDest, iParm, + iCont, iBreak); + if( rc ) return 1; + sqliteVdbeResolveLabel(v, iCont); + sqliteVdbeAddOp(v, OP_Next, tab1, iStart); + sqliteVdbeResolveLabel(v, iBreak); + sqliteVdbeAddOp(v, OP_Close, tab2, 0); + sqliteVdbeAddOp(v, OP_Close, tab1, 0); + if( p->pOrderBy ){ + generateSortTail(p, v, p->pEList->nExpr, eDest, iParm); + } + break; + } + } + assert( p->pEList && pPrior->pEList ); + if( p->pEList->nExpr!=pPrior->pEList->nExpr ){ + sqliteErrorMsg(pParse, "SELECTs to the left and right of %s" + " do not have the same number of result columns", selectOpName(p->op)); + return 1; + } + return 0; +} + +/* +** Scan through the expression pExpr. Replace every reference to +** a column in table number iTable with a copy of the iColumn-th +** entry in pEList. (But leave references to the ROWID column +** unchanged.) +** +** This routine is part of the flattening procedure. A subquery +** whose result set is defined by pEList appears as entry in the +** FROM clause of a SELECT such that the VDBE cursor assigned to that +** FORM clause entry is iTable. This routine make the necessary +** changes to pExpr so that it refers directly to the source table +** of the subquery rather the result set of the subquery. +*/ +static void substExprList(ExprList*,int,ExprList*); /* Forward Decl */ +static void substExpr(Expr *pExpr, int iTable, ExprList *pEList){ + if( pExpr==0 ) return; + if( pExpr->op==TK_COLUMN && pExpr->iTable==iTable ){ + if( pExpr->iColumn<0 ){ + pExpr->op = TK_NULL; + }else{ + Expr *pNew; + assert( pEList!=0 && pExpr->iColumnnExpr ); + assert( pExpr->pLeft==0 && pExpr->pRight==0 && pExpr->pList==0 ); + pNew = pEList->a[pExpr->iColumn].pExpr; + assert( pNew!=0 ); + pExpr->op = pNew->op; + pExpr->dataType = pNew->dataType; + assert( pExpr->pLeft==0 ); + pExpr->pLeft = sqliteExprDup(pNew->pLeft); + assert( pExpr->pRight==0 ); + pExpr->pRight = sqliteExprDup(pNew->pRight); + assert( pExpr->pList==0 ); + pExpr->pList = sqliteExprListDup(pNew->pList); + pExpr->iTable = pNew->iTable; + pExpr->iColumn = pNew->iColumn; + pExpr->iAgg = pNew->iAgg; + sqliteTokenCopy(&pExpr->token, &pNew->token); + sqliteTokenCopy(&pExpr->span, &pNew->span); + } + }else{ + substExpr(pExpr->pLeft, iTable, pEList); + substExpr(pExpr->pRight, iTable, pEList); + substExprList(pExpr->pList, iTable, pEList); + } +} +static void +substExprList(ExprList *pList, int iTable, ExprList *pEList){ + int i; + if( pList==0 ) return; + for(i=0; inExpr; i++){ + substExpr(pList->a[i].pExpr, iTable, pEList); + } +} + +/* +** This routine attempts to flatten subqueries in order to speed +** execution. It returns 1 if it makes changes and 0 if no flattening +** occurs. +** +** To understand the concept of flattening, consider the following +** query: +** +** SELECT a FROM (SELECT x+y AS a FROM t1 WHERE z<100) WHERE a>5 +** +** The default way of implementing this query is to execute the +** subquery first and store the results in a temporary table, then +** run the outer query on that temporary table. This requires two +** passes over the data. Furthermore, because the temporary table +** has no indices, the WHERE clause on the outer query cannot be +** optimized. +** +** This routine attempts to rewrite queries such as the above into +** a single flat select, like this: +** +** SELECT x+y AS a FROM t1 WHERE z<100 AND a>5 +** +** The code generated for this simpification gives the same result +** but only has to scan the data once. And because indices might +** exist on the table t1, a complete scan of the data might be +** avoided. +** +** Flattening is only attempted if all of the following are true: +** +** (1) The subquery and the outer query do not both use aggregates. +** +** (2) The subquery is not an aggregate or the outer query is not a join. +** +** (3) The subquery is not the right operand of a left outer join, or +** the subquery is not itself a join. (Ticket #306) +** +** (4) The subquery is not DISTINCT or the outer query is not a join. +** +** (5) The subquery is not DISTINCT or the outer query does not use +** aggregates. +** +** (6) The subquery does not use aggregates or the outer query is not +** DISTINCT. +** +** (7) The subquery has a FROM clause. +** +** (8) The subquery does not use LIMIT or the outer query is not a join. +** +** (9) The subquery does not use LIMIT or the outer query does not use +** aggregates. +** +** (10) The subquery does not use aggregates or the outer query does not +** use LIMIT. +** +** (11) The subquery and the outer query do not both have ORDER BY clauses. +** +** (12) The subquery is not the right term of a LEFT OUTER JOIN or the +** subquery has no WHERE clause. (added by ticket #350) +** +** In this routine, the "p" parameter is a pointer to the outer query. +** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query +** uses aggregates and subqueryIsAgg is true if the subquery uses aggregates. +** +** If flattening is not attempted, this routine is a no-op and returns 0. +** If flattening is attempted this routine returns 1. +** +** All of the expression analysis must occur on both the outer query and +** the subquery before this routine runs. +*/ +static int flattenSubquery( + Parse *pParse, /* The parsing context */ + Select *p, /* The parent or outer SELECT statement */ + int iFrom, /* Index in p->pSrc->a[] of the inner subquery */ + int isAgg, /* True if outer SELECT uses aggregate functions */ + int subqueryIsAgg /* True if the subquery uses aggregate functions */ +){ + Select *pSub; /* The inner query or "subquery" */ + SrcList *pSrc; /* The FROM clause of the outer query */ + SrcList *pSubSrc; /* The FROM clause of the subquery */ + ExprList *pList; /* The result set of the outer query */ + int iParent; /* VDBE cursor number of the pSub result set temp table */ + int i; + Expr *pWhere; + + /* Check to see if flattening is permitted. Return 0 if not. + */ + if( p==0 ) return 0; + pSrc = p->pSrc; + assert( pSrc && iFrom>=0 && iFromnSrc ); + pSub = pSrc->a[iFrom].pSelect; + assert( pSub!=0 ); + if( isAgg && subqueryIsAgg ) return 0; + if( subqueryIsAgg && pSrc->nSrc>1 ) return 0; + pSubSrc = pSub->pSrc; + assert( pSubSrc ); + if( pSubSrc->nSrc==0 ) return 0; + if( (pSub->isDistinct || pSub->nLimit>=0) && (pSrc->nSrc>1 || isAgg) ){ + return 0; + } + if( (p->isDistinct || p->nLimit>=0) && subqueryIsAgg ) return 0; + if( p->pOrderBy && pSub->pOrderBy ) return 0; + + /* Restriction 3: If the subquery is a join, make sure the subquery is + ** not used as the right operand of an outer join. Examples of why this + ** is not allowed: + ** + ** t1 LEFT OUTER JOIN (t2 JOIN t3) + ** + ** If we flatten the above, we would get + ** + ** (t1 LEFT OUTER JOIN t2) JOIN t3 + ** + ** which is not at all the same thing. + */ + if( pSubSrc->nSrc>1 && iFrom>0 && (pSrc->a[iFrom-1].jointype & JT_OUTER)!=0 ){ + return 0; + } + + /* Restriction 12: If the subquery is the right operand of a left outer + ** join, make sure the subquery has no WHERE clause. + ** An examples of why this is not allowed: + ** + ** t1 LEFT OUTER JOIN (SELECT * FROM t2 WHERE t2.x>0) + ** + ** If we flatten the above, we would get + ** + ** (t1 LEFT OUTER JOIN t2) WHERE t2.x>0 + ** + ** But the t2.x>0 test will always fail on a NULL row of t2, which + ** effectively converts the OUTER JOIN into an INNER JOIN. + */ + if( iFrom>0 && (pSrc->a[iFrom-1].jointype & JT_OUTER)!=0 + && pSub->pWhere!=0 ){ + return 0; + } + + /* If we reach this point, it means flattening is permitted for the + ** iFrom-th entry of the FROM clause in the outer query. + */ + + /* Move all of the FROM elements of the subquery into the + ** the FROM clause of the outer query. Before doing this, remember + ** the cursor number for the original outer query FROM element in + ** iParent. The iParent cursor will never be used. Subsequent code + ** will scan expressions looking for iParent references and replace + ** those references with expressions that resolve to the subquery FROM + ** elements we are now copying in. + */ + iParent = pSrc->a[iFrom].iCursor; + { + int nSubSrc = pSubSrc->nSrc; + int jointype = pSrc->a[iFrom].jointype; + + if( pSrc->a[iFrom].pTab && pSrc->a[iFrom].pTab->isTransient ){ + sqliteDeleteTable(0, pSrc->a[iFrom].pTab); + } + sqliteFree(pSrc->a[iFrom].zDatabase); + sqliteFree(pSrc->a[iFrom].zName); + sqliteFree(pSrc->a[iFrom].zAlias); + if( nSubSrc>1 ){ + int extra = nSubSrc - 1; + for(i=1; ipSrc = pSrc; + for(i=pSrc->nSrc-1; i-extra>=iFrom; i--){ + pSrc->a[i] = pSrc->a[i-extra]; + } + } + for(i=0; ia[i+iFrom] = pSubSrc->a[i]; + memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); + } + pSrc->a[iFrom+nSubSrc-1].jointype = jointype; + } + + /* Now begin substituting subquery result set expressions for + ** references to the iParent in the outer query. + ** + ** Example: + ** + ** SELECT a+5, b*10 FROM (SELECT x*3 AS a, y+10 AS b FROM t1) WHERE a>b; + ** \ \_____________ subquery __________/ / + ** \_____________________ outer query ______________________________/ + ** + ** We look at every expression in the outer query and every place we see + ** "a" we substitute "x*3" and every place we see "b" we substitute "y+10". + */ + substExprList(p->pEList, iParent, pSub->pEList); + pList = p->pEList; + for(i=0; inExpr; i++){ + Expr *pExpr; + if( pList->a[i].zName==0 && (pExpr = pList->a[i].pExpr)->span.z!=0 ){ + pList->a[i].zName = sqliteStrNDup(pExpr->span.z, pExpr->span.n); + } + } + if( isAgg ){ + substExprList(p->pGroupBy, iParent, pSub->pEList); + substExpr(p->pHaving, iParent, pSub->pEList); + } + if( pSub->pOrderBy ){ + assert( p->pOrderBy==0 ); + p->pOrderBy = pSub->pOrderBy; + pSub->pOrderBy = 0; + }else if( p->pOrderBy ){ + substExprList(p->pOrderBy, iParent, pSub->pEList); + } + if( pSub->pWhere ){ + pWhere = sqliteExprDup(pSub->pWhere); + }else{ + pWhere = 0; + } + if( subqueryIsAgg ){ + assert( p->pHaving==0 ); + p->pHaving = p->pWhere; + p->pWhere = pWhere; + substExpr(p->pHaving, iParent, pSub->pEList); + if( pSub->pHaving ){ + Expr *pHaving = sqliteExprDup(pSub->pHaving); + if( p->pHaving ){ + p->pHaving = sqliteExpr(TK_AND, p->pHaving, pHaving, 0); + }else{ + p->pHaving = pHaving; + } + } + assert( p->pGroupBy==0 ); + p->pGroupBy = sqliteExprListDup(pSub->pGroupBy); + }else if( p->pWhere==0 ){ + p->pWhere = pWhere; + }else{ + substExpr(p->pWhere, iParent, pSub->pEList); + if( pWhere ){ + p->pWhere = sqliteExpr(TK_AND, p->pWhere, pWhere, 0); + } + } + + /* The flattened query is distinct if either the inner or the + ** outer query is distinct. + */ + p->isDistinct = p->isDistinct || pSub->isDistinct; + + /* Transfer the limit expression from the subquery to the outer + ** query. + */ + if( pSub->nLimit>=0 ){ + if( p->nLimit<0 ){ + p->nLimit = pSub->nLimit; + }else if( p->nLimit+p->nOffset > pSub->nLimit+pSub->nOffset ){ + p->nLimit = pSub->nLimit + pSub->nOffset - p->nOffset; + } + } + p->nOffset += pSub->nOffset; + + /* Finially, delete what is left of the subquery and return + ** success. + */ + sqliteSelectDelete(pSub); + return 1; +} + +/* +** Analyze the SELECT statement passed in as an argument to see if it +** is a simple min() or max() query. If it is and this query can be +** satisfied using a single seek to the beginning or end of an index, +** then generate the code for this SELECT and return 1. If this is not a +** simple min() or max() query, then return 0; +** +** A simply min() or max() query looks like this: +** +** SELECT min(a) FROM table; +** SELECT max(a) FROM table; +** +** The query may have only a single table in its FROM argument. There +** can be no GROUP BY or HAVING or WHERE clauses. The result set must +** be the min() or max() of a single column of the table. The column +** in the min() or max() function must be indexed. +** +** The parameters to this routine are the same as for sqliteSelect(). +** See the header comment on that routine for additional information. +*/ +static int simpleMinMaxQuery(Parse *pParse, Select *p, int eDest, int iParm){ + Expr *pExpr; + int iCol; + Table *pTab; + Index *pIdx; + int base; + Vdbe *v; + int seekOp; + int cont; + ExprList *pEList, *pList, eList; + struct ExprList_item eListItem; + SrcList *pSrc; + + + /* Check to see if this query is a simple min() or max() query. Return + ** zero if it is not. + */ + if( p->pGroupBy || p->pHaving || p->pWhere ) return 0; + pSrc = p->pSrc; + if( pSrc->nSrc!=1 ) return 0; + pEList = p->pEList; + if( pEList->nExpr!=1 ) return 0; + pExpr = pEList->a[0].pExpr; + if( pExpr->op!=TK_AGG_FUNCTION ) return 0; + pList = pExpr->pList; + if( pList==0 || pList->nExpr!=1 ) return 0; + if( pExpr->token.n!=3 ) return 0; + if( sqliteStrNICmp(pExpr->token.z,"min",3)==0 ){ + seekOp = OP_Rewind; + }else if( sqliteStrNICmp(pExpr->token.z,"max",3)==0 ){ + seekOp = OP_Last; + }else{ + return 0; + } + pExpr = pList->a[0].pExpr; + if( pExpr->op!=TK_COLUMN ) return 0; + iCol = pExpr->iColumn; + pTab = pSrc->a[0].pTab; + + /* If we get to here, it means the query is of the correct form. + ** Check to make sure we have an index and make pIdx point to the + ** appropriate index. If the min() or max() is on an INTEGER PRIMARY + ** key column, no index is necessary so set pIdx to NULL. If no + ** usable index is found, return 0. + */ + if( iCol<0 ){ + pIdx = 0; + }else{ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + assert( pIdx->nColumn>=1 ); + if( pIdx->aiColumn[0]==iCol ) break; + } + if( pIdx==0 ) return 0; + } + + /* Identify column types if we will be using the callback. This + ** step is skipped if the output is going to a table or a memory cell. + ** The column names have already been generated in the calling function. + */ + v = sqliteGetVdbe(pParse); + if( v==0 ) return 0; + if( eDest==SRT_Callback ){ + generateColumnTypes(pParse, p->pSrc, p->pEList); + } + + /* If the output is destined for a temporary table, open that table. + */ + if( eDest==SRT_TempTable ){ + sqliteVdbeAddOp(v, OP_OpenTemp, iParm, 0); + } + + /* Generating code to find the min or the max. Basically all we have + ** to do is find the first or the last entry in the chosen index. If + ** the min() or max() is on the INTEGER PRIMARY KEY, then find the first + ** or last entry in the main table. + */ + sqliteCodeVerifySchema(pParse, pTab->iDb); + base = pSrc->a[0].iCursor; + computeLimitRegisters(pParse, p); + if( pSrc->a[0].pSelect==0 ){ + sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0); + sqliteVdbeOp3(v, OP_OpenRead, base, pTab->tnum, pTab->zName, 0); + } + cont = sqliteVdbeMakeLabel(v); + if( pIdx==0 ){ + sqliteVdbeAddOp(v, seekOp, base, 0); + }else{ + sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0); + sqliteVdbeOp3(v, OP_OpenRead, base+1, pIdx->tnum, pIdx->zName, P3_STATIC); + if( seekOp==OP_Rewind ){ + sqliteVdbeAddOp(v, OP_String, 0, 0); + sqliteVdbeAddOp(v, OP_MakeKey, 1, 0); + sqliteVdbeAddOp(v, OP_IncrKey, 0, 0); + seekOp = OP_MoveTo; + } + sqliteVdbeAddOp(v, seekOp, base+1, 0); + sqliteVdbeAddOp(v, OP_IdxRecno, base+1, 0); + sqliteVdbeAddOp(v, OP_Close, base+1, 0); + sqliteVdbeAddOp(v, OP_MoveTo, base, 0); + } + eList.nExpr = 1; + memset(&eListItem, 0, sizeof(eListItem)); + eList.a = &eListItem; + eList.a[0].pExpr = pExpr; + selectInnerLoop(pParse, p, &eList, 0, 0, 0, -1, eDest, iParm, cont, cont); + sqliteVdbeResolveLabel(v, cont); + sqliteVdbeAddOp(v, OP_Close, base, 0); + + return 1; +} + +/* +** Generate code for the given SELECT statement. +** +** The results are distributed in various ways depending on the +** value of eDest and iParm. +** +** eDest Value Result +** ------------ ------------------------------------------- +** SRT_Callback Invoke the callback for each row of the result. +** +** SRT_Mem Store first result in memory cell iParm +** +** SRT_Set Store results as keys of a table with cursor iParm +** +** SRT_Union Store results as a key in a temporary table iParm +** +** SRT_Except Remove results from the temporary table iParm. +** +** SRT_Table Store results in temporary table iParm +** +** The table above is incomplete. Additional eDist value have be added +** since this comment was written. See the selectInnerLoop() function for +** a complete listing of the allowed values of eDest and their meanings. +** +** This routine returns the number of errors. If any errors are +** encountered, then an appropriate error message is left in +** pParse->zErrMsg. +** +** This routine does NOT free the Select structure passed in. The +** calling function needs to do that. +** +** The pParent, parentTab, and *pParentAgg fields are filled in if this +** SELECT is a subquery. This routine may try to combine this SELECT +** with its parent to form a single flat query. In so doing, it might +** change the parent query from a non-aggregate to an aggregate query. +** For that reason, the pParentAgg flag is passed as a pointer, so it +** can be changed. +** +** Example 1: The meaning of the pParent parameter. +** +** SELECT * FROM t1 JOIN (SELECT x, count(*) FROM t2) JOIN t3; +** \ \_______ subquery _______/ / +** \ / +** \____________________ outer query ___________________/ +** +** This routine is called for the outer query first. For that call, +** pParent will be NULL. During the processing of the outer query, this +** routine is called recursively to handle the subquery. For the recursive +** call, pParent will point to the outer query. Because the subquery is +** the second element in a three-way join, the parentTab parameter will +** be 1 (the 2nd value of a 0-indexed array.) +*/ +int sqliteSelect( + Parse *pParse, /* The parser context */ + Select *p, /* The SELECT statement being coded. */ + int eDest, /* How to dispose of the results */ + int iParm, /* A parameter used by the eDest disposal method */ + Select *pParent, /* Another SELECT for which this is a sub-query */ + int parentTab, /* Index in pParent->pSrc of this query */ + int *pParentAgg /* True if pParent uses aggregate functions */ +){ + int i; + WhereInfo *pWInfo; + Vdbe *v; + int isAgg = 0; /* True for select lists like "count(*)" */ + ExprList *pEList; /* List of columns to extract. */ + SrcList *pTabList; /* List of tables to select from */ + Expr *pWhere; /* The WHERE clause. May be NULL */ + ExprList *pOrderBy; /* The ORDER BY clause. May be NULL */ + ExprList *pGroupBy; /* The GROUP BY clause. May be NULL */ + Expr *pHaving; /* The HAVING clause. May be NULL */ + int isDistinct; /* True if the DISTINCT keyword is present */ + int distinct; /* Table to use for the distinct set */ + int rc = 1; /* Value to return from this function */ + + if( sqlite_malloc_failed || pParse->nErr || p==0 ) return 1; + if( sqliteAuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1; + + /* If there is are a sequence of queries, do the earlier ones first. + */ + if( p->pPrior ){ + return multiSelect(pParse, p, eDest, iParm); + } + + /* Make local copies of the parameters for this query. + */ + pTabList = p->pSrc; + pWhere = p->pWhere; + pOrderBy = p->pOrderBy; + pGroupBy = p->pGroupBy; + pHaving = p->pHaving; + isDistinct = p->isDistinct; + + /* Allocate VDBE cursors for each table in the FROM clause + */ + sqliteSrcListAssignCursors(pParse, pTabList); + + /* + ** Do not even attempt to generate any code if we have already seen + ** errors before this routine starts. + */ + if( pParse->nErr>0 ) goto select_end; + + /* Expand any "*" terms in the result set. (For example the "*" in + ** "SELECT * FROM t1") The fillInColumnlist() routine also does some + ** other housekeeping - see the header comment for details. + */ + if( fillInColumnList(pParse, p) ){ + goto select_end; + } + pWhere = p->pWhere; + pEList = p->pEList; + if( pEList==0 ) goto select_end; + + /* If writing to memory or generating a set + ** only a single column may be output. + */ + if( (eDest==SRT_Mem || eDest==SRT_Set) && pEList->nExpr>1 ){ + sqliteErrorMsg(pParse, "only a single result allowed for " + "a SELECT that is part of an expression"); + goto select_end; + } + + /* ORDER BY is ignored for some destinations. + */ + switch( eDest ){ + case SRT_Union: + case SRT_Except: + case SRT_Discard: + pOrderBy = 0; + break; + default: + break; + } + + /* At this point, we should have allocated all the cursors that we + ** need to handle subquerys and temporary tables. + ** + ** Resolve the column names and do a semantics check on all the expressions. + */ + for(i=0; inExpr; i++){ + if( sqliteExprResolveIds(pParse, pTabList, 0, pEList->a[i].pExpr) ){ + goto select_end; + } + if( sqliteExprCheck(pParse, pEList->a[i].pExpr, 1, &isAgg) ){ + goto select_end; + } + } + if( pWhere ){ + if( sqliteExprResolveIds(pParse, pTabList, pEList, pWhere) ){ + goto select_end; + } + if( sqliteExprCheck(pParse, pWhere, 0, 0) ){ + goto select_end; + } + } + if( pHaving ){ + if( pGroupBy==0 ){ + sqliteErrorMsg(pParse, "a GROUP BY clause is required before HAVING"); + goto select_end; + } + if( sqliteExprResolveIds(pParse, pTabList, pEList, pHaving) ){ + goto select_end; + } + if( sqliteExprCheck(pParse, pHaving, 1, &isAgg) ){ + goto select_end; + } + } + if( pOrderBy ){ + for(i=0; inExpr; i++){ + int iCol; + Expr *pE = pOrderBy->a[i].pExpr; + if( sqliteExprIsInteger(pE, &iCol) && iCol>0 && iCol<=pEList->nExpr ){ + sqliteExprDelete(pE); + pE = pOrderBy->a[i].pExpr = sqliteExprDup(pEList->a[iCol-1].pExpr); + } + if( sqliteExprResolveIds(pParse, pTabList, pEList, pE) ){ + goto select_end; + } + if( sqliteExprCheck(pParse, pE, isAgg, 0) ){ + goto select_end; + } + if( sqliteExprIsConstant(pE) ){ + if( sqliteExprIsInteger(pE, &iCol)==0 ){ + sqliteErrorMsg(pParse, + "ORDER BY terms must not be non-integer constants"); + goto select_end; + }else if( iCol<=0 || iCol>pEList->nExpr ){ + sqliteErrorMsg(pParse, + "ORDER BY column number %d out of range - should be " + "between 1 and %d", iCol, pEList->nExpr); + goto select_end; + } + } + } + } + if( pGroupBy ){ + for(i=0; inExpr; i++){ + int iCol; + Expr *pE = pGroupBy->a[i].pExpr; + if( sqliteExprIsInteger(pE, &iCol) && iCol>0 && iCol<=pEList->nExpr ){ + sqliteExprDelete(pE); + pE = pGroupBy->a[i].pExpr = sqliteExprDup(pEList->a[iCol-1].pExpr); + } + if( sqliteExprResolveIds(pParse, pTabList, pEList, pE) ){ + goto select_end; + } + if( sqliteExprCheck(pParse, pE, isAgg, 0) ){ + goto select_end; + } + if( sqliteExprIsConstant(pE) ){ + if( sqliteExprIsInteger(pE, &iCol)==0 ){ + sqliteErrorMsg(pParse, + "GROUP BY terms must not be non-integer constants"); + goto select_end; + }else if( iCol<=0 || iCol>pEList->nExpr ){ + sqliteErrorMsg(pParse, + "GROUP BY column number %d out of range - should be " + "between 1 and %d", iCol, pEList->nExpr); + goto select_end; + } + } + } + } + + /* Begin generating code. + */ + v = sqliteGetVdbe(pParse); + if( v==0 ) goto select_end; + + /* Identify column names if we will be using them in a callback. This + ** step is skipped if the output is going to some other destination. + */ + if( eDest==SRT_Callback ){ + generateColumnNames(pParse, pTabList, pEList); + } + + /* Generate code for all sub-queries in the FROM clause + */ + for(i=0; inSrc; i++){ + const char *zSavedAuthContext; + int needRestoreContext; + + if( pTabList->a[i].pSelect==0 ) continue; + if( pTabList->a[i].zName!=0 ){ + zSavedAuthContext = pParse->zAuthContext; + pParse->zAuthContext = pTabList->a[i].zName; + needRestoreContext = 1; + }else{ + needRestoreContext = 0; + } + sqliteSelect(pParse, pTabList->a[i].pSelect, SRT_TempTable, + pTabList->a[i].iCursor, p, i, &isAgg); + if( needRestoreContext ){ + pParse->zAuthContext = zSavedAuthContext; + } + pTabList = p->pSrc; + pWhere = p->pWhere; + if( eDest!=SRT_Union && eDest!=SRT_Except && eDest!=SRT_Discard ){ + pOrderBy = p->pOrderBy; + } + pGroupBy = p->pGroupBy; + pHaving = p->pHaving; + isDistinct = p->isDistinct; + } + + /* Check for the special case of a min() or max() function by itself + ** in the result set. + */ + if( simpleMinMaxQuery(pParse, p, eDest, iParm) ){ + rc = 0; + goto select_end; + } + + /* Check to see if this is a subquery that can be "flattened" into its parent. + ** If flattening is a possiblity, do so and return immediately. + */ + if( pParent && pParentAgg && + flattenSubquery(pParse, pParent, parentTab, *pParentAgg, isAgg) ){ + if( isAgg ) *pParentAgg = 1; + return rc; + } + + /* Set the limiter. + */ + computeLimitRegisters(pParse, p); + + /* Identify column types if we will be using a callback. This + ** step is skipped if the output is going to a destination other + ** than a callback. + ** + ** We have to do this separately from the creation of column names + ** above because if the pTabList contains views then they will not + ** have been resolved and we will not know the column types until + ** now. + */ + if( eDest==SRT_Callback ){ + generateColumnTypes(pParse, pTabList, pEList); + } + + /* If the output is destined for a temporary table, open that table. + */ + if( eDest==SRT_TempTable ){ + sqliteVdbeAddOp(v, OP_OpenTemp, iParm, 0); + } + + /* Do an analysis of aggregate expressions. + */ + sqliteAggregateInfoReset(pParse); + if( isAgg || pGroupBy ){ + assert( pParse->nAgg==0 ); + isAgg = 1; + for(i=0; inExpr; i++){ + if( sqliteExprAnalyzeAggregates(pParse, pEList->a[i].pExpr) ){ + goto select_end; + } + } + if( pGroupBy ){ + for(i=0; inExpr; i++){ + if( sqliteExprAnalyzeAggregates(pParse, pGroupBy->a[i].pExpr) ){ + goto select_end; + } + } + } + if( pHaving && sqliteExprAnalyzeAggregates(pParse, pHaving) ){ + goto select_end; + } + if( pOrderBy ){ + for(i=0; inExpr; i++){ + if( sqliteExprAnalyzeAggregates(pParse, pOrderBy->a[i].pExpr) ){ + goto select_end; + } + } + } + } + + /* Reset the aggregator + */ + if( isAgg ){ + sqliteVdbeAddOp(v, OP_AggReset, 0, pParse->nAgg); + for(i=0; inAgg; i++){ + FuncDef *pFunc; + if( (pFunc = pParse->aAgg[i].pFunc)!=0 && pFunc->xFinalize!=0 ){ + sqliteVdbeOp3(v, OP_AggInit, 0, i, (char*)pFunc, P3_POINTER); + } + } + if( pGroupBy==0 ){ + sqliteVdbeAddOp(v, OP_String, 0, 0); + sqliteVdbeAddOp(v, OP_AggFocus, 0, 0); + } + } + + /* Initialize the memory cell to NULL + */ + if( eDest==SRT_Mem ){ + sqliteVdbeAddOp(v, OP_String, 0, 0); + sqliteVdbeAddOp(v, OP_MemStore, iParm, 1); + } + + /* Open a temporary table to use for the distinct set. + */ + if( isDistinct ){ + distinct = pParse->nTab++; + sqliteVdbeAddOp(v, OP_OpenTemp, distinct, 1); + }else{ + distinct = -1; + } + + /* Begin the database scan + */ + pWInfo = sqliteWhereBegin(pParse, pTabList, pWhere, 0, + pGroupBy ? 0 : &pOrderBy); + if( pWInfo==0 ) goto select_end; + + /* Use the standard inner loop if we are not dealing with + ** aggregates + */ + if( !isAgg ){ + if( selectInnerLoop(pParse, p, pEList, 0, 0, pOrderBy, distinct, eDest, + iParm, pWInfo->iContinue, pWInfo->iBreak) ){ + goto select_end; + } + } + + /* If we are dealing with aggregates, then do the special aggregate + ** processing. + */ + else{ + AggExpr *pAgg; + if( pGroupBy ){ + int lbl1; + for(i=0; inExpr; i++){ + sqliteExprCode(pParse, pGroupBy->a[i].pExpr); + } + sqliteVdbeAddOp(v, OP_MakeKey, pGroupBy->nExpr, 0); + if( pParse->db->file_format>=4 ) sqliteAddKeyType(v, pGroupBy); + lbl1 = sqliteVdbeMakeLabel(v); + sqliteVdbeAddOp(v, OP_AggFocus, 0, lbl1); + for(i=0, pAgg=pParse->aAgg; inAgg; i++, pAgg++){ + if( pAgg->isAgg ) continue; + sqliteExprCode(pParse, pAgg->pExpr); + sqliteVdbeAddOp(v, OP_AggSet, 0, i); + } + sqliteVdbeResolveLabel(v, lbl1); + } + for(i=0, pAgg=pParse->aAgg; inAgg; i++, pAgg++){ + Expr *pE; + int nExpr; + FuncDef *pDef; + if( !pAgg->isAgg ) continue; + assert( pAgg->pFunc!=0 ); + assert( pAgg->pFunc->xStep!=0 ); + pDef = pAgg->pFunc; + pE = pAgg->pExpr; + assert( pE!=0 ); + assert( pE->op==TK_AGG_FUNCTION ); + nExpr = sqliteExprCodeExprList(pParse, pE->pList, pDef->includeTypes); + sqliteVdbeAddOp(v, OP_Integer, i, 0); + sqliteVdbeOp3(v, OP_AggFunc, 0, nExpr, (char*)pDef, P3_POINTER); + } + } + + /* End the database scan loop. + */ + sqliteWhereEnd(pWInfo); + + /* If we are processing aggregates, we need to set up a second loop + ** over all of the aggregate values and process them. + */ + if( isAgg ){ + int endagg = sqliteVdbeMakeLabel(v); + int startagg; + startagg = sqliteVdbeAddOp(v, OP_AggNext, 0, endagg); + pParse->useAgg = 1; + if( pHaving ){ + sqliteExprIfFalse(pParse, pHaving, startagg, 1); + } + if( selectInnerLoop(pParse, p, pEList, 0, 0, pOrderBy, distinct, eDest, + iParm, startagg, endagg) ){ + goto select_end; + } + sqliteVdbeAddOp(v, OP_Goto, 0, startagg); + sqliteVdbeResolveLabel(v, endagg); + sqliteVdbeAddOp(v, OP_Noop, 0, 0); + pParse->useAgg = 0; + } + + /* If there is an ORDER BY clause, then we need to sort the results + ** and send them to the callback one by one. + */ + if( pOrderBy ){ + generateSortTail(p, v, pEList->nExpr, eDest, iParm); + } + + /* If this was a subquery, we have now converted the subquery into a + ** temporary table. So delete the subquery structure from the parent + ** to prevent this subquery from being evaluated again and to force the + ** the use of the temporary table. + */ + if( pParent ){ + assert( pParent->pSrc->nSrc>parentTab ); + assert( pParent->pSrc->a[parentTab].pSelect==p ); + sqliteSelectDelete(p); + pParent->pSrc->a[parentTab].pSelect = 0; + } + + /* The SELECT was successfully coded. Set the return code to 0 + ** to indicate no errors. + */ + rc = 0; + + /* Control jumps to here if an error is encountered above, or upon + ** successful coding of the SELECT. + */ +select_end: + sqliteAggregateInfoReset(pParse); + return rc; +} diff --git a/src/libs/sqlite2/shell.c b/src/libs/sqlite2/shell.c new file mode 100644 index 00000000..89898ab4 --- /dev/null +++ b/src/libs/sqlite2/shell.c @@ -0,0 +1,1354 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement the "sqlite" command line +** utility for accessing SQLite databases. +** +** $Id: shell.c 875429 2008-10-24 12:20:41Z cgilles $ +*/ +#include +#include +#include +#include "sqlite.h" +#include + +#if !defined(_WIN32) && !defined(WIN32) && !defined(__MACOS__) +# include +# include +# include +# include +#endif + +#ifdef __MACOS__ +# include +# include +# include +# include +# include +# include +#endif + +#if defined(HAVE_READLINE) && HAVE_READLINE==1 +# include +# include +#else +# define readline(p) local_getline(p,stdin) +# define add_history(X) +# define read_history(X) +# define write_history(X) +# define stifle_history(X) +#endif + +/* Make sure isatty() has a prototype. +*/ +extern int isatty(); + +/* +** The following is the open SQLite database. We make a pointer +** to this database a static variable so that it can be accessed +** by the SIGINT handler to interrupt database processing. +*/ +static sqlite *db = 0; + +/* +** True if an interrupt (Control-C) has been received. +*/ +static int seenInterrupt = 0; + +/* +** This is the name of our program. It is set in main(), used +** in a number of other places, mostly for error messages. +*/ +static char *Argv0; + +/* +** Prompt strings. Initialized in main. Settable with +** .prompt main continue +*/ +static char mainPrompt[20]; /* First line prompt. default: "sqlite> "*/ +static char continuePrompt[20]; /* Continuation prompt. default: " ...> " */ + + +/* +** Determines if a string is a number of not. +*/ +extern int sqliteIsNumber(const char*); + +/* +** This routine reads a line of text from standard input, stores +** the text in memory obtained from malloc() and returns a pointer +** to the text. NULL is returned at end of file, or if malloc() +** fails. +** +** The interface is like "readline" but no command-line editing +** is done. +*/ +static char *local_getline(char *zPrompt, FILE *in){ + char *zLine; + int nLine; + int n; + int eol; + + if( zPrompt && *zPrompt ){ + printf("%s",zPrompt); + fflush(stdout); + } + nLine = 100; + zLine = malloc( nLine ); + if( zLine==0 ) return 0; + n = 0; + eol = 0; + while( !eol ){ + if( n+100>nLine ){ + nLine = nLine*2 + 100; + zLine = realloc(zLine, nLine); + if( zLine==0 ) return 0; + } + if( fgets(&zLine[n], nLine - n, in)==0 ){ + if( n==0 ){ + free(zLine); + return 0; + } + zLine[n] = 0; + eol = 1; + break; + } + while( zLine[n] ){ n++; } + if( n>0 && zLine[n-1]=='\n' ){ + n--; + zLine[n] = 0; + eol = 1; + } + } + zLine = realloc( zLine, n+1 ); + return zLine; +} + +/* +** Retrieve a single line of input text. "isatty" is true if text +** is coming from a terminal. In that case, we issue a prompt and +** attempt to use "readline" for command-line editing. If "isatty" +** is false, use "local_getline" instead of "readline" and issue no prompt. +** +** zPrior is a string of prior text retrieved. If not the empty +** string, then issue a continuation prompt. +*/ +static char *one_input_line(const char *zPrior, FILE *in){ + char *zPrompt; + char *zResult; + if( in!=0 ){ + return local_getline(0, in); + } + if( zPrior && zPrior[0] ){ + zPrompt = continuePrompt; + }else{ + zPrompt = mainPrompt; + } + zResult = readline(zPrompt); + if( zResult ) add_history(zResult); + return zResult; +} + +struct previous_mode_data { + int valid; /* Is there legit data in here? */ + int mode; + int showHeader; + int colWidth[100]; +}; +/* +** An pointer to an instance of this structure is passed from +** the main program to the callback. This is used to communicate +** state and mode information. +*/ +struct callback_data { + sqlite *db; /* The database */ + int echoOn; /* True to echo input commands */ + int cnt; /* Number of records displayed so far */ + FILE *out; /* Write results here */ + int mode; /* An output mode setting */ + int showHeader; /* True to show column names in List or Column mode */ + char *zDestTable; /* Name of destination table when MODE_Insert */ + char separator[20]; /* Separator character for MODE_List */ + int colWidth[100]; /* Requested width of each column when in column mode*/ + int actualWidth[100]; /* Actual width of each column */ + char nullvalue[20]; /* The text to print when a NULL comes back from + ** the database */ + struct previous_mode_data explainPrev; + /* Holds the mode information just before + ** .explain ON */ + char outfile[FILENAME_MAX]; /* Filename for *out */ + const char *zDbFilename; /* name of the database file */ + char *zKey; /* Encryption key */ +}; + +/* +** These are the allowed modes. +*/ +#define MODE_Line 0 /* One column per line. Blank line between records */ +#define MODE_Column 1 /* One record per line in neat columns */ +#define MODE_List 2 /* One record per line with a separator */ +#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */ +#define MODE_Html 4 /* Generate an XHTML table */ +#define MODE_Insert 5 /* Generate SQL "insert" statements */ +#define MODE_NUM_OF 6 /* The number of modes (not a mode itself) */ + +char *modeDescr[MODE_NUM_OF] = { + "line", + "column", + "list", + "semi", + "html", + "insert" +}; + +/* +** Number of elements in an array +*/ +#define ArraySize(X) (sizeof(X)/sizeof(X[0])) + +/* +** Output the given string as a quoted string using SQL quoting conventions. +*/ +static void output_quoted_string(FILE *out, const char *z){ + int i; + int nSingle = 0; + for(i=0; z[i]; i++){ + if( z[i]=='\'' ) nSingle++; + } + if( nSingle==0 ){ + fprintf(out,"'%s'",z); + }else{ + fprintf(out,"'"); + while( *z ){ + for(i=0; z[i] && z[i]!='\''; i++){} + if( i==0 ){ + fprintf(out,"''"); + z++; + }else if( z[i]=='\'' ){ + fprintf(out,"%.*s''",i,z); + z += i+1; + }else{ + fprintf(out,"%s",z); + break; + } + } + fprintf(out,"'"); + } +} + +/* +** Output the given string with characters that are special to +** HTML escaped. +*/ +static void output_html_string(FILE *out, const char *z){ + int i; + while( *z ){ + for(i=0; z[i] && z[i]!='<' && z[i]!='&'; i++){} + if( i>0 ){ + fprintf(out,"%.*s",i,z); + } + if( z[i]=='<' ){ + fprintf(out,"<"); + }else if( z[i]=='&' ){ + fprintf(out,"&"); + }else{ + break; + } + z += i + 1; + } +} + +/* +** This routine runs when the user presses Ctrl-C +*/ +static void interrupt_handler(int NotUsed){ + seenInterrupt = 1; + if( db ) sqlite_interrupt(db); +} + +/* +** This is the callback routine that the SQLite library +** invokes for each row of a query result. +*/ +static int callback(void *pArg, int nArg, char **azArg, char **azCol){ + int i; + struct callback_data *p = (struct callback_data*)pArg; + switch( p->mode ){ + case MODE_Line: { + int w = 5; + if( azArg==0 ) break; + for(i=0; iw ) w = len; + } + if( p->cnt++>0 ) fprintf(p->out,"\n"); + for(i=0; iout,"%*s = %s\n", w, azCol[i], + azArg[i] ? azArg[i] : p->nullvalue); + } + break; + } + case MODE_Column: { + if( p->cnt++==0 ){ + for(i=0; icolWidth) ){ + w = p->colWidth[i]; + }else{ + w = 0; + } + if( w<=0 ){ + w = strlen(azCol[i] ? azCol[i] : ""); + if( w<10 ) w = 10; + n = strlen(azArg && azArg[i] ? azArg[i] : p->nullvalue); + if( wactualWidth) ){ + p->actualWidth[i] = w; + } + if( p->showHeader ){ + fprintf(p->out,"%-*.*s%s",w,w,azCol[i], i==nArg-1 ? "\n": " "); + } + } + if( p->showHeader ){ + for(i=0; iactualWidth) ){ + w = p->actualWidth[i]; + }else{ + w = 10; + } + fprintf(p->out,"%-*.*s%s",w,w,"-----------------------------------" + "----------------------------------------------------------", + i==nArg-1 ? "\n": " "); + } + } + } + if( azArg==0 ) break; + for(i=0; iactualWidth) ){ + w = p->actualWidth[i]; + }else{ + w = 10; + } + fprintf(p->out,"%-*.*s%s",w,w, + azArg[i] ? azArg[i] : p->nullvalue, i==nArg-1 ? "\n": " "); + } + break; + } + case MODE_Semi: + case MODE_List: { + if( p->cnt++==0 && p->showHeader ){ + for(i=0; iout,"%s%s",azCol[i], i==nArg-1 ? "\n" : p->separator); + } + } + if( azArg==0 ) break; + for(i=0; inullvalue; + fprintf(p->out, "%s", z); + if( iout, "%s", p->separator); + }else if( p->mode==MODE_Semi ){ + fprintf(p->out, ";\n"); + }else{ + fprintf(p->out, "\n"); + } + } + break; + } + case MODE_Html: { + if( p->cnt++==0 && p->showHeader ){ + fprintf(p->out,"
    "); + for(i=0; iout,"",azCol[i]); + } + fprintf(p->out,"\n"); + } + if( azArg==0 ) break; + fprintf(p->out,""); + for(i=0; iout,"\n"); + } + fprintf(p->out,"\n"); + break; + } + case MODE_Insert: { + if( azArg==0 ) break; + fprintf(p->out,"INSERT INTO %s VALUES(",p->zDestTable); + for(i=0; i0 ? ",": ""; + if( azArg[i]==0 ){ + fprintf(p->out,"%sNULL",zSep); + }else if( sqliteIsNumber(azArg[i]) ){ + fprintf(p->out,"%s%s",zSep, azArg[i]); + }else{ + if( zSep[0] ) fprintf(p->out,"%s",zSep); + output_quoted_string(p->out, azArg[i]); + } + } + fprintf(p->out,");\n"); + break; + } + } + return 0; +} + +/* +** Set the destination table field of the callback_data structure to +** the name of the table given. Escape any quote characters in the +** table name. +*/ +static void set_table_name(struct callback_data *p, const char *zName){ + int i, n; + int needQuote; + char *z; + + if( p->zDestTable ){ + free(p->zDestTable); + p->zDestTable = 0; + } + if( zName==0 ) return; + needQuote = !isalpha(*zName) && *zName!='_'; + for(i=n=0; zName[i]; i++, n++){ + if( !isalnum(zName[i]) && zName[i]!='_' ){ + needQuote = 1; + if( zName[i]=='\'' ) n++; + } + } + if( needQuote ) n += 2; + z = p->zDestTable = malloc( n+1 ); + if( z==0 ){ + fprintf(stderr,"Out of memory!\n"); + exit(1); + } + n = 0; + if( needQuote ) z[n++] = '\''; + for(i=0; zName[i]; i++){ + z[n++] = zName[i]; + if( zName[i]=='\'' ) z[n++] = '\''; + } + if( needQuote ) z[n++] = '\''; + z[n] = 0; +} + +/* +** This is a different callback routine used for dumping the database. +** Each row received by this callback consists of a table name, +** the table type ("index" or "table") and SQL to create the table. +** This routine should print text sufficient to recreate the table. +*/ +static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){ + struct callback_data *p = (struct callback_data *)pArg; + if( nArg!=3 ) return 1; + fprintf(p->out, "%s;\n", azArg[2]); + if( strcmp(azArg[1],"table")==0 ){ + struct callback_data d2; + d2 = *p; + d2.mode = MODE_Insert; + d2.zDestTable = 0; + set_table_name(&d2, azArg[0]); + sqlite_exec_printf(p->db, + "SELECT * FROM '%q'", + callback, &d2, 0, azArg[0] + ); + set_table_name(&d2, 0); + } + return 0; +} + +/* +** Text of a help message +*/ +static char zHelp[] = + ".databases List names and files of attached databases\n" + ".dump ?TABLE? ... Dump the database in a text format\n" + ".echo ON|OFF Turn command echo on or off\n" + ".exit Exit this program\n" + ".explain ON|OFF Turn output mode suitable for EXPLAIN on or off.\n" + ".header(s) ON|OFF Turn display of headers on or off\n" + ".help Show this message\n" + ".indices TABLE Show names of all indices on TABLE\n" + ".mode MODE Set mode to one of \"line(s)\", \"column(s)\", \n" + " \"insert\", \"list\", or \"html\"\n" + ".mode insert TABLE Generate SQL insert statements for TABLE\n" + ".nullvalue STRING Print STRING instead of nothing for NULL data\n" + ".output FILENAME Send output to FILENAME\n" + ".output stdout Send output to the screen\n" + ".prompt MAIN CONTINUE Replace the standard prompts\n" + ".quit Exit this program\n" + ".read FILENAME Execute SQL in FILENAME\n" +#ifdef SQLITE_HAS_CODEC + ".rekey OLD NEW NEW Change the encryption key\n" +#endif + ".schema ?TABLE? Show the CREATE statements\n" + ".separator STRING Change separator string for \"list\" mode\n" + ".show Show the current values for various settings\n" + ".tables ?PATTERN? List names of tables matching a pattern\n" + ".timeout MS Try opening locked tables for MS milliseconds\n" + ".width NUM NUM ... Set column widths for \"column\" mode\n" +; + +/* Forward reference */ +static void process_input(struct callback_data *p, FILE *in); + +/* +** Make sure the database is open. If it is not, then open it. If +** the database fails to open, print an error message and exit. +*/ +static void open_db(struct callback_data *p){ + if( p->db==0 ){ + char *zErrMsg = 0; +#ifdef SQLITE_HAS_CODEC + int n = p->zKey ? strlen(p->zKey) : 0; + db = p->db = sqlite_open_encrypted(p->zDbFilename, p->zKey, n, 0, &zErrMsg); +#else + db = p->db = sqlite_open(p->zDbFilename, 0, &zErrMsg); +#endif + if( p->db==0 ){ + if( zErrMsg ){ + fprintf(stderr,"Unable to open database \"%s\": %s\n", + p->zDbFilename, zErrMsg); + }else{ + fprintf(stderr,"Unable to open database %s\n", p->zDbFilename); + } + exit(1); + } + } +} + +/* +** If an input line begins with "." then invoke this routine to +** process that line. +** +** Return 1 to exit and 0 to continue. +*/ +static int do_meta_command(char *zLine, struct callback_data *p){ + int i = 1; + int nArg = 0; + int n, c; + int rc = 0; + char *azArg[50]; + + /* Parse the input line into tokens. + */ + while( zLine[i] && nArg1 && strncmp(azArg[0], "databases", n)==0 ){ + struct callback_data data; + char *zErrMsg = 0; + open_db(p); + memcpy(&data, p, sizeof(data)); + data.showHeader = 1; + data.mode = MODE_Column; + data.colWidth[0] = 3; + data.colWidth[1] = 15; + data.colWidth[2] = 58; + sqlite_exec(p->db, "PRAGMA database_list; ", callback, &data, &zErrMsg); + if( zErrMsg ){ + fprintf(stderr,"Error: %s\n", zErrMsg); + sqlite_freemem(zErrMsg); + } + }else + + if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ + char *zErrMsg = 0; + open_db(p); + fprintf(p->out, "BEGIN TRANSACTION;\n"); + if( nArg==1 ){ + sqlite_exec(p->db, + "SELECT name, type, sql FROM sqlite_master " + "WHERE type!='meta' AND sql NOT NULL " + "ORDER BY substr(type,2,1), rowid", + dump_callback, p, &zErrMsg + ); + }else{ + int i; + for(i=1; idb, + "SELECT name, type, sql FROM sqlite_master " + "WHERE tbl_name LIKE '%q' AND type!='meta' AND sql NOT NULL " + "ORDER BY substr(type,2,1), rowid", + dump_callback, p, &zErrMsg, azArg[i] + ); + } + } + if( zErrMsg ){ + fprintf(stderr,"Error: %s\n", zErrMsg); + sqlite_freemem(zErrMsg); + }else{ + fprintf(p->out, "COMMIT;\n"); + } + }else + + if( c=='e' && strncmp(azArg[0], "echo", n)==0 && nArg>1 ){ + int j; + char *z = azArg[1]; + int val = atoi(azArg[1]); + for(j=0; z[j]; j++){ + if( isupper(z[j]) ) z[j] = tolower(z[j]); + } + if( strcmp(z,"on")==0 ){ + val = 1; + }else if( strcmp(z,"yes")==0 ){ + val = 1; + } + p->echoOn = val; + }else + + if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ + rc = 1; + }else + + if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){ + int j; + char *z = nArg>=2 ? azArg[1] : "1"; + int val = atoi(z); + for(j=0; z[j]; j++){ + if( isupper(z[j]) ) z[j] = tolower(z[j]); + } + if( strcmp(z,"on")==0 ){ + val = 1; + }else if( strcmp(z,"yes")==0 ){ + val = 1; + } + if(val == 1) { + if(!p->explainPrev.valid) { + p->explainPrev.valid = 1; + p->explainPrev.mode = p->mode; + p->explainPrev.showHeader = p->showHeader; + memcpy(p->explainPrev.colWidth,p->colWidth,sizeof(p->colWidth)); + } + /* We could put this code under the !p->explainValid + ** condition so that it does not execute if we are already in + ** explain mode. However, always executing it allows us an easy + ** was to reset to explain mode in case the user previously + ** did an .explain followed by a .width, .mode or .header + ** command. + */ + p->mode = MODE_Column; + p->showHeader = 1; + memset(p->colWidth,0,ArraySize(p->colWidth)); + p->colWidth[0] = 4; + p->colWidth[1] = 12; + p->colWidth[2] = 10; + p->colWidth[3] = 10; + p->colWidth[4] = 35; + }else if (p->explainPrev.valid) { + p->explainPrev.valid = 0; + p->mode = p->explainPrev.mode; + p->showHeader = p->explainPrev.showHeader; + memcpy(p->colWidth,p->explainPrev.colWidth,sizeof(p->colWidth)); + } + }else + + if( c=='h' && (strncmp(azArg[0], "header", n)==0 + || + strncmp(azArg[0], "headers", n)==0 )&& nArg>1 ){ + int j; + char *z = azArg[1]; + int val = atoi(azArg[1]); + for(j=0; z[j]; j++){ + if( isupper(z[j]) ) z[j] = tolower(z[j]); + } + if( strcmp(z,"on")==0 ){ + val = 1; + }else if( strcmp(z,"yes")==0 ){ + val = 1; + } + p->showHeader = val; + }else + + if( c=='h' && strncmp(azArg[0], "help", n)==0 ){ + fprintf(stderr, "%s", zHelp); + }else + + if( c=='i' && strncmp(azArg[0], "indices", n)==0 && nArg>1 ){ + struct callback_data data; + char *zErrMsg = 0; + open_db(p); + memcpy(&data, p, sizeof(data)); + data.showHeader = 0; + data.mode = MODE_List; + sqlite_exec_printf(p->db, + "SELECT name FROM sqlite_master " + "WHERE type='index' AND tbl_name LIKE '%q' " + "UNION ALL " + "SELECT name FROM sqlite_temp_master " + "WHERE type='index' AND tbl_name LIKE '%q' " + "ORDER BY 1", + callback, &data, &zErrMsg, azArg[1], azArg[1] + ); + if( zErrMsg ){ + fprintf(stderr,"Error: %s\n", zErrMsg); + sqlite_freemem(zErrMsg); + } + }else + + if( c=='m' && strncmp(azArg[0], "mode", n)==0 && nArg>=2 ){ + int n2 = strlen(azArg[1]); + if( strncmp(azArg[1],"line",n2)==0 + || + strncmp(azArg[1],"lines",n2)==0 ){ + p->mode = MODE_Line; + }else if( strncmp(azArg[1],"column",n2)==0 + || + strncmp(azArg[1],"columns",n2)==0 ){ + p->mode = MODE_Column; + }else if( strncmp(azArg[1],"list",n2)==0 ){ + p->mode = MODE_List; + }else if( strncmp(azArg[1],"html",n2)==0 ){ + p->mode = MODE_Html; + }else if( strncmp(azArg[1],"insert",n2)==0 ){ + p->mode = MODE_Insert; + if( nArg>=3 ){ + set_table_name(p, azArg[2]); + }else{ + set_table_name(p, "table"); + } + }else { + fprintf(stderr,"mode should be on of: column html insert line list\n"); + } + }else + + if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 && nArg==2 ) { + sprintf(p->nullvalue, "%.*s", (int)ArraySize(p->nullvalue)-1, azArg[1]); + }else + + if( c=='o' && strncmp(azArg[0], "output", n)==0 && nArg==2 ){ + if( p->out!=stdout ){ + fclose(p->out); + } + if( strcmp(azArg[1],"stdout")==0 ){ + p->out = stdout; + strcpy(p->outfile,"stdout"); + }else{ + p->out = fopen(azArg[1], "wb"); + if( p->out==0 ){ + fprintf(stderr,"can't write to \"%s\"\n", azArg[1]); + p->out = stdout; + } else { + strcpy(p->outfile,azArg[1]); + } + } + }else + + if( c=='p' && strncmp(azArg[0], "prompt", n)==0 && (nArg==2 || nArg==3)){ + if( nArg >= 2) { + strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1); + } + if( nArg >= 3) { + strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1); + } + }else + + if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){ + rc = 1; + }else + + if( c=='r' && strncmp(azArg[0], "read", n)==0 && nArg==2 ){ + FILE *alt = fopen(azArg[1], "rb"); + if( alt==0 ){ + fprintf(stderr,"can't open \"%s\"\n", azArg[1]); + }else{ + process_input(p, alt); + fclose(alt); + } + }else + +#ifdef SQLITE_HAS_CODEC + if( c=='r' && strncmp(azArg[0],"rekey", n)==0 && nArg==4 ){ + char *zOld = p->zKey; + if( zOld==0 ) zOld = ""; + if( strcmp(azArg[1],zOld) ){ + fprintf(stderr,"old key is incorrect\n"); + }else if( strcmp(azArg[2], azArg[3]) ){ + fprintf(stderr,"2nd copy of new key does not match the 1st\n"); + }else{ + sqlite_freemem(p->zKey); + p->zKey = sqlite_mprintf("%s", azArg[2]); + sqlite_rekey(p->db, p->zKey, strlen(p->zKey)); + } + }else +#endif + + if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){ + struct callback_data data; + char *zErrMsg = 0; + open_db(p); + memcpy(&data, p, sizeof(data)); + data.showHeader = 0; + data.mode = MODE_Semi; + if( nArg>1 ){ + extern int sqliteStrICmp(const char*,const char*); + if( sqliteStrICmp(azArg[1],"sqlite_master")==0 ){ + char *new_argv[2], *new_colv[2]; + new_argv[0] = "CREATE TABLE sqlite_master (\n" + " type text,\n" + " name text,\n" + " tbl_name text,\n" + " rootpage integer,\n" + " sql text\n" + ")"; + new_argv[1] = 0; + new_colv[0] = "sql"; + new_colv[1] = 0; + callback(&data, 1, new_argv, new_colv); + }else if( sqliteStrICmp(azArg[1],"sqlite_temp_master")==0 ){ + char *new_argv[2], *new_colv[2]; + new_argv[0] = "CREATE TEMP TABLE sqlite_temp_master (\n" + " type text,\n" + " name text,\n" + " tbl_name text,\n" + " rootpage integer,\n" + " sql text\n" + ")"; + new_argv[1] = 0; + new_colv[0] = "sql"; + new_colv[1] = 0; + callback(&data, 1, new_argv, new_colv); + }else{ + sqlite_exec_printf(p->db, + "SELECT sql FROM " + " (SELECT * FROM sqlite_master UNION ALL" + " SELECT * FROM sqlite_temp_master) " + "WHERE tbl_name LIKE '%q' AND type!='meta' AND sql NOTNULL " + "ORDER BY substr(type,2,1), name", + callback, &data, &zErrMsg, azArg[1]); + } + }else{ + sqlite_exec(p->db, + "SELECT sql FROM " + " (SELECT * FROM sqlite_master UNION ALL" + " SELECT * FROM sqlite_temp_master) " + "WHERE type!='meta' AND sql NOTNULL " + "ORDER BY substr(type,2,1), name", + callback, &data, &zErrMsg + ); + } + if( zErrMsg ){ + fprintf(stderr,"Error: %s\n", zErrMsg); + sqlite_freemem(zErrMsg); + } + }else + + if( c=='s' && strncmp(azArg[0], "separator", n)==0 && nArg==2 ){ + sprintf(p->separator, "%.*s", (int)ArraySize(p->separator)-1, azArg[1]); + }else + + if( c=='s' && strncmp(azArg[0], "show", n)==0){ + int i; + fprintf(p->out,"%9.9s: %s\n","echo", p->echoOn ? "on" : "off"); + fprintf(p->out,"%9.9s: %s\n","explain", p->explainPrev.valid ? "on" :"off"); + fprintf(p->out,"%9.9s: %s\n","headers", p->showHeader ? "on" : "off"); + fprintf(p->out,"%9.9s: %s\n","mode", modeDescr[p->mode]); + fprintf(p->out,"%9.9s: %s\n","nullvalue", p->nullvalue); + fprintf(p->out,"%9.9s: %s\n","output", + strlen(p->outfile) ? p->outfile : "stdout"); + fprintf(p->out,"%9.9s: %s\n","separator", p->separator); + fprintf(p->out,"%9.9s: ","width"); + for (i=0;i<(int)ArraySize(p->colWidth) && p->colWidth[i] != 0;i++) { + fprintf(p->out,"%d ",p->colWidth[i]); + } + fprintf(p->out,"\n\n"); + }else + + if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 ){ + char **azResult; + int nRow, rc; + char *zErrMsg; + open_db(p); + if( nArg==1 ){ + rc = sqlite_get_table(p->db, + "SELECT name FROM sqlite_master " + "WHERE type IN ('table','view') " + "UNION ALL " + "SELECT name FROM sqlite_temp_master " + "WHERE type IN ('table','view') " + "ORDER BY 1", + &azResult, &nRow, 0, &zErrMsg + ); + }else{ + rc = sqlite_get_table_printf(p->db, + "SELECT name FROM sqlite_master " + "WHERE type IN ('table','view') AND name LIKE '%%%q%%' " + "UNION ALL " + "SELECT name FROM sqlite_temp_master " + "WHERE type IN ('table','view') AND name LIKE '%%%q%%' " + "ORDER BY 1", + &azResult, &nRow, 0, &zErrMsg, azArg[1], azArg[1] + ); + } + if( zErrMsg ){ + fprintf(stderr,"Error: %s\n", zErrMsg); + sqlite_freemem(zErrMsg); + } + if( rc==SQLITE_OK ){ + int len, maxlen = 0; + int i, j; + int nPrintCol, nPrintRow; + for(i=1; i<=nRow; i++){ + if( azResult[i]==0 ) continue; + len = strlen(azResult[i]); + if( len>maxlen ) maxlen = len; + } + nPrintCol = 80/(maxlen+2); + if( nPrintCol<1 ) nPrintCol = 1; + nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; + for(i=0; i1 && strncmp(azArg[0], "timeout", n)==0 && nArg>=2 ){ + open_db(p); + sqlite_busy_timeout(p->db, atoi(azArg[1])); + }else + + if( c=='w' && strncmp(azArg[0], "width", n)==0 ){ + int j; + for(j=1; jcolWidth); j++){ + p->colWidth[j-1] = atoi(azArg[j]); + } + }else + + { + fprintf(stderr, "unknown command or invalid arguments: " + " \"%s\". Enter \".help\" for help\n", azArg[0]); + } + + return rc; +} + +/* +** Return TRUE if the last non-whitespace character in z[] is a semicolon. +** z[] is N characters long. +*/ +static int _ends_with_semicolon(const char *z, int N){ + while( N>0 && isspace(z[N-1]) ){ N--; } + return N>0 && z[N-1]==';'; +} + +/* +** Test to see if a line consists entirely of whitespace. +*/ +static int _all_whitespace(const char *z){ + for(; *z; z++){ + if( isspace(*z) ) continue; + if( *z=='/' && z[1]=='*' ){ + z += 2; + while( *z && (*z!='*' || z[1]!='/') ){ z++; } + if( *z==0 ) return 0; + z++; + continue; + } + if( *z=='-' && z[1]=='-' ){ + z += 2; + while( *z && *z!='\n' ){ z++; } + if( *z==0 ) return 1; + continue; + } + return 0; + } + return 1; +} + +/* +** Return TRUE if the line typed in is an SQL command terminator other +** than a semi-colon. The SQL Server style "go" command is understood +** as is the Oracle "/". +*/ +static int _is_command_terminator(const char *zLine){ + extern int sqliteStrNICmp(const char*,const char*,int); + while( isspace(*zLine) ){ zLine++; }; + if( zLine[0]=='/' && _all_whitespace(&zLine[1]) ) return 1; /* Oracle */ + if( sqliteStrNICmp(zLine,"go",2)==0 && _all_whitespace(&zLine[2]) ){ + return 1; /* SQL Server */ + } + return 0; +} + +/* +** Read input from *in and process it. If *in==0 then input +** is interactive - the user is typing it it. Otherwise, input +** is coming from a file or device. A prompt is issued and history +** is saved only if input is interactive. An interrupt signal will +** cause this routine to exit immediately, unless input is interactive. +*/ +static void process_input(struct callback_data *p, FILE *in){ + char *zLine; + char *zSql = 0; + int nSql = 0; + char *zErrMsg; + int rc; + while( fflush(p->out), (zLine = one_input_line(zSql, in))!=0 ){ + if( seenInterrupt ){ + if( in!=0 ) break; + seenInterrupt = 0; + } + if( p->echoOn ) printf("%s\n", zLine); + if( (zSql==0 || zSql[0]==0) && _all_whitespace(zLine) ) continue; + if( zLine && zLine[0]=='.' && nSql==0 ){ + int rc = do_meta_command(zLine, p); + free(zLine); + if( rc ) break; + continue; + } + if( _is_command_terminator(zLine) ){ + strcpy(zLine,";"); + } + if( zSql==0 ){ + int i; + for(i=0; zLine[i] && isspace(zLine[i]); i++){} + if( zLine[i]!=0 ){ + nSql = strlen(zLine); + zSql = malloc( nSql+1 ); + strcpy(zSql, zLine); + } + }else{ + int len = strlen(zLine); + zSql = realloc( zSql, nSql + len + 2 ); + if( zSql==0 ){ + fprintf(stderr,"%s: out of memory!\n", Argv0); + exit(1); + } + strcpy(&zSql[nSql++], "\n"); + strcpy(&zSql[nSql], zLine); + nSql += len; + } + free(zLine); + if( zSql && _ends_with_semicolon(zSql, nSql) && sqlite_complete(zSql) ){ + p->cnt = 0; + open_db(p); + rc = sqlite_exec(p->db, zSql, callback, p, &zErrMsg); + if( rc || zErrMsg ){ + if( in!=0 && !p->echoOn ) printf("%s\n",zSql); + if( zErrMsg!=0 ){ + printf("SQL error: %s\n", zErrMsg); + sqlite_freemem(zErrMsg); + zErrMsg = 0; + }else{ + printf("SQL error: %s\n", sqlite_error_string(rc)); + } + } + free(zSql); + zSql = 0; + nSql = 0; + } + } + if( zSql ){ + if( !_all_whitespace(zSql) ) printf("Incomplete SQL: %s\n", zSql); + free(zSql); + } +} + +/* +** Return a pathname which is the user's home directory. A +** 0 return indicates an error of some kind. Space to hold the +** resulting string is obtained from malloc(). The calling +** function should free the result. +*/ +static char *find_home_dir(void){ + char *home_dir = NULL; + +#if !defined(_WIN32) && !defined(WIN32) && !defined(__MACOS__) + struct passwd *pwent; + uid_t uid = getuid(); + if( (pwent=getpwuid(uid)) != NULL) { + home_dir = pwent->pw_dir; + } +#endif + +#ifdef __MACOS__ + char home_path[_MAX_PATH+1]; + home_dir = getcwd(home_path, _MAX_PATH); +#endif + + if (!home_dir) { + home_dir = getenv("HOME"); + if (!home_dir) { + home_dir = getenv("HOMEPATH"); /* Windows? */ + } + } + +#if defined(_WIN32) || defined(WIN32) + if (!home_dir) { + home_dir = "c:"; + } +#endif + + if( home_dir ){ + char *z = malloc( strlen(home_dir)+1 ); + if( z ) strcpy(z, home_dir); + home_dir = z; + } + + return home_dir; +} + +/* +** Read input from the file given by sqliterc_override. Or if that +** parameter is NULL, take input from ~/.sqliterc +*/ +static void process_sqliterc( + struct callback_data *p, /* Configuration data */ + const char *sqliterc_override /* Name of config file. NULL to use default */ +){ + char *home_dir = NULL; + const char *sqliterc = sqliterc_override; + char *zBuf; + FILE *in = NULL; + + if (sqliterc == NULL) { + home_dir = find_home_dir(); + if( home_dir==0 ){ + fprintf(stderr,"%s: cannot locate your home directory!\n", Argv0); + return; + } + zBuf = malloc(strlen(home_dir) + 15); + if( zBuf==0 ){ + fprintf(stderr,"%s: out of memory!\n", Argv0); + exit(1); + } + sprintf(zBuf,"%s/.sqliterc",home_dir); + free(home_dir); + sqliterc = (const char*)zBuf; + } + in = fopen(sqliterc,"rb"); + if( in ){ + if( isatty(fileno(stdout)) ){ + printf("Loading resources from %s\n",sqliterc); + } + process_input(p,in); + fclose(in); + } + return; +} + +/* +** Show available command line options +*/ +static const char zOptions[] = + " -init filename read/process named file\n" + " -echo print commands before execution\n" + " -[no]header turn headers on or off\n" + " -column set output mode to 'column'\n" + " -html set output mode to HTML\n" +#ifdef SQLITE_HAS_CODEC + " -key KEY encryption key\n" +#endif + " -line set output mode to 'line'\n" + " -list set output mode to 'list'\n" + " -separator 'x' set output field separator (|)\n" + " -nullvalue 'text' set text string for NULL values\n" + " -version show SQLite version\n" + " -help show this text, also show dot-commands\n" +; +static void usage(int showDetail){ + fprintf(stderr, "Usage: %s [OPTIONS] FILENAME [SQL]\n", Argv0); + if( showDetail ){ + fprintf(stderr, "Options are:\n%s", zOptions); + }else{ + fprintf(stderr, "Use the -help option for additional information\n"); + } + exit(1); +} + +/* +** Initialize the state information in data +*/ +void main_init(struct callback_data *data) { + memset(data, 0, sizeof(*data)); + data->mode = MODE_List; + strcpy(data->separator,"|"); + data->showHeader = 0; + strcpy(mainPrompt,"sqlite> "); + strcpy(continuePrompt," ...> "); +} + +int main(int argc, char **argv){ + char *zErrMsg = 0; + struct callback_data data; + const char *zInitFile = 0; + char *zFirstCmd = 0; + int i; + extern int sqliteOsFileExists(const char*); + +#ifdef __MACOS__ + argc = ccommand(&argv); +#endif + + Argv0 = argv[0]; + main_init(&data); + + /* Make sure we have a valid signal handler early, before anything + ** else is done. + */ +#ifdef SIGINT + signal(SIGINT, interrupt_handler); +#endif + + /* Do an initial pass through the command-line argument to locate + ** the name of the database file, the name of the initialization file, + ** and the first command to execute. + */ + for(i=1; i /* Needed for the definition of va_list */ + +/* +** Make sure we can call this stuff from C++. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +/* +** The version of the SQLite library. +*/ +#define SQLITE_VERSION "2.8.14" + +/* +** The version string is also compiled into the library so that a program +** can check to make sure that the lib*.a file and the *.h file are from +** the same version. +*/ +extern const char sqlite_version[]; + +/* +** The SQLITE_UTF8 macro is defined if the library expects to see +** UTF-8 encoded data. The SQLITE_ISO8859 macro is defined if the +** iso8859 encoded should be used. +*/ +/* #define SQLITE_ISO8859 1 */ + +/* DigiKam customizations */ +#define SQLITE_UTF8 1 +#define THREADSAFE 1 + +/* +** The following constant holds one of two strings, "UTF-8" or "iso8859", +** depending on which character encoding the SQLite library expects to +** see. The character encoding makes a difference for the LIKE and GLOB +** operators and for the LENGTH() and SUBSTR() functions. +*/ +extern const char sqlite_encoding[]; + +/* +** Each open sqlite database is represented by an instance of the +** following opaque structure. +*/ +typedef struct sqlite sqlite; + +/* +** A function to open a new sqlite database. +** +** If the database does not exist and mode indicates write +** permission, then a new database is created. If the database +** does not exist and mode does not indicate write permission, +** then the open fails, an error message generated (if errmsg!=0) +** and the function returns 0. +** +** If mode does not indicates user write permission, then the +** database is opened read-only. +** +** The Truth: As currently implemented, all databases are opened +** for writing all the time. Maybe someday we will provide the +** ability to open a database readonly. The mode parameters is +** provided in anticipation of that enhancement. +*/ +sqlite *sqlite_open(const char *filename, int mode, char **errmsg); + +/* +** A function to close the database. +** +** Call this function with a pointer to a structure that was previously +** returned from sqlite_open() and the corresponding database will by closed. +*/ +void sqlite_close(sqlite *); + +/* +** The type for a callback function. +*/ +typedef int (*sqlite_callback)(void*,int,char**, char**); + +/* +** A function to executes one or more statements of SQL. +** +** If one or more of the SQL statements are queries, then +** the callback function specified by the 3rd parameter is +** invoked once for each row of the query result. This callback +** should normally return 0. If the callback returns a non-zero +** value then the query is aborted, all subsequent SQL statements +** are skipped and the sqlite_exec() function returns the SQLITE_ABORT. +** +** The 4th parameter is an arbitrary pointer that is passed +** to the callback function as its first parameter. +** +** The 2nd parameter to the callback function is the number of +** columns in the query result. The 3rd parameter to the callback +** is an array of strings holding the values for each column. +** The 4th parameter to the callback is an array of strings holding +** the names of each column. +** +** The callback function may be NULL, even for queries. A NULL +** callback is not an error. It just means that no callback +** will be invoked. +** +** If an error occurs while parsing or evaluating the SQL (but +** not while executing the callback) then an appropriate error +** message is written into memory obtained from malloc() and +** *errmsg is made to point to that message. The calling function +** is responsible for freeing the memory that holds the error +** message. Use sqlite_freemem() for this. If errmsg==NULL, +** then no error message is ever written. +** +** The return value is is SQLITE_OK if there are no errors and +** some other return code if there is an error. The particular +** return value depends on the type of error. +** +** If the query could not be executed because a database file is +** locked or busy, then this function returns SQLITE_BUSY. (This +** behavior can be modified somewhat using the sqlite_busy_handler() +** and sqlite_busy_timeout() functions below.) +*/ +int sqlite_exec( + sqlite*, /* An open database */ + const char *sql, /* SQL to be executed */ + sqlite_callback, /* Callback function */ + void *, /* 1st argument to callback function */ + char **errmsg /* Error msg written here */ +); + +/* +** Return values for sqlite_exec() and sqlite_step() +*/ +#define SQLITE_OK 0 /* Successful result */ +#define SQLITE_ERROR 1 /* SQL error or missing database */ +#define SQLITE_INTERNAL 2 /* An internal logic error in SQLite */ +#define SQLITE_PERM 3 /* Access permission denied */ +#define SQLITE_ABORT 4 /* Callback routine requested an abort */ +#define SQLITE_BUSY 5 /* The database file is locked */ +#define SQLITE_LOCKED 6 /* A table in the database is locked */ +#define SQLITE_NOMEM 7 /* A malloc() failed */ +#define SQLITE_READONLY 8 /* Attempt to write a readonly database */ +#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite_interrupt() */ +#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ +#define SQLITE_CORRUPT 11 /* The database disk image is malformed */ +#define SQLITE_NOTFOUND 12 /* (Internal Only) Table or record not found */ +#define SQLITE_FULL 13 /* Insertion failed because database is full */ +#define SQLITE_CANTOPEN 14 /* Unable to open the database file */ +#define SQLITE_PROTOCOL 15 /* Database lock protocol error */ +#define SQLITE_EMPTY 16 /* (Internal Only) Database table is empty */ +#define SQLITE_SCHEMA 17 /* The database schema changed */ +#define SQLITE_TOOBIG 18 /* Too much data for one row of a table */ +#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ +#define SQLITE_MISMATCH 20 /* Data type mismatch */ +#define SQLITE_MISUSE 21 /* Library used incorrectly */ +#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ +#define SQLITE_AUTH 23 /* Authorization denied */ +#define SQLITE_FORMAT 24 /* Auxiliary database format error */ +#define SQLITE_RANGE 25 /* 2nd parameter to sqlite_bind out of range */ +#define SQLITE_NOTADB 26 /* File opened that is not a database file */ +#define SQLITE_ROW 100 /* sqlite_step() has another row ready */ +#define SQLITE_DONE 101 /* sqlite_step() has finished executing */ + +/* +** Each entry in an SQLite table has a unique integer key. (The key is +** the value of the INTEGER PRIMARY KEY column if there is such a column, +** otherwise the key is generated at random. The unique key is always +** available as the ROWID, OID, or _ROWID_ column.) The following routine +** returns the integer key of the most recent insert in the database. +** +** This function is similar to the mysql_insert_id() function from MySQL. +*/ +int sqlite_last_insert_rowid(sqlite*); + +/* +** This function returns the number of database rows that were changed +** (or inserted or deleted) by the most recent called sqlite_exec(). +** +** All changes are counted, even if they were later undone by a +** ROLLBACK or ABORT. Except, changes associated with creating and +** dropping tables are not counted. +** +** If a callback invokes sqlite_exec() recursively, then the changes +** in the inner, recursive call are counted together with the changes +** in the outer call. +** +** SQLite implements the command "DELETE FROM table" without a WHERE clause +** by dropping and recreating the table. (This is much faster than going +** through and deleting individual elements form the table.) Because of +** this optimization, the change count for "DELETE FROM table" will be +** zero regardless of the number of elements that were originally in the +** table. To get an accurate count of the number of rows deleted, use +** "DELETE FROM table WHERE 1" instead. +*/ +int sqlite_changes(sqlite*); + +/* +** This function returns the number of database rows that were changed +** by the last INSERT, UPDATE, or DELETE statement executed by sqlite_exec(), +** or by the last VM to run to completion. The change count is not updated +** by SQL statements other than INSERT, UPDATE or DELETE. +** +** Changes are counted, even if they are later undone by a ROLLBACK or +** ABORT. Changes associated with trigger programs that execute as a +** result of the INSERT, UPDATE, or DELETE statement are not counted. +** +** If a callback invokes sqlite_exec() recursively, then the changes +** in the inner, recursive call are counted together with the changes +** in the outer call. +** +** SQLite implements the command "DELETE FROM table" without a WHERE clause +** by dropping and recreating the table. (This is much faster than going +** through and deleting individual elements form the table.) Because of +** this optimization, the change count for "DELETE FROM table" will be +** zero regardless of the number of elements that were originally in the +** table. To get an accurate count of the number of rows deleted, use +** "DELETE FROM table WHERE 1" instead. +** +******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ****** +*/ +int sqlite_last_statement_changes(sqlite*); + +/* If the parameter to this routine is one of the return value constants +** defined above, then this routine returns a constant text string which +** describes (in English) the meaning of the return value. +*/ +const char *sqlite_error_string(int); +#define sqliteErrStr sqlite_error_string /* Legacy. Do not use in new code. */ + +/* This function causes any pending database operation to abort and +** return at its earliest opportunity. This routine is typically +** called in response to a user action such as pressing "Cancel" +** or Ctrl-C where the user wants a long query operation to halt +** immediately. +*/ +void sqlite_interrupt(sqlite*); + + +/* This function returns true if the given input string comprises +** one or more complete SQL statements. +** +** The algorithm is simple. If the last token other than spaces +** and comments is a semicolon, then return true. otherwise return +** false. +*/ +int sqlite_complete(const char *sql); + +/* +** This routine identifies a callback function that is invoked +** whenever an attempt is made to open a database table that is +** currently locked by another process or thread. If the busy callback +** is NULL, then sqlite_exec() returns SQLITE_BUSY immediately if +** it finds a locked table. If the busy callback is not NULL, then +** sqlite_exec() invokes the callback with three arguments. The +** second argument is the name of the locked table and the third +** argument is the number of times the table has been busy. If the +** busy callback returns 0, then sqlite_exec() immediately returns +** SQLITE_BUSY. If the callback returns non-zero, then sqlite_exec() +** tries to open the table again and the cycle repeats. +** +** The default busy callback is NULL. +** +** Sqlite is re-entrant, so the busy handler may start a new query. +** (It is not clear why anyone would every want to do this, but it +** is allowed, in theory.) But the busy handler may not close the +** database. Closing the database from a busy handler will delete +** data structures out from under the executing query and will +** probably result in a coredump. +*/ +void sqlite_busy_handler(sqlite*, int(*)(void*,const char*,int), void*); + +/* +** This routine sets a busy handler that sleeps for a while when a +** table is locked. The handler will sleep multiple times until +** at least "ms" milliseconds of sleeping have been done. After +** "ms" milliseconds of sleeping, the handler returns 0 which +** causes sqlite_exec() to return SQLITE_BUSY. +** +** Calling this routine with an argument less than or equal to zero +** turns off all busy handlers. +*/ +void sqlite_busy_timeout(sqlite*, int ms); + +/* +** This next routine is really just a wrapper around sqlite_exec(). +** Instead of invoking a user-supplied callback for each row of the +** result, this routine remembers each row of the result in memory +** obtained from malloc(), then returns all of the result after the +** query has finished. +** +** As an example, suppose the query result where this table: +** +** Name | Age +** ----------------------- +** Alice | 43 +** Bob | 28 +** Cindy | 21 +** +** If the 3rd argument were &azResult then after the function returns +** azResult will contain the following data: +** +** azResult[0] = "Name"; +** azResult[1] = "Age"; +** azResult[2] = "Alice"; +** azResult[3] = "43"; +** azResult[4] = "Bob"; +** azResult[5] = "28"; +** azResult[6] = "Cindy"; +** azResult[7] = "21"; +** +** Notice that there is an extra row of data containing the column +** headers. But the *nrow return value is still 3. *ncolumn is +** set to 2. In general, the number of values inserted into azResult +** will be ((*nrow) + 1)*(*ncolumn). +** +** After the calling function has finished using the result, it should +** pass the result data pointer to sqlite_free_table() in order to +** release the memory that was malloc-ed. Because of the way the +** malloc() happens, the calling function must not try to call +** malloc() directly. Only sqlite_free_table() is able to release +** the memory properly and safely. +** +** The return value of this routine is the same as from sqlite_exec(). +*/ +int sqlite_get_table( + sqlite*, /* An open database */ + const char *sql, /* SQL to be executed */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncolumn, /* Number of result columns written here */ + char **errmsg /* Error msg written here */ +); + +/* +** Call this routine to free the memory that sqlite_get_table() allocated. +*/ +void sqlite_free_table(char **result); + +/* +** The following routines are wrappers around sqlite_exec() and +** sqlite_get_table(). The only difference between the routines that +** follow and the originals is that the second argument to the +** routines that follow is really a printf()-style format +** string describing the SQL to be executed. Arguments to the format +** string appear at the end of the argument list. +** +** All of the usual printf formatting options apply. In addition, there +** is a "%q" option. %q works like %s in that it substitutes a null-terminated +** string from the argument list. But %q also doubles every '\'' character. +** %q is designed for use inside a string literal. By doubling each '\'' +** character it escapes that character and allows it to be inserted into +** the string. +** +** For example, so some string variable contains text as follows: +** +** char *zText = "It's a happy day!"; +** +** We can use this text in an SQL statement as follows: +** +** sqlite_exec_printf(db, "INSERT INTO table VALUES('%q')", +** callback1, 0, 0, zText); +** +** Because the %q format string is used, the '\'' character in zText +** is escaped and the SQL generated is as follows: +** +** INSERT INTO table1 VALUES('It''s a happy day!') +** +** This is correct. Had we used %s instead of %q, the generated SQL +** would have looked like this: +** +** INSERT INTO table1 VALUES('It's a happy day!'); +** +** This second example is an SQL syntax error. As a general rule you +** should always use %q instead of %s when inserting text into a string +** literal. +*/ +int sqlite_exec_printf( + sqlite*, /* An open database */ + const char *sqlFormat, /* printf-style format string for the SQL */ + sqlite_callback, /* Callback function */ + void *, /* 1st argument to callback function */ + char **errmsg, /* Error msg written here */ + ... /* Arguments to the format string. */ +); +int sqlite_exec_vprintf( + sqlite*, /* An open database */ + const char *sqlFormat, /* printf-style format string for the SQL */ + sqlite_callback, /* Callback function */ + void *, /* 1st argument to callback function */ + char **errmsg, /* Error msg written here */ + va_list ap /* Arguments to the format string. */ +); +int sqlite_get_table_printf( + sqlite*, /* An open database */ + const char *sqlFormat, /* printf-style format string for the SQL */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncolumn, /* Number of result columns written here */ + char **errmsg, /* Error msg written here */ + ... /* Arguments to the format string */ +); +int sqlite_get_table_vprintf( + sqlite*, /* An open database */ + const char *sqlFormat, /* printf-style format string for the SQL */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncolumn, /* Number of result columns written here */ + char **errmsg, /* Error msg written here */ + va_list ap /* Arguments to the format string */ +); +char *sqlite_mprintf(const char*,...); +char *sqlite_vmprintf(const char*, va_list); + +/* +** Windows systems should call this routine to free memory that +** is returned in the in the errmsg parameter of sqlite_open() when +** SQLite is a DLL. For some reason, it does not work to call free() +** directly. +*/ +void sqlite_freemem(void *p); + +/* +** Windows systems need functions to call to return the sqlite_version +** and sqlite_encoding strings. +*/ +const char *sqlite_libversion(void); +const char *sqlite_libencoding(void); + +/* +** A pointer to the following structure is used to communicate with +** the implementations of user-defined functions. +*/ +typedef struct sqlite_func sqlite_func; + +/* +** Use the following routines to create new user-defined functions. See +** the documentation for details. +*/ +int sqlite_create_function( + sqlite*, /* Database where the new function is registered */ + const char *zName, /* Name of the new function */ + int nArg, /* Number of arguments. -1 means any number */ + void (*xFunc)(sqlite_func*,int,const char**), /* C code to implement */ + void *pUserData /* Available via the sqlite_user_data() call */ +); +int sqlite_create_aggregate( + sqlite*, /* Database where the new function is registered */ + const char *zName, /* Name of the function */ + int nArg, /* Number of arguments */ + void (*xStep)(sqlite_func*,int,const char**), /* Called for each row */ + void (*xFinalize)(sqlite_func*), /* Called once to get final result */ + void *pUserData /* Available via the sqlite_user_data() call */ +); + +/* +** Use the following routine to define the datatype returned by a +** user-defined function. The second argument can be one of the +** constants SQLITE_NUMERIC, SQLITE_TEXT, or SQLITE_ARGS or it +** can be an integer greater than or equal to zero. When the datatype +** parameter is non-negative, the type of the result will be the +** same as the datatype-th argument. If datatype==SQLITE_NUMERIC +** then the result is always numeric. If datatype==SQLITE_TEXT then +** the result is always text. If datatype==SQLITE_ARGS then the result +** is numeric if any argument is numeric and is text otherwise. +*/ +int sqlite_function_type( + sqlite *db, /* The database there the function is registered */ + const char *zName, /* Name of the function */ + int datatype /* The datatype for this function */ +); +#define SQLITE_NUMERIC (-1) +#define SQLITE_TEXT (-2) +#define SQLITE_ARGS (-3) + +/* +** The user function implementations call one of the following four routines +** in order to return their results. The first parameter to each of these +** routines is a copy of the first argument to xFunc() or xFinialize(). +** The second parameter to these routines is the result to be returned. +** A NULL can be passed as the second parameter to sqlite_set_result_string() +** in order to return a NULL result. +** +** The 3rd argument to _string and _error is the number of characters to +** take from the string. If this argument is negative, then all characters +** up to and including the first '\000' are used. +** +** The sqlite_set_result_string() function allocates a buffer to hold the +** result and returns a pointer to this buffer. The calling routine +** (that is, the implementation of a user function) can alter the content +** of this buffer if desired. +*/ +char *sqlite_set_result_string(sqlite_func*,const char*,int); +void sqlite_set_result_int(sqlite_func*,int); +void sqlite_set_result_double(sqlite_func*,double); +void sqlite_set_result_error(sqlite_func*,const char*,int); + +/* +** The pUserData parameter to the sqlite_create_function() and +** sqlite_create_aggregate() routines used to register user functions +** is available to the implementation of the function using this +** call. +*/ +void *sqlite_user_data(sqlite_func*); + +/* +** Aggregate functions use the following routine to allocate +** a structure for storing their state. The first time this routine +** is called for a particular aggregate, a new structure of size nBytes +** is allocated, zeroed, and returned. On subsequent calls (for the +** same aggregate instance) the same buffer is returned. The implementation +** of the aggregate can use the returned buffer to accumulate data. +** +** The buffer allocated is freed automatically be SQLite. +*/ +void *sqlite_aggregate_context(sqlite_func*, int nBytes); + +/* +** The next routine returns the number of calls to xStep for a particular +** aggregate function instance. The current call to xStep counts so this +** routine always returns at least 1. +*/ +int sqlite_aggregate_count(sqlite_func*); + +/* +** This routine registers a callback with the SQLite library. The +** callback is invoked (at compile-time, not at run-time) for each +** attempt to access a column of a table in the database. The callback +** returns SQLITE_OK if access is allowed, SQLITE_DENY if the entire +** SQL statement should be aborted with an error and SQLITE_IGNORE +** if the column should be treated as a NULL value. +*/ +int sqlite_set_authorizer( + sqlite*, + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), + void *pUserData +); + +/* +** The second parameter to the access authorization function above will +** be one of the values below. These values signify what kind of operation +** is to be authorized. The 3rd and 4th parameters to the authorization +** function will be parameters or NULL depending on which of the following +** codes is used as the second parameter. The 5th parameter is the name +** of the database ("main", "temp", etc.) if applicable. The 6th parameter +** is the name of the inner-most trigger or view that is responsible for +** the access attempt or NULL if this access attempt is directly from +** input SQL code. +** +** Arg-3 Arg-4 +*/ +#define SQLITE_COPY 0 /* Table Name File Name */ +#define SQLITE_CREATE_INDEX 1 /* Index Name Table Name */ +#define SQLITE_CREATE_TABLE 2 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_INDEX 3 /* Index Name Table Name */ +#define SQLITE_CREATE_TEMP_TABLE 4 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name Table Name */ +#define SQLITE_CREATE_TEMP_VIEW 6 /* View Name NULL */ +#define SQLITE_CREATE_TRIGGER 7 /* Trigger Name Table Name */ +#define SQLITE_CREATE_VIEW 8 /* View Name NULL */ +#define SQLITE_DELETE 9 /* Table Name NULL */ +#define SQLITE_DROP_INDEX 10 /* Index Name Table Name */ +#define SQLITE_DROP_TABLE 11 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_INDEX 12 /* Index Name Table Name */ +#define SQLITE_DROP_TEMP_TABLE 13 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_TRIGGER 14 /* Trigger Name Table Name */ +#define SQLITE_DROP_TEMP_VIEW 15 /* View Name NULL */ +#define SQLITE_DROP_TRIGGER 16 /* Trigger Name Table Name */ +#define SQLITE_DROP_VIEW 17 /* View Name NULL */ +#define SQLITE_INSERT 18 /* Table Name NULL */ +#define SQLITE_PRAGMA 19 /* Pragma Name 1st arg or NULL */ +#define SQLITE_READ 20 /* Table Name Column Name */ +#define SQLITE_SELECT 21 /* NULL NULL */ +#define SQLITE_TRANSACTION 22 /* NULL NULL */ +#define SQLITE_UPDATE 23 /* Table Name Column Name */ +#define SQLITE_ATTACH 24 /* Filename NULL */ +#define SQLITE_DETACH 25 /* Database Name NULL */ + + +/* +** The return value of the authorization function should be one of the +** following constants: +*/ +/* #define SQLITE_OK 0 // Allow access (This is actually defined above) */ +#define SQLITE_DENY 1 /* Abort the SQL statement with an error */ +#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */ + +/* +** Register a function that is called at every invocation of sqlite_exec() +** or sqlite_compile(). This function can be used (for example) to generate +** a log file of all SQL executed against a database. +*/ +void *sqlite_trace(sqlite*, void(*xTrace)(void*,const char*), void*); + +/*** The Callback-Free API +** +** The following routines implement a new way to access SQLite that does not +** involve the use of callbacks. +** +** An sqlite_vm is an opaque object that represents a single SQL statement +** that is ready to be executed. +*/ +typedef struct sqlite_vm sqlite_vm; + +/* +** To execute an SQLite query without the use of callbacks, you first have +** to compile the SQL using this routine. The 1st parameter "db" is a pointer +** to an sqlite object obtained from sqlite_open(). The 2nd parameter +** "zSql" is the text of the SQL to be compiled. The remaining parameters +** are all outputs. +** +** *pzTail is made to point to the first character past the end of the first +** SQL statement in zSql. This routine only compiles the first statement +** in zSql, so *pzTail is left pointing to what remains uncompiled. +** +** *ppVm is left pointing to a "virtual machine" that can be used to execute +** the compiled statement. Or if there is an error, *ppVm may be set to NULL. +** If the input text contained no SQL (if the input is and empty string or +** a comment) then *ppVm is set to NULL. +** +** If any errors are detected during compilation, an error message is written +** into space obtained from malloc() and *pzErrMsg is made to point to that +** error message. The calling routine is responsible for freeing the text +** of this message when it has finished with it. Use sqlite_freemem() to +** free the message. pzErrMsg may be NULL in which case no error message +** will be generated. +** +** On success, SQLITE_OK is returned. Otherwise and error code is returned. +*/ +int sqlite_compile( + sqlite *db, /* The open database */ + const char *zSql, /* SQL statement to be compiled */ + const char **pzTail, /* OUT: uncompiled tail of zSql */ + sqlite_vm **ppVm, /* OUT: the virtual machine to execute zSql */ + char **pzErrmsg /* OUT: Error message. */ +); + +/* +** After an SQL statement has been compiled, it is handed to this routine +** to be executed. This routine executes the statement as far as it can +** go then returns. The return value will be one of SQLITE_DONE, +** SQLITE_ERROR, SQLITE_BUSY, SQLITE_ROW, or SQLITE_MISUSE. +** +** SQLITE_DONE means that the execute of the SQL statement is complete +** an no errors have occurred. sqlite_step() should not be called again +** for the same virtual machine. *pN is set to the number of columns in +** the result set and *pazColName is set to an array of strings that +** describe the column names and datatypes. The name of the i-th column +** is (*pazColName)[i] and the datatype of the i-th column is +** (*pazColName)[i+*pN]. *pazValue is set to NULL. +** +** SQLITE_ERROR means that the virtual machine encountered a run-time +** error. sqlite_step() should not be called again for the same +** virtual machine. *pN is set to 0 and *pazColName and *pazValue are set +** to NULL. Use sqlite_finalize() to obtain the specific error code +** and the error message text for the error. +** +** SQLITE_BUSY means that an attempt to open the database failed because +** another thread or process is holding a lock. The calling routine +** can try again to open the database by calling sqlite_step() again. +** The return code will only be SQLITE_BUSY if no busy handler is registered +** using the sqlite_busy_handler() or sqlite_busy_timeout() routines. If +** a busy handler callback has been registered but returns 0, then this +** routine will return SQLITE_ERROR and sqltie_finalize() will return +** SQLITE_BUSY when it is called. +** +** SQLITE_ROW means that a single row of the result is now available. +** The data is contained in *pazValue. The value of the i-th column is +** (*azValue)[i]. *pN and *pazColName are set as described in SQLITE_DONE. +** Invoke sqlite_step() again to advance to the next row. +** +** SQLITE_MISUSE is returned if sqlite_step() is called incorrectly. +** For example, if you call sqlite_step() after the virtual machine +** has halted (after a prior call to sqlite_step() has returned SQLITE_DONE) +** or if you call sqlite_step() with an incorrectly initialized virtual +** machine or a virtual machine that has been deleted or that is associated +** with an sqlite structure that has been closed. +*/ +int sqlite_step( + sqlite_vm *pVm, /* The virtual machine to execute */ + int *pN, /* OUT: Number of columns in result */ + const char ***pazValue, /* OUT: Column data */ + const char ***pazColName /* OUT: Column names and datatypes */ +); + +/* +** This routine is called to delete a virtual machine after it has finished +** executing. The return value is the result code. SQLITE_OK is returned +** if the statement executed successfully and some other value is returned if +** there was any kind of error. If an error occurred and pzErrMsg is not +** NULL, then an error message is written into memory obtained from malloc() +** and *pzErrMsg is made to point to that error message. The calling routine +** should use sqlite_freemem() to delete this message when it has finished +** with it. +** +** This routine can be called at any point during the execution of the +** virtual machine. If the virtual machine has not completed execution +** when this routine is called, that is like encountering an error or +** an interrupt. (See sqlite_interrupt().) Incomplete updates may be +** rolled back and transactions canceled, depending on the circumstances, +** and the result code returned will be SQLITE_ABORT. +*/ +int sqlite_finalize(sqlite_vm*, char **pzErrMsg); + +/* +** This routine deletes the virtual machine, writes any error message to +** *pzErrMsg and returns an SQLite return code in the same way as the +** sqlite_finalize() function. +** +** Additionally, if ppVm is not NULL, *ppVm is left pointing to a new virtual +** machine loaded with the compiled version of the original query ready for +** execution. +** +** If sqlite_reset() returns SQLITE_SCHEMA, then *ppVm is set to NULL. +** +******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ****** +*/ +int sqlite_reset(sqlite_vm*, char **pzErrMsg); + +/* +** If the SQL that was handed to sqlite_compile contains variables that +** are represented in the SQL text by a question mark ('?'). This routine +** is used to assign values to those variables. +** +** The first parameter is a virtual machine obtained from sqlite_compile(). +** The 2nd "idx" parameter determines which variable in the SQL statement +** to bind the value to. The left most '?' is 1. The 3rd parameter is +** the value to assign to that variable. The 4th parameter is the number +** of bytes in the value, including the terminating \000 for strings. +** Finally, the 5th "copy" parameter is TRUE if SQLite should make its +** own private copy of this value, or false if the space that the 3rd +** parameter points to will be unchanging and can be used directly by +** SQLite. +** +** Unbound variables are treated as having a value of NULL. To explicitly +** set a variable to NULL, call this routine with the 3rd parameter as a +** NULL pointer. +** +** If the 4th "len" parameter is -1, then strlen() is used to find the +** length. +** +** This routine can only be called immediately after sqlite_compile() +** or sqlite_reset() and before any calls to sqlite_step(). +** +******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ****** +*/ +int sqlite_bind(sqlite_vm*, int idx, const char *value, int len, int copy); + +/* +** This routine configures a callback function - the progress callback - that +** is invoked periodically during long running calls to sqlite_exec(), +** sqlite_step() and sqlite_get_table(). An example use for this API is to keep +** a GUI updated during a large query. +** +** The progress callback is invoked once for every N virtual machine opcodes, +** where N is the second argument to this function. The progress callback +** itself is identified by the third argument to this function. The fourth +** argument to this function is a void pointer passed to the progress callback +** function each time it is invoked. +** +** If a call to sqlite_exec(), sqlite_step() or sqlite_get_table() results +** in less than N opcodes being executed, then the progress callback is not +** invoked. +** +** Calling this routine overwrites any previously installed progress callback. +** To remove the progress callback altogether, pass NULL as the third +** argument to this function. +** +** If the progress callback returns a result other than 0, then the current +** query is immediately terminated and any database changes rolled back. If the +** query was part of a larger transaction, then the transaction is not rolled +** back and remains active. The sqlite_exec() call returns SQLITE_ABORT. +** +******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ****** +*/ +void sqlite_progress_handler(sqlite*, int, int(*)(void*), void*); + +/* +** Register a callback function to be invoked whenever a new transaction +** is committed. The pArg argument is passed through to the callback. +** callback. If the callback function returns non-zero, then the commit +** is converted into a rollback. +** +** If another function was previously registered, its pArg value is returned. +** Otherwise NULL is returned. +** +** Registering a NULL function disables the callback. +** +******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ****** +*/ +void *sqlite_commit_hook(sqlite*, int(*)(void*), void*); + +/* +** Open an encrypted SQLite database. If pKey==0 or nKey==0, this routine +** is the same as sqlite_open(). +** +** The code to implement this API is not available in the public release +** of SQLite. +*/ +sqlite *sqlite_open_encrypted( + const char *zFilename, /* Name of the encrypted database */ + const void *pKey, /* Pointer to the key */ + int nKey, /* Number of bytes in the key */ + int *pErrcode, /* Write error code here */ + char **pzErrmsg /* Write error message here */ +); + +/* +** Change the key on an open database. If the current database is not +** encrypted, this routine will encrypt it. If pNew==0 or nNew==0, the +** database is decrypted. +** +** The code to implement this API is not available in the public release +** of SQLite. +*/ +int sqlite_rekey( + sqlite *db, /* Database to be re-keyed */ + const void *pKey, int nKey /* The new key */ +); + +/* +** Encode a binary buffer "in" of size n bytes so that it contains +** no instances of characters '\'' or '\000'. The output is +** null-terminated and can be used as a string value in an INSERT +** or UPDATE statement. Use sqlite_decode_binary() to convert the +** string back into its original binary. +** +** The result is written into a preallocated output buffer "out". +** "out" must be able to hold at least 2 +(257*n)/254 bytes. +** In other words, the output will be expanded by as much as 3 +** bytes for every 254 bytes of input plus 2 bytes of fixed overhead. +** (This is approximately 2 + 1.0118*n or about a 1.2% size increase.) +** +** The return value is the number of characters in the encoded +** string, excluding the "\000" terminator. +** +** If out==NULL then no output is generated but the routine still returns +** the number of characters that would have been generated if out had +** not been NULL. +*/ +int sqlite_encode_binary(const unsigned char *in, int n, unsigned char *out); + +/* +** Decode the string "in" into binary data and write it into "out". +** This routine reverses the encoding created by sqlite_encode_binary(). +** The output will always be a few bytes less than the input. The number +** of bytes of output is returned. If the input is not a well-formed +** encoding, -1 is returned. +** +** The "in" and "out" parameters may point to the same buffer in order +** to decode a string in place. +*/ +int sqlite_decode_binary(const unsigned char *in, unsigned char *out); + +#ifdef __cplusplus +} /* End of the 'extern "C"' block */ +#endif + +#endif /* _SQLITE_H_ */ diff --git a/src/libs/sqlite2/sqliteInt.h b/src/libs/sqlite2/sqliteInt.h new file mode 100644 index 00000000..3c4d818a --- /dev/null +++ b/src/libs/sqlite2/sqliteInt.h @@ -0,0 +1,1274 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Internal interface definitions for SQLite. +** +** @(#) $Id: sqliteInt.h 875675 2008-10-25 07:31:30Z cgilles $ +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sqlite.h" +#include "hash.h" +#include "parse.h" +#include "btree.h" +#include +#include +#include +#include + +/* +** The maximum number of in-memory pages to use for the main database +** table and for temporary tables. +*/ +#define MAX_PAGES 2000 +#define TEMP_PAGES 500 + +/* +** If the following macro is set to 1, then NULL values are considered +** distinct for the SELECT DISTINCT statement and for UNION or EXCEPT +** compound queries. No other SQL database engine (among those tested) +** works this way except for OCELOT. But the SQL92 spec implies that +** this is how things should work. +** +** If the following macro is set to 0, then NULLs are indistinct for +** SELECT DISTINCT and for UNION. +*/ +#define NULL_ALWAYS_DISTINCT 0 + +/* +** If the following macro is set to 1, then NULL values are considered +** distinct when determining whether or not two entries are the same +** in a UNIQUE index. This is the way PostgreSQL, Oracle, DB2, MySQL, +** OCELOT, and Firebird all work. The SQL92 spec explicitly says this +** is the way things are suppose to work. +** +** If the following macro is set to 0, the NULLs are indistinct for +** a UNIQUE index. In this mode, you can only have a single NULL entry +** for a column declared UNIQUE. This is the way Informix and SQL Server +** work. +*/ +#define NULL_DISTINCT_FOR_UNIQUE 1 + +/* +** The maximum number of attached databases. This must be at least 2 +** in order to support the main database file (0) and the file used to +** hold temporary tables (1). And it must be less than 256 because +** an unsigned character is used to stored the database index. +*/ +#define MAX_ATTACHED 10 + +/* +** The next macro is used to determine where TEMP tables and indices +** are stored. Possible values: +** +** 0 Always use a temporary files +** 1 Use a file unless overridden by "PRAGMA temp_store" +** 2 Use memory unless overridden by "PRAGMA temp_store" +** 3 Always use memory +*/ +#ifndef TEMP_STORE +# define TEMP_STORE 1 +#endif + +/* +** When building SQLite for embedded systems where memory is scarce, +** you can define one or more of the following macros to omit extra +** features of the library and thus keep the size of the library to +** a minimum. +*/ +/* #define SQLITE_OMIT_AUTHORIZATION 1 */ +/* #define SQLITE_OMIT_INMEMORYDB 1 */ +/* #define SQLITE_OMIT_VACUUM 1 */ +/* #define SQLITE_OMIT_DATETIME_FUNCS 1 */ +/* #define SQLITE_OMIT_PROGRESS_CALLBACK 1 */ + +/* +** Integers of known sizes. These typedefs might change for architectures +** where the sizes very. Preprocessor macros are available so that the +** types can be conveniently redefined at compile-type. Like this: +** +** cc '-DUINTPTR_TYPE=long long int' ... +*/ +#ifndef UINT32_TYPE +# define UINT32_TYPE unsigned int +#endif +#ifndef UINT16_TYPE +# define UINT16_TYPE unsigned short int +#endif +#ifndef INT16_TYPE +# define INT16_TYPE short int +#endif +#ifndef UINT8_TYPE +# define UINT8_TYPE unsigned char +#endif +#ifndef INT8_TYPE +# define INT8_TYPE signed char +#endif +#ifndef INTPTR_TYPE +# if SQLITE_PTR_SZ==4 +# define INTPTR_TYPE int +# else +# define INTPTR_TYPE long long +# endif +#endif +typedef UINT32_TYPE u32; /* 4-byte unsigned integer */ +typedef UINT16_TYPE u16; /* 2-byte unsigned integer */ +typedef INT16_TYPE i16; /* 2-byte signed integer */ +typedef UINT8_TYPE u8; /* 1-byte unsigned integer */ +typedef UINT8_TYPE i8; /* 1-byte signed integer */ +typedef INTPTR_TYPE ptr; /* Big enough to hold a pointer */ +typedef unsigned INTPTR_TYPE uptr; /* Big enough to hold a pointer */ + +/* +** Defer sourcing vdbe.h until after the "u8" typedef is defined. +*/ +#include "vdbe.h" + +/* +** Most C compilers these days recognize "long double", don't they? +** Just in case we encounter one that does not, we will create a macro +** for long double so that it can be easily changed to just "double". +*/ +#ifndef LONGDOUBLE_TYPE +# define LONGDOUBLE_TYPE long double +#endif + +/* +** This macro casts a pointer to an integer. Useful for doing +** pointer arithmetic. +*/ +#define Addr(X) ((uptr)X) + +/* +** The maximum number of bytes of data that can be put into a single +** row of a single table. The upper bound on this limit is 16777215 +** bytes (or 16MB-1). We have arbitrarily set the limit to just 1MB +** here because the overflow page chain is inefficient for really big +** records and we want to discourage people from thinking that +** multi-megabyte records are OK. If your needs are different, you can +** change this define and recompile to increase or decrease the record +** size. +** +** The 16777198 is computed as follows: 238 bytes of payload on the +** original pages plus 16448 overflow pages each holding 1020 bytes of +** data. +*/ +#define MAX_BYTES_PER_ROW 1048576 +/* #define MAX_BYTES_PER_ROW 16777198 */ + +/* +** If memory allocation problems are found, recompile with +** +** -DMEMORY_DEBUG=1 +** +** to enable some sanity checking on malloc() and free(). To +** check for memory leaks, recompile with +** +** -DMEMORY_DEBUG=2 +** +** and a line of text will be written to standard error for +** each malloc() and free(). This output can be analyzed +** by an AWK script to determine if there are any leaks. +*/ +#ifdef MEMORY_DEBUG +# define sqliteMalloc(X) sqliteMalloc_(X,1,__FILE__,__LINE__) +# define sqliteMallocRaw(X) sqliteMalloc_(X,0,__FILE__,__LINE__) +# define sqliteFree(X) sqliteFree_(X,__FILE__,__LINE__) +# define sqliteRealloc(X,Y) sqliteRealloc_(X,Y,__FILE__,__LINE__) +# define sqliteStrDup(X) sqliteStrDup_(X,__FILE__,__LINE__) +# define sqliteStrNDup(X,Y) sqliteStrNDup_(X,Y,__FILE__,__LINE__) + void sqliteStrRealloc(char**); +#else +# define sqliteRealloc_(X,Y) sqliteRealloc(X,Y) +# define sqliteStrRealloc(X) +#endif + +/* +** This variable gets set if malloc() ever fails. After it gets set, +** the SQLite library shuts down permanently. +*/ +extern int sqlite_malloc_failed; + +/* +** The following global variables are used for testing and debugging +** only. They only work if MEMORY_DEBUG is defined. +*/ +#ifdef MEMORY_DEBUG +extern int sqlite_nMalloc; /* Number of sqliteMalloc() calls */ +extern int sqlite_nFree; /* Number of sqliteFree() calls */ +extern int sqlite_iMallocFail; /* Fail sqliteMalloc() after this many calls */ +#endif + +/* +** Name of the master database table. The master database table +** is a special table that holds the names and attributes of all +** user tables and indices. +*/ +#define MASTER_NAME "sqlite_master" +#define TEMP_MASTER_NAME "sqlite_temp_master" + +/* +** The name of the schema table. +*/ +#define SCHEMA_TABLE(x) (x?TEMP_MASTER_NAME:MASTER_NAME) + +/* +** A convenience macro that returns the number of elements in +** an array. +*/ +#define ArraySize(X) (sizeof(X)/sizeof(X[0])) + +/* +** Forward references to structures +*/ +typedef struct Column Column; +typedef struct Table Table; +typedef struct Index Index; +typedef struct Instruction Instruction; +typedef struct Expr Expr; +typedef struct ExprList ExprList; +typedef struct Parse Parse; +typedef struct Token Token; +typedef struct IdList IdList; +typedef struct SrcList SrcList; +typedef struct WhereInfo WhereInfo; +typedef struct WhereLevel WhereLevel; +typedef struct Select Select; +typedef struct AggExpr AggExpr; +typedef struct FuncDef FuncDef; +typedef struct Trigger Trigger; +typedef struct TriggerStep TriggerStep; +typedef struct TriggerStack TriggerStack; +typedef struct FKey FKey; +typedef struct Db Db; +typedef struct AuthContext AuthContext; + +/* +** Each database file to be accessed by the system is an instance +** of the following structure. There are normally two of these structures +** in the sqlite.aDb[] array. aDb[0] is the main database file and +** aDb[1] is the database file used to hold temporary tables. Additional +** databases may be attached. +*/ +struct Db { + char *zName; /* Name of this database */ + Btree *pBt; /* The B*Tree structure for this database file */ + int schema_cookie; /* Database schema version number for this file */ + Hash tblHash; /* All tables indexed by name */ + Hash idxHash; /* All (named) indices indexed by name */ + Hash trigHash; /* All triggers indexed by name */ + Hash aFKey; /* Foreign keys indexed by to-table */ + u8 inTrans; /* 0: not writable. 1: Transaction. 2: Checkpoint */ + u16 flags; /* Flags associated with this database */ + void *pAux; /* Auxiliary data. Usually NULL */ + void (*xFreeAux)(void*); /* Routine to free pAux */ +}; + +/* +** These macros can be used to test, set, or clear bits in the +** Db.flags field. +*/ +#define DbHasProperty(D,I,P) (((D)->aDb[I].flags&(P))==(P)) +#define DbHasAnyProperty(D,I,P) (((D)->aDb[I].flags&(P))!=0) +#define DbSetProperty(D,I,P) (D)->aDb[I].flags|=(P) +#define DbClearProperty(D,I,P) (D)->aDb[I].flags&=~(P) + +/* +** Allowed values for the DB.flags field. +** +** The DB_Locked flag is set when the first OP_Transaction or OP_Checkpoint +** opcode is emitted for a database. This prevents multiple occurances +** of those opcodes for the same database in the same program. Similarly, +** the DB_Cookie flag is set when the OP_VerifyCookie opcode is emitted, +** and prevents duplicate OP_VerifyCookies from taking up space and slowing +** down execution. +** +** The DB_SchemaLoaded flag is set after the database schema has been +** read into internal hash tables. +** +** DB_UnresetViews means that one or more views have column names that +** have been filled out. If the schema changes, these column names might +** changes and so the view will need to be reset. +*/ +#define DB_Locked 0x0001 /* OP_Transaction opcode has been emitted */ +#define DB_Cookie 0x0002 /* OP_VerifyCookie opcode has been emiited */ +#define DB_SchemaLoaded 0x0004 /* The schema has been loaded */ +#define DB_UnresetViews 0x0008 /* Some views have defined column names */ + + +/* +** Each database is an instance of the following structure. +** +** The sqlite.file_format is initialized by the database file +** and helps determines how the data in the database file is +** represented. This field allows newer versions of the library +** to read and write older databases. The various file formats +** are as follows: +** +** file_format==1 Version 2.1.0. +** file_format==2 Version 2.2.0. Add support for INTEGER PRIMARY KEY. +** file_format==3 Version 2.6.0. Fix empty-string index bug. +** file_format==4 Version 2.7.0. Add support for separate numeric and +** text datatypes. +** +** The sqlite.temp_store determines where temporary database files +** are stored. If 1, then a file is created to hold those tables. If +** 2, then they are held in memory. 0 means use the default value in +** the TEMP_STORE macro. +** +** The sqlite.lastRowid records the last insert rowid generated by an +** insert statement. Inserts on views do not affect its value. Each +** trigger has its own context, so that lastRowid can be updated inside +** triggers as usual. The previous value will be restored once the trigger +** exits. Upon entering a before or instead of trigger, lastRowid is no +** longer (since after version 2.8.12) reset to -1. +** +** The sqlite.nChange does not count changes within triggers and keeps no +** context. It is reset at start of sqlite_exec. +** The sqlite.lsChange represents the number of changes made by the last +** insert, update, or delete statement. It remains constant throughout the +** length of a statement and is then updated by OP_SetCounts. It keeps a +** context stack just like lastRowid so that the count of changes +** within a trigger is not seen outside the trigger. Changes to views do not +** affect the value of lsChange. +** The sqlite.csChange keeps track of the number of current changes (since +** the last statement) and is used to update sqlite_lsChange. +*/ +struct sqlite { + int nDb; /* Number of backends currently in use */ + Db *aDb; /* All backends */ + Db aDbStatic[2]; /* Static space for the 2 default backends */ + int flags; /* Miscellanous flags. See below */ + u8 file_format; /* What file format version is this database? */ + u8 safety_level; /* How aggressive at synching data to disk */ + u8 want_to_close; /* Close after all VDBEs are deallocated */ + u8 temp_store; /* 1=file, 2=memory, 0=compile-time default */ + u8 onError; /* Default conflict algorithm */ + int next_cookie; /* Next value of aDb[0].schema_cookie */ + int cache_size; /* Number of pages to use in the cache */ + int nTable; /* Number of tables in the database */ + void *pBusyArg; /* 1st Argument to the busy callback */ + int (*xBusyCallback)(void *,const char*,int); /* The busy callback */ + void *pCommitArg; /* Argument to xCommitCallback() */ + int (*xCommitCallback)(void*);/* Invoked at every commit. */ + Hash aFunc; /* All functions that can be in SQL exprs */ + int lastRowid; /* ROWID of most recent insert (see above) */ + int priorNewRowid; /* Last randomly generated ROWID */ + int magic; /* Magic number for detect library misuse */ + int nChange; /* Number of rows changed (see above) */ + int lsChange; /* Last statement change count (see above) */ + int csChange; /* Current statement change count (see above) */ + struct sqliteInitInfo { /* Information used during initialization */ + int iDb; /* When back is being initialized */ + int newTnum; /* Rootpage of table being initialized */ + u8 busy; /* TRUE if currently initializing */ + } init; + struct Vdbe *pVdbe; /* List of active virtual machines */ + void (*xTrace)(void*,const char*); /* Trace function */ + void *pTraceArg; /* Argument to the trace function */ +#ifndef SQLITE_OMIT_AUTHORIZATION + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*); + /* Access authorization function */ + void *pAuthArg; /* 1st argument to the access auth function */ +#endif +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + int (*xProgress)(void *); /* The progress callback */ + void *pProgressArg; /* Argument to the progress callback */ + int nProgressOps; /* Number of opcodes for progress callback */ +#endif +}; + +/* +** Possible values for the sqlite.flags and or Db.flags fields. +** +** On sqlite.flags, the SQLITE_InTrans value means that we have +** executed a BEGIN. On Db.flags, SQLITE_InTrans means a statement +** transaction is active on that particular database file. +*/ +#define SQLITE_VdbeTrace 0x00000001 /* True to trace VDBE execution */ +#define SQLITE_Initialized 0x00000002 /* True after initialization */ +#define SQLITE_Interrupt 0x00000004 /* Cancel current operation */ +#define SQLITE_InTrans 0x00000008 /* True if in a transaction */ +#define SQLITE_InternChanges 0x00000010 /* Uncommitted Hash table changes */ +#define SQLITE_FullColNames 0x00000020 /* Show full column names on SELECT */ +#define SQLITE_ShortColNames 0x00000040 /* Show short columns names */ +#define SQLITE_CountRows 0x00000080 /* Count rows changed by INSERT, */ + /* DELETE, or UPDATE and return */ + /* the count using a callback. */ +#define SQLITE_NullCallback 0x00000100 /* Invoke the callback once if the */ + /* result set is empty */ +#define SQLITE_ReportTypes 0x00000200 /* Include information on datatypes */ + /* in 4th argument of callback */ + +/* +** Possible values for the sqlite.magic field. +** The numbers are obtained at random and have no special meaning, other +** than being distinct from one another. +*/ +#define SQLITE_MAGIC_OPEN 0xa029a697 /* Database is open */ +#define SQLITE_MAGIC_CLOSED 0x9f3c2d33 /* Database is closed */ +#define SQLITE_MAGIC_BUSY 0xf03b7906 /* Database currently in use */ +#define SQLITE_MAGIC_ERROR 0xb5357930 /* An SQLITE_MISUSE error occurred */ + +/* +** Each SQL function is defined by an instance of the following +** structure. A pointer to this structure is stored in the sqlite.aFunc +** hash table. When multiple functions have the same name, the hash table +** points to a linked list of these structures. +*/ +struct FuncDef { + void (*xFunc)(sqlite_func*,int,const char**); /* Regular function */ + void (*xStep)(sqlite_func*,int,const char**); /* Aggregate function step */ + void (*xFinalize)(sqlite_func*); /* Aggregate function finializer */ + signed char nArg; /* Number of arguments. -1 means unlimited */ + signed char dataType; /* Arg that determines datatype. -1=NUMERIC, */ + /* -2=TEXT. -3=SQLITE_ARGS */ + u8 includeTypes; /* Add datatypes to args of xFunc and xStep */ + void *pUserData; /* User data parameter */ + FuncDef *pNext; /* Next function with same name */ +}; + +/* +** information about each column of an SQL table is held in an instance +** of this structure. +*/ +struct Column { + char *zName; /* Name of this column */ + char *zDflt; /* Default value of this column */ + char *zType; /* Data type for this column */ + u8 notNull; /* True if there is a NOT NULL constraint */ + u8 isPrimKey; /* True if this column is part of the PRIMARY KEY */ + u8 sortOrder; /* Some combination of SQLITE_SO_... values */ + u8 dottedName; /* True if zName contains a "." character */ +}; + +/* +** The allowed sort orders. +** +** The TEXT and NUM values use bits that do not overlap with DESC and ASC. +** That way the two can be combined into a single number. +*/ +#define SQLITE_SO_UNK 0 /* Use the default collating type. (SCT_NUM) */ +#define SQLITE_SO_TEXT 2 /* Sort using memcmp() */ +#define SQLITE_SO_NUM 4 /* Sort using sqliteCompare() */ +#define SQLITE_SO_TYPEMASK 6 /* Mask to extract the collating sequence */ +#define SQLITE_SO_ASC 0 /* Sort in ascending order */ +#define SQLITE_SO_DESC 1 /* Sort in descending order */ +#define SQLITE_SO_DIRMASK 1 /* Mask to extract the sort direction */ + +/* +** Each SQL table is represented in memory by an instance of the +** following structure. +** +** Table.zName is the name of the table. The case of the original +** CREATE TABLE statement is stored, but case is not significant for +** comparisons. +** +** Table.nCol is the number of columns in this table. Table.aCol is a +** pointer to an array of Column structures, one for each column. +** +** If the table has an INTEGER PRIMARY KEY, then Table.iPKey is the index of +** the column that is that key. Otherwise Table.iPKey is negative. Note +** that the datatype of the PRIMARY KEY must be INTEGER for this field to +** be set. An INTEGER PRIMARY KEY is used as the rowid for each row of +** the table. If a table has no INTEGER PRIMARY KEY, then a random rowid +** is generated for each row of the table. Table.hasPrimKey is true if +** the table has any PRIMARY KEY, INTEGER or otherwise. +** +** Table.tnum is the page number for the root BTree page of the table in the +** database file. If Table.iDb is the index of the database table backend +** in sqlite.aDb[]. 0 is for the main database and 1 is for the file that +** holds temporary tables and indices. If Table.isTransient +** is true, then the table is stored in a file that is automatically deleted +** when the VDBE cursor to the table is closed. In this case Table.tnum +** refers VDBE cursor number that holds the table open, not to the root +** page number. Transient tables are used to hold the results of a +** sub-query that appears instead of a real table name in the FROM clause +** of a SELECT statement. +*/ +struct Table { + char *zName; /* Name of the table */ + int nCol; /* Number of columns in this table */ + Column *aCol; /* Information about each column */ + int iPKey; /* If not less then 0, use aCol[iPKey] as the primary key */ + Index *pIndex; /* List of SQL indexes on this table. */ + int tnum; /* Root BTree node for this table (see note above) */ + Select *pSelect; /* NULL for tables. Points to definition if a view. */ + u8 readOnly; /* True if this table should not be written by the user */ + u8 iDb; /* Index into sqlite.aDb[] of the backend for this table */ + u8 isTransient; /* True if automatically deleted when VDBE finishes */ + u8 hasPrimKey; /* True if there exists a primary key */ + u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ + Trigger *pTrigger; /* List of SQL triggers on this table */ + FKey *pFKey; /* Linked list of all foreign keys in this table */ +}; + +/* +** Each foreign key constraint is an instance of the following structure. +** +** A foreign key is associated with two tables. The "from" table is +** the table that contains the REFERENCES clause that creates the foreign +** key. The "to" table is the table that is named in the REFERENCES clause. +** Consider this example: +** +** CREATE TABLE ex1( +** a INTEGER PRIMARY KEY, +** b INTEGER CONSTRAINT fk1 REFERENCES ex2(x) +** ); +** +** For foreign key "fk1", the from-table is "ex1" and the to-table is "ex2". +** +** Each REFERENCES clause generates an instance of the following structure +** which is attached to the from-table. The to-table need not exist when +** the from-table is created. The existance of the to-table is not checked +** until an attempt is made to insert data into the from-table. +** +** The sqlite.aFKey hash table stores pointers to this structure +** given the name of a to-table. For each to-table, all foreign keys +** associated with that table are on a linked list using the FKey.pNextTo +** field. +*/ +struct FKey { + Table *pFrom; /* The table that constains the REFERENCES clause */ + FKey *pNextFrom; /* Next foreign key in pFrom */ + char *zTo; /* Name of table that the key points to */ + FKey *pNextTo; /* Next foreign key that points to zTo */ + int nCol; /* Number of columns in this key */ + struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ + int iFrom; /* Index of column in pFrom */ + char *zCol; /* Name of column in zTo. If 0 use PRIMARY KEY */ + } *aCol; /* One entry for each of nCol column s */ + u8 isDeferred; /* True if constraint checking is deferred till COMMIT */ + u8 updateConf; /* How to resolve conflicts that occur on UPDATE */ + u8 deleteConf; /* How to resolve conflicts that occur on DELETE */ + u8 insertConf; /* How to resolve conflicts that occur on INSERT */ +}; + +/* +** SQLite supports many different ways to resolve a contraint +** error. ROLLBACK processing means that a constraint violation +** causes the operation in process to fail and for the current transaction +** to be rolled back. ABORT processing means the operation in process +** fails and any prior changes from that one operation are backed out, +** but the transaction is not rolled back. FAIL processing means that +** the operation in progress stops and returns an error code. But prior +** changes due to the same operation are not backed out and no rollback +** occurs. IGNORE means that the particular row that caused the constraint +** error is not inserted or updated. Processing continues and no error +** is returned. REPLACE means that preexisting database rows that caused +** a UNIQUE constraint violation are removed so that the new insert or +** update can proceed. Processing continues and no error is reported. +** +** RESTRICT, SETNULL, and CASCADE actions apply only to foreign keys. +** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the +** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign +** key is set to NULL. CASCADE means that a DELETE or UPDATE of the +** referenced table row is propagated into the row that holds the +** foreign key. +** +** The following symbolic values are used to record which type +** of action to take. +*/ +#define OE_None 0 /* There is no constraint to check */ +#define OE_Rollback 1 /* Fail the operation and rollback the transaction */ +#define OE_Abort 2 /* Back out changes but do no rollback transaction */ +#define OE_Fail 3 /* Stop the operation but leave all prior changes */ +#define OE_Ignore 4 /* Ignore the error. Do not do the INSERT or UPDATE */ +#define OE_Replace 5 /* Delete existing record, then do INSERT or UPDATE */ + +#define OE_Restrict 6 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */ +#define OE_SetNull 7 /* Set the foreign key value to NULL */ +#define OE_SetDflt 8 /* Set the foreign key value to its default */ +#define OE_Cascade 9 /* Cascade the changes */ + +#define OE_Default 99 /* Do whatever the default action is */ + +/* +** Each SQL index is represented in memory by an +** instance of the following structure. +** +** The columns of the table that are to be indexed are described +** by the aiColumn[] field of this structure. For example, suppose +** we have the following table and index: +** +** CREATE TABLE Ex1(c1 int, c2 int, c3 text); +** CREATE INDEX Ex2 ON Ex1(c3,c1); +** +** In the Table structure describing Ex1, nCol==3 because there are +** three columns in the table. In the Index structure describing +** Ex2, nColumn==2 since 2 of the 3 columns of Ex1 are indexed. +** The value of aiColumn is {2, 0}. aiColumn[0]==2 because the +** first column to be indexed (c3) has an index of 2 in Ex1.aCol[]. +** The second column to be indexed (c1) has an index of 0 in +** Ex1.aCol[], hence Ex2.aiColumn[1]==0. +** +** The Index.onError field determines whether or not the indexed columns +** must be unique and what to do if they are not. When Index.onError=OE_None, +** it means this is not a unique index. Otherwise it is a unique index +** and the value of Index.onError indicate the which conflict resolution +** algorithm to employ whenever an attempt is made to insert a non-unique +** element. +*/ +struct Index { + char *zName; /* Name of this index */ + int nColumn; /* Number of columns in the table used by this index */ + int *aiColumn; /* Which columns are used by this index. 1st is 0 */ + Table *pTable; /* The SQL table being indexed */ + int tnum; /* Page containing root of this index in database file */ + u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ + u8 autoIndex; /* True if is automatically created (ex: by UNIQUE) */ + u8 iDb; /* Index in sqlite.aDb[] of where this index is stored */ + Index *pNext; /* The next index associated with the same table */ +}; + +/* +** Each token coming out of the lexer is an instance of +** this structure. Tokens are also used as part of an expression. +** +** Note if Token.z==0 then Token.dyn and Token.n are undefined and +** may contain random values. Do not make any assuptions about Token.dyn +** and Token.n when Token.z==0. +*/ +struct Token { + const char *z; /* Text of the token. Not NULL-terminated! */ + unsigned dyn : 1; /* True for malloced memory, false for static */ + unsigned n : 31; /* Number of characters in this token */ +}; + +/* +** Each node of an expression in the parse tree is an instance +** of this structure. +** +** Expr.op is the opcode. The integer parser token codes are reused +** as opcodes here. For example, the parser defines TK_GE to be an integer +** code representing the ">=" operator. This same integer code is reused +** to represent the greater-than-or-equal-to operator in the expression +** tree. +** +** Expr.pRight and Expr.pLeft are subexpressions. Expr.pList is a list +** of argument if the expression is a function. +** +** Expr.token is the operator token for this node. For some expressions +** that have subexpressions, Expr.token can be the complete text that gave +** rise to the Expr. In the latter case, the token is marked as being +** a compound token. +** +** An expression of the form ID or ID.ID refers to a column in a table. +** For such expressions, Expr.op is set to TK_COLUMN and Expr.iTable is +** the integer cursor number of a VDBE cursor pointing to that table and +** Expr.iColumn is the column number for the specific column. If the +** expression is used as a result in an aggregate SELECT, then the +** value is also stored in the Expr.iAgg column in the aggregate so that +** it can be accessed after all aggregates are computed. +** +** If the expression is a function, the Expr.iTable is an integer code +** representing which function. If the expression is an unbound variable +** marker (a question mark character '?' in the original SQL) then the +** Expr.iTable holds the index number for that variable. +** +** The Expr.pSelect field points to a SELECT statement. The SELECT might +** be the right operand of an IN operator. Or, if a scalar SELECT appears +** in an expression the opcode is TK_SELECT and Expr.pSelect is the only +** operand. +*/ +struct Expr { + u8 op; /* Operation performed by this node */ + u8 dataType; /* Either SQLITE_SO_TEXT or SQLITE_SO_NUM */ + u8 iDb; /* Database referenced by this expression */ + u8 flags; /* Various flags. See below */ + Expr *pLeft, *pRight; /* Left and right subnodes */ + ExprList *pList; /* A list of expressions used as function arguments + ** or in " IN (useAgg==TRUE, pull + ** result from the iAgg-th element of the aggregator */ + Select *pSelect; /* When the expression is a sub-select. Also the + ** right side of " IN (
    %s
    "); + output_html_string(p->out, azArg[i] ? azArg[i] : p->nullvalue); + fprintf(p->out,"
    +**
    Internal
    Type
    Requested
    Type
    Conversion +** +**
    NULL INTEGER Result is 0 +**
    NULL FLOAT Result is 0.0 +**
    NULL TEXT Result is NULL pointer +**
    NULL BLOB Result is NULL pointer +**
    INTEGER FLOAT Convert from integer to float +**
    INTEGER TEXT ASCII rendering of the integer +**
    INTEGER BLOB Same as for INTEGER->TEXT +**
    FLOAT INTEGER Convert from float to integer +**
    FLOAT TEXT ASCII rendering of the float +**
    FLOAT BLOB Same as FLOAT->TEXT +**
    TEXT INTEGER Use atoi() +**
    TEXT FLOAT Use atof() +**
    TEXT BLOB No change +**
    BLOB INTEGER Convert to TEXT then use atoi() +**
    BLOB FLOAT Convert to TEXT then use atof() +**
    BLOB TEXT Add a zero terminator if needed +**
    +** +** +** The table above makes reference to standard C library functions atoi() +** and atof(). SQLite does not really use these functions. It has its +** on equavalent internal routines. The atoi() and atof() names are +** used in the table for brevity and because they are familiar to most +** C programmers. +** +** Note that when type conversions occur, pointers returned by prior +** calls to sqlite3_column_blob(), sqlite3_column_text(), and/or +** sqlite3_column_text16() may be invalidated. +** Type conversions and pointer invalidations might occur +** in the following cases: +** +**

      +**
    • The initial content is a BLOB and sqlite3_column_text() +** or sqlite3_column_text16() is called. A zero-terminator might +** need to be added to the string.

    • +** +**
    • The initial content is UTF-8 text and sqlite3_column_bytes16() or +** sqlite3_column_text16() is called. The content must be converted +** to UTF-16.

    • +** +**
    • The initial content is UTF-16 text and sqlite3_column_bytes() or +** sqlite3_column_text() is called. The content must be converted +** to UTF-8.

    • +**
    +** +** Conversions between UTF-16be and UTF-16le are always done in place and do +** not invalidate a prior pointer, though of course the content of the buffer +** that the prior pointer points to will have been modified. Other kinds +** of conversion are done in place when it is possible, but sometime it is +** not possible and in those cases prior pointers are invalidated. +** +** The safest and easiest to remember policy is to invoke these routines +** in one of the following ways: +** +**
      +**
    • sqlite3_column_text() followed by sqlite3_column_bytes()
    • +**
    • sqlite3_column_blob() followed by sqlite3_column_bytes()
    • +**
    • sqlite3_column_text16() followed by sqlite3_column_bytes16()
    • +**
    +** +** In other words, you should call sqlite3_column_text(), sqlite3_column_blob(), +** or sqlite3_column_text16() first to force the result into the desired +** format, then invoke sqlite3_column_bytes() or sqlite3_column_bytes16() to +** find the size of the result. Do not mix call to sqlite3_column_text() or +** sqlite3_column_blob() with calls to sqlite3_column_bytes16(). And do not +** mix calls to sqlite3_column_text16() with calls to sqlite3_column_bytes(). +** +** The pointers returned are valid until a type conversion occurs as +** described above, or until [sqlite3_step()] or [sqlite3_reset()] or +** [sqlite3_finalize()] is called. The memory space used to hold strings +** and blobs is freed automatically. Do not pass the pointers returned +** [sqlite3_column_blob()], [sqlite3_column_text()], etc. into +** [sqlite3_free()]. +** +** If a memory allocation error occurs during the evaluation of any +** of these routines, a default value is returned. The default value +** is either the integer 0, the floating point number 0.0, or a NULL +** pointer. Subsequent calls to [sqlite3_errcode()] will return +** [SQLITE_NOMEM]. +** +** INVARIANTS: +** +** {F13803} The [sqlite3_column_blob(S,N)] interface converts the +** Nth column in the current row of the result set for +** [prepared statement] S into a blob and then returns a +** pointer to the converted value. +** +** {F13806} The [sqlite3_column_bytes(S,N)] interface returns the +** number of bytes in the blob or string (exclusive of the +** zero terminator on the string) that was returned by the +** most recent call to [sqlite3_column_blob(S,N)] or +** [sqlite3_column_text(S,N)]. +** +** {F13809} The [sqlite3_column_bytes16(S,N)] interface returns the +** number of bytes in the string (exclusive of the +** zero terminator on the string) that was returned by the +** most recent call to [sqlite3_column_text16(S,N)]. +** +** {F13812} The [sqlite3_column_double(S,N)] interface converts the +** Nth column in the current row of the result set for +** [prepared statement] S into a floating point value and +** returns a copy of that value. +** +** {F13815} The [sqlite3_column_int(S,N)] interface converts the +** Nth column in the current row of the result set for +** [prepared statement] S into a 64-bit signed integer and +** returns the lower 32 bits of that integer. +** +** {F13818} The [sqlite3_column_int64(S,N)] interface converts the +** Nth column in the current row of the result set for +** [prepared statement] S into a 64-bit signed integer and +** returns a copy of that integer. +** +** {F13821} The [sqlite3_column_text(S,N)] interface converts the +** Nth column in the current row of the result set for +** [prepared statement] S into a zero-terminated UTF-8 +** string and returns a pointer to that string. +** +** {F13824} The [sqlite3_column_text16(S,N)] interface converts the +** Nth column in the current row of the result set for +** [prepared statement] S into a zero-terminated 2-byte +** aligned UTF-16 native byte order +** string and returns a pointer to that string. +** +** {F13827} The [sqlite3_column_type(S,N)] interface returns +** one of [SQLITE_NULL], [SQLITE_INTEGER], [SQLITE_FLOAT], +** [SQLITE_TEXT], or [SQLITE_BLOB] as appropriate for +** the Nth column in the current row of the result set for +** [prepared statement] S. +** +** {F13830} The [sqlite3_column_value(S,N)] interface returns a +** pointer to an [unprotected sqlite3_value] object for the +** Nth column in the current row of the result set for +** [prepared statement] S. +*/ +SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); +SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); +SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); +SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); + +/* +** CAPI3REF: Destroy A Prepared Statement Object {F13300} +** +** The sqlite3_finalize() function is called to delete a +** [prepared statement]. If the statement was +** executed successfully, or not executed at all, then SQLITE_OK is returned. +** If execution of the statement failed then an +** [error code] or [extended error code] +** is returned. +** +** This routine can be called at any point during the execution of the +** [prepared statement]. If the virtual machine has not +** completed execution when this routine is called, that is like +** encountering an error or an interrupt. (See [sqlite3_interrupt()].) +** Incomplete updates may be rolled back and transactions cancelled, +** depending on the circumstances, and the +** [error code] returned will be [SQLITE_ABORT]. +** +** INVARIANTS: +** +** {F11302} The [sqlite3_finalize(S)] interface destroys the +** [prepared statement] S and releases all +** memory and file resources held by that object. +** +** {F11304} If the most recent call to [sqlite3_step(S)] for the +** [prepared statement] S returned an error, +** then [sqlite3_finalize(S)] returns that same error. +*/ +SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Reset A Prepared Statement Object {F13330} +** +** The sqlite3_reset() function is called to reset a +** [prepared statement] object. +** back to its initial state, ready to be re-executed. +** Any SQL statement variables that had values bound to them using +** the [sqlite3_bind_blob | sqlite3_bind_*() API] retain their values. +** Use [sqlite3_clear_bindings()] to reset the bindings. +** +** {F11332} The [sqlite3_reset(S)] interface resets the [prepared statement] S +** back to the beginning of its program. +** +** {F11334} If the most recent call to [sqlite3_step(S)] for +** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE], +** or if [sqlite3_step(S)] has never before been called on S, +** then [sqlite3_reset(S)] returns [SQLITE_OK]. +** +** {F11336} If the most recent call to [sqlite3_step(S)] for +** [prepared statement] S indicated an error, then +** [sqlite3_reset(S)] returns an appropriate [error code]. +** +** {F11338} The [sqlite3_reset(S)] interface does not change the values +** of any [sqlite3_bind_blob|bindings] on [prepared statement] S. +*/ +SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Create Or Redefine SQL Functions {F16100} +** KEYWORDS: {function creation routines} +** +** These two functions (collectively known as +** "function creation routines") are used to add SQL functions or aggregates +** or to redefine the behavior of existing SQL functions or aggregates. The +** difference only between the two is that the second parameter, the +** name of the (scalar) function or aggregate, is encoded in UTF-8 for +** sqlite3_create_function() and UTF-16 for sqlite3_create_function16(). +** +** The first parameter is the [database connection] to which the SQL +** function is to be added. If a single +** program uses more than one [database connection] internally, then SQL +** functions must be added individually to each [database connection]. +** +** The second parameter is the name of the SQL function to be created +** or redefined. +** The length of the name is limited to 255 bytes, exclusive of the +** zero-terminator. Note that the name length limit is in bytes, not +** characters. Any attempt to create a function with a longer name +** will result in an SQLITE_ERROR error. +** +** The third parameter is the number of arguments that the SQL function or +** aggregate takes. If this parameter is negative, then the SQL function or +** aggregate may take any number of arguments. +** +** The fourth parameter, eTextRep, specifies what +** [SQLITE_UTF8 | text encoding] this SQL function prefers for +** its parameters. Any SQL function implementation should be able to work +** work with UTF-8, UTF-16le, or UTF-16be. But some implementations may be +** more efficient with one encoding than another. It is allowed to +** invoke sqlite3_create_function() or sqlite3_create_function16() multiple +** times with the same function but with different values of eTextRep. +** When multiple implementations of the same function are available, SQLite +** will pick the one that involves the least amount of data conversion. +** If there is only a single implementation which does not care what +** text encoding is used, then the fourth argument should be +** [SQLITE_ANY]. +** +** The fifth parameter is an arbitrary pointer. The implementation +** of the function can gain access to this pointer using +** [sqlite3_user_data()]. +** +** The seventh, eighth and ninth parameters, xFunc, xStep and xFinal, are +** pointers to C-language functions that implement the SQL +** function or aggregate. A scalar SQL function requires an implementation of +** the xFunc callback only, NULL pointers should be passed as the xStep +** and xFinal parameters. An aggregate SQL function requires an implementation +** of xStep and xFinal and NULL should be passed for xFunc. To delete an +** existing SQL function or aggregate, pass NULL for all three function +** callback. +** +** It is permitted to register multiple implementations of the same +** functions with the same name but with either differing numbers of +** arguments or differing perferred text encodings. SQLite will use +** the implementation most closely matches the way in which the +** SQL function is used. +** +** INVARIANTS: +** +** {F16103} The [sqlite3_create_function16()] interface behaves exactly +** like [sqlite3_create_function()] in every way except that it +** interprets the zFunctionName argument as +** zero-terminated UTF-16 native byte order instead of as a +** zero-terminated UTF-8. +** +** {F16106} A successful invocation of +** the [sqlite3_create_function(D,X,N,E,...)] interface registers +** or replaces callback functions in [database connection] D +** used to implement the SQL function named X with N parameters +** and having a perferred text encoding of E. +** +** {F16109} A successful call to [sqlite3_create_function(D,X,N,E,P,F,S,L)] +** replaces the P, F, S, and L values from any prior calls with +** the same D, X, N, and E values. +** +** {F16112} The [sqlite3_create_function(D,X,...)] interface fails with +** a return code of [SQLITE_ERROR] if the SQL function name X is +** longer than 255 bytes exclusive of the zero terminator. +** +** {F16118} Either F must be NULL and S and L are non-NULL or else F +** is non-NULL and S and L are NULL, otherwise +** [sqlite3_create_function(D,X,N,E,P,F,S,L)] returns [SQLITE_ERROR]. +** +** {F16121} The [sqlite3_create_function(D,...)] interface fails with an +** error code of [SQLITE_BUSY] if there exist [prepared statements] +** associated with the [database connection] D. +** +** {F16124} The [sqlite3_create_function(D,X,N,...)] interface fails with an +** error code of [SQLITE_ERROR] if parameter N (specifying the number +** of arguments to the SQL function being registered) is less +** than -1 or greater than 127. +** +** {F16127} When N is non-negative, the [sqlite3_create_function(D,X,N,...)] +** interface causes callbacks to be invoked for the SQL function +** named X when the number of arguments to the SQL function is +** exactly N. +** +** {F16130} When N is -1, the [sqlite3_create_function(D,X,N,...)] +** interface causes callbacks to be invoked for the SQL function +** named X with any number of arguments. +** +** {F16133} When calls to [sqlite3_create_function(D,X,N,...)] +** specify multiple implementations of the same function X +** and when one implementation has N>=0 and the other has N=(-1) +** the implementation with a non-zero N is preferred. +** +** {F16136} When calls to [sqlite3_create_function(D,X,N,E,...)] +** specify multiple implementations of the same function X with +** the same number of arguments N but with different +** encodings E, then the implementation where E matches the +** database encoding is preferred. +** +** {F16139} For an aggregate SQL function created using +** [sqlite3_create_function(D,X,N,E,P,0,S,L)] the finializer +** function L will always be invoked exactly once if the +** step function S is called one or more times. +** +** {F16142} When SQLite invokes either the xFunc or xStep function of +** an application-defined SQL function or aggregate created +** by [sqlite3_create_function()] or [sqlite3_create_function16()], +** then the array of [sqlite3_value] objects passed as the +** third parameter are always [protected sqlite3_value] objects. +*/ +SQLITE_API int sqlite3_create_function( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); +SQLITE_API int sqlite3_create_function16( + sqlite3 *db, + const void *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); + +/* +** CAPI3REF: Text Encodings {F10267} +** +** These constant define integer codes that represent the various +** text encodings supported by SQLite. +*/ +#define SQLITE_UTF8 1 +#define SQLITE_UTF16LE 2 +#define SQLITE_UTF16BE 3 +#define SQLITE_UTF16 4 /* Use native byte order */ +#define SQLITE_ANY 5 /* sqlite3_create_function only */ +#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ + +/* +** CAPI3REF: Obsolete Functions +** +** These functions are all now obsolete. In order to maintain +** backwards compatibility with older code, we continue to support +** these functions. However, new development projects should avoid +** the use of these functions. To help encourage people to avoid +** using these functions, we are not going to tell you want they do. +*/ +SQLITE_API int sqlite3_aggregate_count(sqlite3_context*); +SQLITE_API int sqlite3_expired(sqlite3_stmt*); +SQLITE_API int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); +SQLITE_API int sqlite3_global_recover(void); +SQLITE_API void sqlite3_thread_cleanup(void); +SQLITE_API int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int),void*,sqlite3_int64); + +/* +** CAPI3REF: Obtaining SQL Function Parameter Values {F15100} +** +** The C-language implementation of SQL functions and aggregates uses +** this set of interface routines to access the parameter values on +** the function or aggregate. +** +** The xFunc (for scalar functions) or xStep (for aggregates) parameters +** to [sqlite3_create_function()] and [sqlite3_create_function16()] +** define callbacks that implement the SQL functions and aggregates. +** The 4th parameter to these callbacks is an array of pointers to +** [protected sqlite3_value] objects. There is one [sqlite3_value] object for +** each parameter to the SQL function. These routines are used to +** extract values from the [sqlite3_value] objects. +** +** These routines work only with [protected sqlite3_value] objects. +** Any attempt to use these routines on an [unprotected sqlite3_value] +** object results in undefined behavior. +** +** These routines work just like the corresponding +** [sqlite3_column_blob | sqlite3_column_* routines] except that +** these routines take a single [protected sqlite3_value] object pointer +** instead of an [sqlite3_stmt*] pointer and an integer column number. +** +** The sqlite3_value_text16() interface extracts a UTF16 string +** in the native byte-order of the host machine. The +** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces +** extract UTF16 strings as big-endian and little-endian respectively. +** +** The sqlite3_value_numeric_type() interface attempts to apply +** numeric affinity to the value. This means that an attempt is +** made to convert the value to an integer or floating point. If +** such a conversion is possible without loss of information (in other +** words if the value is a string that looks like a number) +** then the conversion is done. Otherwise no conversion occurs. The +** [SQLITE_INTEGER | datatype] after conversion is returned. +** +** Please pay particular attention to the fact that the pointer that +** is returned from [sqlite3_value_blob()], [sqlite3_value_text()], or +** [sqlite3_value_text16()] can be invalidated by a subsequent call to +** [sqlite3_value_bytes()], [sqlite3_value_bytes16()], [sqlite3_value_text()], +** or [sqlite3_value_text16()]. +** +** These routines must be called from the same thread as +** the SQL function that supplied the [sqlite3_value*] parameters. +** +** +** INVARIANTS: +** +** {F15103} The [sqlite3_value_blob(V)] interface converts the +** [protected sqlite3_value] object V into a blob and then returns a +** pointer to the converted value. +** +** {F15106} The [sqlite3_value_bytes(V)] interface returns the +** number of bytes in the blob or string (exclusive of the +** zero terminator on the string) that was returned by the +** most recent call to [sqlite3_value_blob(V)] or +** [sqlite3_value_text(V)]. +** +** {F15109} The [sqlite3_value_bytes16(V)] interface returns the +** number of bytes in the string (exclusive of the +** zero terminator on the string) that was returned by the +** most recent call to [sqlite3_value_text16(V)], +** [sqlite3_value_text16be(V)], or [sqlite3_value_text16le(V)]. +** +** {F15112} The [sqlite3_value_double(V)] interface converts the +** [protected sqlite3_value] object V into a floating point value and +** returns a copy of that value. +** +** {F15115} The [sqlite3_value_int(V)] interface converts the +** [protected sqlite3_value] object V into a 64-bit signed integer and +** returns the lower 32 bits of that integer. +** +** {F15118} The [sqlite3_value_int64(V)] interface converts the +** [protected sqlite3_value] object V into a 64-bit signed integer and +** returns a copy of that integer. +** +** {F15121} The [sqlite3_value_text(V)] interface converts the +** [protected sqlite3_value] object V into a zero-terminated UTF-8 +** string and returns a pointer to that string. +** +** {F15124} The [sqlite3_value_text16(V)] interface converts the +** [protected sqlite3_value] object V into a zero-terminated 2-byte +** aligned UTF-16 native byte order +** string and returns a pointer to that string. +** +** {F15127} The [sqlite3_value_text16be(V)] interface converts the +** [protected sqlite3_value] object V into a zero-terminated 2-byte +** aligned UTF-16 big-endian +** string and returns a pointer to that string. +** +** {F15130} The [sqlite3_value_text16le(V)] interface converts the +** [protected sqlite3_value] object V into a zero-terminated 2-byte +** aligned UTF-16 little-endian +** string and returns a pointer to that string. +** +** {F15133} The [sqlite3_value_type(V)] interface returns +** one of [SQLITE_NULL], [SQLITE_INTEGER], [SQLITE_FLOAT], +** [SQLITE_TEXT], or [SQLITE_BLOB] as appropriate for +** the [sqlite3_value] object V. +** +** {F15136} The [sqlite3_value_numeric_type(V)] interface converts +** the [protected sqlite3_value] object V into either an integer or +** a floating point value if it can do so without loss of +** information, and returns one of [SQLITE_NULL], +** [SQLITE_INTEGER], [SQLITE_FLOAT], [SQLITE_TEXT], or +** [SQLITE_BLOB] as appropriate for +** the [protected sqlite3_value] object V after the conversion attempt. +*/ +SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); +SQLITE_API double sqlite3_value_double(sqlite3_value*); +SQLITE_API int sqlite3_value_int(sqlite3_value*); +SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*); +SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*); +SQLITE_API int sqlite3_value_type(sqlite3_value*); +SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); + +/* +** CAPI3REF: Obtain Aggregate Function Context {F16210} +** +** The implementation of aggregate SQL functions use this routine to allocate +** a structure for storing their state. +** The first time the sqlite3_aggregate_context() routine is +** is called for a particular aggregate, SQLite allocates nBytes of memory +** zeros that memory, and returns a pointer to it. +** On second and subsequent calls to sqlite3_aggregate_context() +** for the same aggregate function index, the same buffer is returned. +** The implementation +** of the aggregate can use the returned buffer to accumulate data. +** +** SQLite automatically frees the allocated buffer when the aggregate +** query concludes. +** +** The first parameter should be a copy of the +** [sqlite3_context | SQL function context] that is the first +** parameter to the callback routine that implements the aggregate +** function. +** +** This routine must be called from the same thread in which +** the aggregate SQL function is running. +** +** INVARIANTS: +** +** {F16211} The first invocation of [sqlite3_aggregate_context(C,N)] for +** a particular instance of an aggregate function (for a particular +** context C) causes SQLite to allocation N bytes of memory, +** zero that memory, and return a pointer to the allocationed +** memory. +** +** {F16213} If a memory allocation error occurs during +** [sqlite3_aggregate_context(C,N)] then the function returns 0. +** +** {F16215} Second and subsequent invocations of +** [sqlite3_aggregate_context(C,N)] for the same context pointer C +** ignore the N parameter and return a pointer to the same +** block of memory returned by the first invocation. +** +** {F16217} The memory allocated by [sqlite3_aggregate_context(C,N)] is +** automatically freed on the next call to [sqlite3_reset()] +** or [sqlite3_finalize()] for the [prepared statement] containing +** the aggregate function associated with context C. +*/ +SQLITE_API void *sqlite3_aggregate_context(sqlite3_context*, int nBytes); + +/* +** CAPI3REF: User Data For Functions {F16240} +** +** The sqlite3_user_data() interface returns a copy of +** the pointer that was the pUserData parameter (the 5th parameter) +** of the the [sqlite3_create_function()] +** and [sqlite3_create_function16()] routines that originally +** registered the application defined function. {END} +** +** This routine must be called from the same thread in which +** the application-defined function is running. +** +** INVARIANTS: +** +** {F16243} The [sqlite3_user_data(C)] interface returns a copy of the +** P pointer from the [sqlite3_create_function(D,X,N,E,P,F,S,L)] +** or [sqlite3_create_function16(D,X,N,E,P,F,S,L)] call that +** registered the SQL function associated with +** [sqlite3_context] C. +*/ +SQLITE_API void *sqlite3_user_data(sqlite3_context*); + +/* +** CAPI3REF: Database Connection For Functions {F16250} +** +** The sqlite3_context_db_handle() interface returns a copy of +** the pointer to the [database connection] (the 1st parameter) +** of the the [sqlite3_create_function()] +** and [sqlite3_create_function16()] routines that originally +** registered the application defined function. +** +** INVARIANTS: +** +** {F16253} The [sqlite3_context_db_handle(C)] interface returns a copy of the +** D pointer from the [sqlite3_create_function(D,X,N,E,P,F,S,L)] +** or [sqlite3_create_function16(D,X,N,E,P,F,S,L)] call that +** registered the SQL function associated with +** [sqlite3_context] C. +*/ +SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); + +/* +** CAPI3REF: Function Auxiliary Data {F16270} +** +** The following two functions may be used by scalar SQL functions to +** associate meta-data with argument values. If the same value is passed to +** multiple invocations of the same SQL function during query execution, under +** some circumstances the associated meta-data may be preserved. This may +** be used, for example, to add a regular-expression matching scalar +** function. The compiled version of the regular expression is stored as +** meta-data associated with the SQL value passed as the regular expression +** pattern. The compiled regular expression can be reused on multiple +** invocations of the same function so that the original pattern string +** does not need to be recompiled on each invocation. +** +** The sqlite3_get_auxdata() interface returns a pointer to the meta-data +** associated by the sqlite3_set_auxdata() function with the Nth argument +** value to the application-defined function. +** If no meta-data has been ever been set for the Nth +** argument of the function, or if the cooresponding function parameter +** has changed since the meta-data was set, then sqlite3_get_auxdata() +** returns a NULL pointer. +** +** The sqlite3_set_auxdata() interface saves the meta-data +** pointed to by its 3rd parameter as the meta-data for the N-th +** argument of the application-defined function. Subsequent +** calls to sqlite3_get_auxdata() might return this data, if it has +** not been destroyed. +** If it is not NULL, SQLite will invoke the destructor +** function given by the 4th parameter to sqlite3_set_auxdata() on +** the meta-data when the corresponding function parameter changes +** or when the SQL statement completes, whichever comes first. +** +** SQLite is free to call the destructor and drop meta-data on +** any parameter of any function at any time. The only guarantee +** is that the destructor will be called before the metadata is +** dropped. +** +** In practice, meta-data is preserved between function calls for +** expressions that are constant at compile time. This includes literal +** values and SQL variables. +** +** These routines must be called from the same thread in which +** the SQL function is running. +** +** INVARIANTS: +** +** {F16272} The [sqlite3_get_auxdata(C,N)] interface returns a pointer +** to metadata associated with the Nth parameter of the SQL function +** whose context is C, or NULL if there is no metadata associated +** with that parameter. +** +** {F16274} The [sqlite3_set_auxdata(C,N,P,D)] interface assigns a metadata +** pointer P to the Nth parameter of the SQL function with context +** C. +** +** {F16276} SQLite will invoke the destructor D with a single argument +** which is the metadata pointer P following a call to +** [sqlite3_set_auxdata(C,N,P,D)] when SQLite ceases to hold +** the metadata. +** +** {F16277} SQLite ceases to hold metadata for an SQL function parameter +** when the value of that parameter changes. +** +** {F16278} When [sqlite3_set_auxdata(C,N,P,D)] is invoked, the destructor +** is called for any prior metadata associated with the same function +** context C and parameter N. +** +** {F16279} SQLite will call destructors for any metadata it is holding +** in a particular [prepared statement] S when either +** [sqlite3_reset(S)] or [sqlite3_finalize(S)] is called. +*/ +SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N); +SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); + + +/* +** CAPI3REF: Constants Defining Special Destructor Behavior {F10280} +** +** These are special value for the destructor that is passed in as the +** final argument to routines like [sqlite3_result_blob()]. If the destructor +** argument is SQLITE_STATIC, it means that the content pointer is constant +** and will never change. It does not need to be destroyed. The +** SQLITE_TRANSIENT value means that the content will likely change in +** the near future and that SQLite should make its own private copy of +** the content before returning. +** +** The typedef is necessary to work around problems in certain +** C++ compilers. See ticket #2191. +*/ +typedef void (*sqlite3_destructor_type)(void*); +#define SQLITE_STATIC ((sqlite3_destructor_type)0) +#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) + +/* +** CAPI3REF: Setting The Result Of An SQL Function {F16400} +** +** These routines are used by the xFunc or xFinal callbacks that +** implement SQL functions and aggregates. See +** [sqlite3_create_function()] and [sqlite3_create_function16()] +** for additional information. +** +** These functions work very much like the +** [sqlite3_bind_blob | sqlite3_bind_*] family of functions used +** to bind values to host parameters in prepared statements. +** Refer to the +** [sqlite3_bind_blob | sqlite3_bind_* documentation] for +** additional information. +** +** The sqlite3_result_blob() interface sets the result from +** an application defined function to be the BLOB whose content is pointed +** to by the second parameter and which is N bytes long where N is the +** third parameter. +** The sqlite3_result_zeroblob() inerfaces set the result of +** the application defined function to be a BLOB containing all zero +** bytes and N bytes in size, where N is the value of the 2nd parameter. +** +** The sqlite3_result_double() interface sets the result from +** an application defined function to be a floating point value specified +** by its 2nd argument. +** +** The sqlite3_result_error() and sqlite3_result_error16() functions +** cause the implemented SQL function to throw an exception. +** SQLite uses the string pointed to by the +** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16() +** as the text of an error message. SQLite interprets the error +** message string from sqlite3_result_error() as UTF8. SQLite +** interprets the string from sqlite3_result_error16() as UTF16 in native +** byte order. If the third parameter to sqlite3_result_error() +** or sqlite3_result_error16() is negative then SQLite takes as the error +** message all text up through the first zero character. +** If the third parameter to sqlite3_result_error() or +** sqlite3_result_error16() is non-negative then SQLite takes that many +** bytes (not characters) from the 2nd parameter as the error message. +** The sqlite3_result_error() and sqlite3_result_error16() +** routines make a copy private copy of the error message text before +** they return. Hence, the calling function can deallocate or +** modify the text after they return without harm. +** The sqlite3_result_error_code() function changes the error code +** returned by SQLite as a result of an error in a function. By default, +** the error code is SQLITE_ERROR. A subsequent call to sqlite3_result_error() +** or sqlite3_result_error16() resets the error code to SQLITE_ERROR. +** +** The sqlite3_result_toobig() interface causes SQLite +** to throw an error indicating that a string or BLOB is to long +** to represent. The sqlite3_result_nomem() interface +** causes SQLite to throw an exception indicating that the a +** memory allocation failed. +** +** The sqlite3_result_int() interface sets the return value +** of the application-defined function to be the 32-bit signed integer +** value given in the 2nd argument. +** The sqlite3_result_int64() interface sets the return value +** of the application-defined function to be the 64-bit signed integer +** value given in the 2nd argument. +** +** The sqlite3_result_null() interface sets the return value +** of the application-defined function to be NULL. +** +** The sqlite3_result_text(), sqlite3_result_text16(), +** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces +** set the return value of the application-defined function to be +** a text string which is represented as UTF-8, UTF-16 native byte order, +** UTF-16 little endian, or UTF-16 big endian, respectively. +** SQLite takes the text result from the application from +** the 2nd parameter of the sqlite3_result_text* interfaces. +** If the 3rd parameter to the sqlite3_result_text* interfaces +** is negative, then SQLite takes result text from the 2nd parameter +** through the first zero character. +** If the 3rd parameter to the sqlite3_result_text* interfaces +** is non-negative, then as many bytes (not characters) of the text +** pointed to by the 2nd parameter are taken as the application-defined +** function result. +** If the 4th parameter to the sqlite3_result_text* interfaces +** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that +** function as the destructor on the text or blob result when it has +** finished using that result. +** If the 4th parameter to the sqlite3_result_text* interfaces +** or sqlite3_result_blob is the special constant SQLITE_STATIC, then +** SQLite assumes that the text or blob result is constant space and +** does not copy the space or call a destructor when it has +** finished using that result. +** If the 4th parameter to the sqlite3_result_text* interfaces +** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT +** then SQLite makes a copy of the result into space obtained from +** from [sqlite3_malloc()] before it returns. +** +** The sqlite3_result_value() interface sets the result of +** the application-defined function to be a copy the +** [unprotected sqlite3_value] object specified by the 2nd parameter. The +** sqlite3_result_value() interface makes a copy of the [sqlite3_value] +** so that [sqlite3_value] specified in the parameter may change or +** be deallocated after sqlite3_result_value() returns without harm. +** A [protected sqlite3_value] object may always be used where an +** [unprotected sqlite3_value] object is required, so either +** kind of [sqlite3_value] object can be used with this interface. +** +** If these routines are called from within the different thread +** than the one containing the application-defined function that recieved +** the [sqlite3_context] pointer, the results are undefined. +** +** INVARIANTS: +** +** {F16403} The default return value from any SQL function is NULL. +** +** {F16406} The [sqlite3_result_blob(C,V,N,D)] interface changes the +** return value of function C to be a blob that is N bytes +** in length and with content pointed to by V. +** +** {F16409} The [sqlite3_result_double(C,V)] interface changes the +** return value of function C to be the floating point value V. +** +** {F16412} The [sqlite3_result_error(C,V,N)] interface changes the return +** value of function C to be an exception with error code +** [SQLITE_ERROR] and a UTF8 error message copied from V up to the +** first zero byte or until N bytes are read if N is positive. +** +** {F16415} The [sqlite3_result_error16(C,V,N)] interface changes the return +** value of function C to be an exception with error code +** [SQLITE_ERROR] and a UTF16 native byte order error message +** copied from V up to the first zero terminator or until N bytes +** are read if N is positive. +** +** {F16418} The [sqlite3_result_error_toobig(C)] interface changes the return +** value of the function C to be an exception with error code +** [SQLITE_TOOBIG] and an appropriate error message. +** +** {F16421} The [sqlite3_result_error_nomem(C)] interface changes the return +** value of the function C to be an exception with error code +** [SQLITE_NOMEM] and an appropriate error message. +** +** {F16424} The [sqlite3_result_error_code(C,E)] interface changes the return +** value of the function C to be an exception with error code E. +** The error message text is unchanged. +** +** {F16427} The [sqlite3_result_int(C,V)] interface changes the +** return value of function C to be the 32-bit integer value V. +** +** {F16430} The [sqlite3_result_int64(C,V)] interface changes the +** return value of function C to be the 64-bit integer value V. +** +** {F16433} The [sqlite3_result_null(C)] interface changes the +** return value of function C to be NULL. +** +** {F16436} The [sqlite3_result_text(C,V,N,D)] interface changes the +** return value of function C to be the UTF8 string +** V up to the first zero if N is negative +** or the first N bytes of V if N is non-negative. +** +** {F16439} The [sqlite3_result_text16(C,V,N,D)] interface changes the +** return value of function C to be the UTF16 native byte order +** string V up to the first zero if N is +** negative or the first N bytes of V if N is non-negative. +** +** {F16442} The [sqlite3_result_text16be(C,V,N,D)] interface changes the +** return value of function C to be the UTF16 big-endian +** string V up to the first zero if N is +** is negative or the first N bytes or V if N is non-negative. +** +** {F16445} The [sqlite3_result_text16le(C,V,N,D)] interface changes the +** return value of function C to be the UTF16 little-endian +** string V up to the first zero if N is +** negative or the first N bytes of V if N is non-negative. +** +** {F16448} The [sqlite3_result_value(C,V)] interface changes the +** return value of function C to be [unprotected sqlite3_value] +** object V. +** +** {F16451} The [sqlite3_result_zeroblob(C,N)] interface changes the +** return value of function C to be an N-byte blob of all zeros. +** +** {F16454} The [sqlite3_result_error()] and [sqlite3_result_error16()] +** interfaces make a copy of their error message strings before +** returning. +** +** {F16457} If the D destructor parameter to [sqlite3_result_blob(C,V,N,D)], +** [sqlite3_result_text(C,V,N,D)], [sqlite3_result_text16(C,V,N,D)], +** [sqlite3_result_text16be(C,V,N,D)], or +** [sqlite3_result_text16le(C,V,N,D)] is the constant [SQLITE_STATIC] +** then no destructor is ever called on the pointer V and SQLite +** assumes that V is immutable. +** +** {F16460} If the D destructor parameter to [sqlite3_result_blob(C,V,N,D)], +** [sqlite3_result_text(C,V,N,D)], [sqlite3_result_text16(C,V,N,D)], +** [sqlite3_result_text16be(C,V,N,D)], or +** [sqlite3_result_text16le(C,V,N,D)] is the constant +** [SQLITE_TRANSIENT] then the interfaces makes a copy of the +** content of V and retains the copy. +** +** {F16463} If the D destructor parameter to [sqlite3_result_blob(C,V,N,D)], +** [sqlite3_result_text(C,V,N,D)], [sqlite3_result_text16(C,V,N,D)], +** [sqlite3_result_text16be(C,V,N,D)], or +** [sqlite3_result_text16le(C,V,N,D)] is some value other than +** the constants [SQLITE_STATIC] and [SQLITE_TRANSIENT] then +** SQLite will invoke the destructor D with V as its only argument +** when it has finished with the V value. +*/ +SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_double(sqlite3_context*, double); +SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int); +SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int); +SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*); +SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*); +SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int); +SQLITE_API void sqlite3_result_int(sqlite3_context*, int); +SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); +SQLITE_API void sqlite3_result_null(sqlite3_context*); +SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*); +SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n); + +/* +** CAPI3REF: Define New Collating Sequences {F16600} +** +** These functions are used to add new collation sequences to the +** [sqlite3*] handle specified as the first argument. +** +** The name of the new collation sequence is specified as a UTF-8 string +** for sqlite3_create_collation() and sqlite3_create_collation_v2() +** and a UTF-16 string for sqlite3_create_collation16(). In all cases +** the name is passed as the second function argument. +** +** The third argument may be one of the constants [SQLITE_UTF8], +** [SQLITE_UTF16LE] or [SQLITE_UTF16BE], indicating that the user-supplied +** routine expects to be passed pointers to strings encoded using UTF-8, +** UTF-16 little-endian or UTF-16 big-endian respectively. The +** third argument might also be [SQLITE_UTF16_ALIGNED] to indicate that +** the routine expects pointers to 16-bit word aligned strings +** of UTF16 in the native byte order of the host computer. +** +** A pointer to the user supplied routine must be passed as the fifth +** argument. If it is NULL, this is the same as deleting the collation +** sequence (so that SQLite cannot call it anymore). +** Each time the application +** supplied function is invoked, it is passed a copy of the void* passed as +** the fourth argument to sqlite3_create_collation() or +** sqlite3_create_collation16() as its first parameter. +** +** The remaining arguments to the application-supplied routine are two strings, +** each represented by a (length, data) pair and encoded in the encoding +** that was passed as the third argument when the collation sequence was +** registered. {END} The application defined collation routine should +** return negative, zero or positive if +** the first string is less than, equal to, or greater than the second +** string. i.e. (STRING1 - STRING2). +** +** The sqlite3_create_collation_v2() works like sqlite3_create_collation() +** excapt that it takes an extra argument which is a destructor for +** the collation. The destructor is called when the collation is +** destroyed and is passed a copy of the fourth parameter void* pointer +** of the sqlite3_create_collation_v2(). +** Collations are destroyed when +** they are overridden by later calls to the collation creation functions +** or when the [sqlite3*] database handle is closed using [sqlite3_close()]. +** +** INVARIANTS: +** +** {F16603} A successful call to the +** [sqlite3_create_collation_v2(B,X,E,P,F,D)] interface +** registers function F as the comparison function used to +** implement collation X on [database connection] B for +** databases having encoding E. +** +** {F16604} SQLite understands the X parameter to +** [sqlite3_create_collation_v2(B,X,E,P,F,D)] as a zero-terminated +** UTF-8 string in which case is ignored for ASCII characters and +** is significant for non-ASCII characters. +** +** {F16606} Successive calls to [sqlite3_create_collation_v2(B,X,E,P,F,D)] +** with the same values for B, X, and E, override prior values +** of P, F, and D. +** +** {F16609} The destructor D in [sqlite3_create_collation_v2(B,X,E,P,F,D)] +** is not NULL then it is called with argument P when the +** collating function is dropped by SQLite. +** +** {F16612} A collating function is dropped when it is overloaded. +** +** {F16615} A collating function is dropped when the database connection +** is closed using [sqlite3_close()]. +** +** {F16618} The pointer P in [sqlite3_create_collation_v2(B,X,E,P,F,D)] +** is passed through as the first parameter to the comparison +** function F for all subsequent invocations of F. +** +** {F16621} A call to [sqlite3_create_collation(B,X,E,P,F)] is exactly +** the same as a call to [sqlite3_create_collation_v2()] with +** the same parameters and a NULL destructor. +** +** {F16624} Following a [sqlite3_create_collation_v2(B,X,E,P,F,D)], +** SQLite uses the comparison function F for all text comparison +** operations on [database connection] B on text values that +** use the collating sequence name X. +** +** {F16627} The [sqlite3_create_collation16(B,X,E,P,F)] works the same +** as [sqlite3_create_collation(B,X,E,P,F)] except that the +** collation name X is understood as UTF-16 in native byte order +** instead of UTF-8. +** +** {F16630} When multiple comparison functions are available for the same +** collating sequence, SQLite chooses the one whose text encoding +** requires the least amount of conversion from the default +** text encoding of the database. +*/ +SQLITE_API int sqlite3_create_collation( + sqlite3*, + const char *zName, + int eTextRep, + void*, + int(*xCompare)(void*,int,const void*,int,const void*) +); +SQLITE_API int sqlite3_create_collation_v2( + sqlite3*, + const char *zName, + int eTextRep, + void*, + int(*xCompare)(void*,int,const void*,int,const void*), + void(*xDestroy)(void*) +); +SQLITE_API int sqlite3_create_collation16( + sqlite3*, + const char *zName, + int eTextRep, + void*, + int(*xCompare)(void*,int,const void*,int,const void*) +); + +/* +** CAPI3REF: Collation Needed Callbacks {F16700} +** +** To avoid having to register all collation sequences before a database +** can be used, a single callback function may be registered with the +** database handle to be called whenever an undefined collation sequence is +** required. +** +** If the function is registered using the sqlite3_collation_needed() API, +** then it is passed the names of undefined collation sequences as strings +** encoded in UTF-8. {F16703} If sqlite3_collation_needed16() is used, the names +** are passed as UTF-16 in machine native byte order. A call to either +** function replaces any existing callback. +** +** When the callback is invoked, the first argument passed is a copy +** of the second argument to sqlite3_collation_needed() or +** sqlite3_collation_needed16(). The second argument is the database +** handle. The third argument is one of [SQLITE_UTF8], +** [SQLITE_UTF16BE], or [SQLITE_UTF16LE], indicating the most +** desirable form of the collation sequence function required. +** The fourth parameter is the name of the +** required collation sequence. +** +** The callback function should register the desired collation using +** [sqlite3_create_collation()], [sqlite3_create_collation16()], or +** [sqlite3_create_collation_v2()]. +** +** INVARIANTS: +** +** {F16702} A successful call to [sqlite3_collation_needed(D,P,F)] +** or [sqlite3_collation_needed16(D,P,F)] causes +** the [database connection] D to invoke callback F with first +** parameter P whenever it needs a comparison function for a +** collating sequence that it does not know about. +** +** {F16704} Each successful call to [sqlite3_collation_needed()] or +** [sqlite3_collation_needed16()] overrides the callback registered +** on the same [database connection] by prior calls to either +** interface. +** +** {F16706} The name of the requested collating function passed in the +** 4th parameter to the callback is in UTF-8 if the callback +** was registered using [sqlite3_collation_needed()] and +** is in UTF-16 native byte order if the callback was +** registered using [sqlite3_collation_needed16()]. +** +** +*/ +SQLITE_API int sqlite3_collation_needed( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const char*) +); +SQLITE_API int sqlite3_collation_needed16( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const void*) +); + +/* +** Specify the key for an encrypted database. This routine should be +** called right after sqlite3_open(). +** +** The code to implement this API is not available in the public release +** of SQLite. +*/ +SQLITE_API int sqlite3_key( + sqlite3 *db, /* Database to be rekeyed */ + const void *pKey, int nKey /* The key */ +); + +/* +** Change the key on an open database. If the current database is not +** encrypted, this routine will encrypt it. If pNew==0 or nNew==0, the +** database is decrypted. +** +** The code to implement this API is not available in the public release +** of SQLite. +*/ +SQLITE_API int sqlite3_rekey( + sqlite3 *db, /* Database to be rekeyed */ + const void *pKey, int nKey /* The new key */ +); + +/* +** CAPI3REF: Suspend Execution For A Short Time {F10530} +** +** The sqlite3_sleep() function +** causes the current thread to suspend execution +** for at least a number of milliseconds specified in its parameter. +** +** If the operating system does not support sleep requests with +** millisecond time resolution, then the time will be rounded up to +** the nearest second. The number of milliseconds of sleep actually +** requested from the operating system is returned. +** +** SQLite implements this interface by calling the xSleep() +** method of the default [sqlite3_vfs] object. +** +** INVARIANTS: +** +** {F10533} The [sqlite3_sleep(M)] interface invokes the xSleep +** method of the default [sqlite3_vfs|VFS] in order to +** suspend execution of the current thread for at least +** M milliseconds. +** +** {F10536} The [sqlite3_sleep(M)] interface returns the number of +** milliseconds of sleep actually requested of the operating +** system, which might be larger than the parameter M. +*/ +SQLITE_API int sqlite3_sleep(int); + +/* +** CAPI3REF: Name Of The Folder Holding Temporary Files {F10310} +** +** If this global variable is made to point to a string which is +** the name of a folder (a.ka. directory), then all temporary files +** created by SQLite will be placed in that directory. If this variable +** is NULL pointer, then SQLite does a search for an appropriate temporary +** file directory. +** +** It is not safe to modify this variable once a database connection +** has been opened. It is intended that this variable be set once +** as part of process initialization and before any SQLite interface +** routines have been call and remain unchanged thereafter. +*/ +SQLITE_API char *sqlite3_temp_directory; + +/* +** CAPI3REF: Test To See If The Database Is In Auto-Commit Mode {F12930} +** +** The sqlite3_get_autocommit() interfaces returns non-zero or +** zero if the given database connection is or is not in autocommit mode, +** respectively. Autocommit mode is on +** by default. Autocommit mode is disabled by a [BEGIN] statement. +** Autocommit mode is reenabled by a [COMMIT] or [ROLLBACK]. +** +** If certain kinds of errors occur on a statement within a multi-statement +** transactions (errors including [SQLITE_FULL], [SQLITE_IOERR], +** [SQLITE_NOMEM], [SQLITE_BUSY], and [SQLITE_INTERRUPT]) then the +** transaction might be rolled back automatically. The only way to +** find out if SQLite automatically rolled back the transaction after +** an error is to use this function. +** +** INVARIANTS: +** +** {F12931} The [sqlite3_get_autocommit(D)] interface returns non-zero or +** zero if the [database connection] D is or is not in autocommit +** mode, respectively. +** +** {F12932} Autocommit mode is on by default. +** +** {F12933} Autocommit mode is disabled by a successful [BEGIN] statement. +** +** {F12934} Autocommit mode is enabled by a successful [COMMIT] or [ROLLBACK] +** statement. +** +** +** LIMITATIONS: +*** +** {U12936} If another thread changes the autocommit status of the database +** connection while this routine is running, then the return value +** is undefined. +*/ +SQLITE_API int sqlite3_get_autocommit(sqlite3*); + +/* +** CAPI3REF: Find The Database Handle Of A Prepared Statement {F13120} +** +** The sqlite3_db_handle interface +** returns the [sqlite3*] database handle to which a +** [prepared statement] belongs. +** The database handle returned by sqlite3_db_handle +** is the same database handle that was +** the first argument to the [sqlite3_prepare_v2()] or its variants +** that was used to create the statement in the first place. +** +** INVARIANTS: +** +** {F13123} The [sqlite3_db_handle(S)] interface returns a pointer +** to the [database connection] associated with +** [prepared statement] S. +*/ +SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); + + +/* +** CAPI3REF: Commit And Rollback Notification Callbacks {F12950} +** +** The sqlite3_commit_hook() interface registers a callback +** function to be invoked whenever a transaction is committed. +** Any callback set by a previous call to sqlite3_commit_hook() +** for the same database connection is overridden. +** The sqlite3_rollback_hook() interface registers a callback +** function to be invoked whenever a transaction is committed. +** Any callback set by a previous call to sqlite3_commit_hook() +** for the same database connection is overridden. +** The pArg argument is passed through +** to the callback. If the callback on a commit hook function +** returns non-zero, then the commit is converted into a rollback. +** +** If another function was previously registered, its +** pArg value is returned. Otherwise NULL is returned. +** +** Registering a NULL function disables the callback. +** +** For the purposes of this API, a transaction is said to have been +** rolled back if an explicit "ROLLBACK" statement is executed, or +** an error or constraint causes an implicit rollback to occur. +** The rollback callback is not invoked if a transaction is +** automatically rolled back because the database connection is closed. +** The rollback callback is not invoked if a transaction is +** rolled back because a commit callback returned non-zero. +** Check on this +** +** These are experimental interfaces and are subject to change. +** +** INVARIANTS: +** +** {F12951} The [sqlite3_commit_hook(D,F,P)] interface registers the +** callback function F to be invoked with argument P whenever +** a transaction commits on [database connection] D. +** +** {F12952} The [sqlite3_commit_hook(D,F,P)] interface returns the P +** argument from the previous call with the same +** [database connection ] D , or NULL on the first call +** for a particular [database connection] D. +** +** {F12953} Each call to [sqlite3_commit_hook()] overwrites the callback +** registered by prior calls. +** +** {F12954} If the F argument to [sqlite3_commit_hook(D,F,P)] is NULL +** then the commit hook callback is cancelled and no callback +** is invoked when a transaction commits. +** +** {F12955} If the commit callback returns non-zero then the commit is +** converted into a rollback. +** +** {F12961} The [sqlite3_rollback_hook(D,F,P)] interface registers the +** callback function F to be invoked with argument P whenever +** a transaction rolls back on [database connection] D. +** +** {F12962} The [sqlite3_rollback_hook(D,F,P)] interface returns the P +** argument from the previous call with the same +** [database connection ] D , or NULL on the first call +** for a particular [database connection] D. +** +** {F12963} Each call to [sqlite3_rollback_hook()] overwrites the callback +** registered by prior calls. +** +** {F12964} If the F argument to [sqlite3_rollback_hook(D,F,P)] is NULL +** then the rollback hook callback is cancelled and no callback +** is invoked when a transaction rolls back. +*/ +SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); +SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); + +/* +** CAPI3REF: Data Change Notification Callbacks {F12970} +** +** The sqlite3_update_hook() interface +** registers a callback function with the database connection identified by the +** first argument to be invoked whenever a row is updated, inserted or deleted. +** Any callback set by a previous call to this function for the same +** database connection is overridden. +** +** The second argument is a pointer to the function to invoke when a +** row is updated, inserted or deleted. +** The first argument to the callback is +** a copy of the third argument to sqlite3_update_hook(). +** The second callback +** argument is one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], +** depending on the operation that caused the callback to be invoked. +** The third and +** fourth arguments to the callback contain pointers to the database and +** table name containing the affected row. +** The final callback parameter is +** the rowid of the row. +** In the case of an update, this is the rowid after +** the update takes place. +** +** The update hook is not invoked when internal system tables are +** modified (i.e. sqlite_master and sqlite_sequence). +** +** If another function was previously registered, its pArg value +** is returned. Otherwise NULL is returned. +** +** INVARIANTS: +** +** {F12971} The [sqlite3_update_hook(D,F,P)] interface causes callback +** function F to be invoked with first parameter P whenever +** a table row is modified, inserted, or deleted on +** [database connection] D. +** +** {F12973} The [sqlite3_update_hook(D,F,P)] interface returns the value +** of P for the previous call on the same [database connection] D, +** or NULL for the first call. +** +** {F12975} If the update hook callback F in [sqlite3_update_hook(D,F,P)] +** is NULL then the no update callbacks are made. +** +** {F12977} Each call to [sqlite3_update_hook(D,F,P)] overrides prior calls +** to the same interface on the same [database connection] D. +** +** {F12979} The update hook callback is not invoked when internal system +** tables such as sqlite_master and sqlite_sequence are modified. +** +** {F12981} The second parameter to the update callback +** is one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], +** depending on the operation that caused the callback to be invoked. +** +** {F12983} The third and fourth arguments to the callback contain pointers +** to zero-terminated UTF-8 strings which are the names of the +** database and table that is being updated. + +** {F12985} The final callback parameter is the rowid of the row after +** the change occurs. +*/ +SQLITE_API void *sqlite3_update_hook( + sqlite3*, + void(*)(void *,int ,char const *,char const *,sqlite3_int64), + void* +); + +/* +** CAPI3REF: Enable Or Disable Shared Pager Cache {F10330} +** +** This routine enables or disables the sharing of the database cache +** and schema data structures between connections to the same database. +** Sharing is enabled if the argument is true and disabled if the argument +** is false. +** +** Cache sharing is enabled and disabled +** for an entire process. {END} This is a change as of SQLite version 3.5.0. +** In prior versions of SQLite, sharing was +** enabled or disabled for each thread separately. +** +** The cache sharing mode set by this interface effects all subsequent +** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()]. +** Existing database connections continue use the sharing mode +** that was in effect at the time they were opened. +** +** Virtual tables cannot be used with a shared cache. When shared +** cache is enabled, the [sqlite3_create_module()] API used to register +** virtual tables will always return an error. +** +** This routine returns [SQLITE_OK] if shared cache was +** enabled or disabled successfully. An [error code] +** is returned otherwise. +** +** Shared cache is disabled by default. But this might change in +** future releases of SQLite. Applications that care about shared +** cache setting should set it explicitly. +** +** INVARIANTS: +** +** {F10331} A successful invocation of [sqlite3_enable_shared_cache(B)] +** will enable or disable shared cache mode for any subsequently +** created [database connection] in the same process. +** +** {F10336} When shared cache is enabled, the [sqlite3_create_module()] +** interface will always return an error. +** +** {F10337} The [sqlite3_enable_shared_cache(B)] interface returns +** [SQLITE_OK] if shared cache was enabled or disabled successfully. +** +** {F10339} Shared cache is disabled by default. +*/ +SQLITE_API int sqlite3_enable_shared_cache(int); + +/* +** CAPI3REF: Attempt To Free Heap Memory {F17340} +** +** The sqlite3_release_memory() interface attempts to +** free N bytes of heap memory by deallocating non-essential memory +** allocations held by the database labrary. {END} Memory used +** to cache database pages to improve performance is an example of +** non-essential memory. Sqlite3_release_memory() returns +** the number of bytes actually freed, which might be more or less +** than the amount requested. +** +** INVARIANTS: +** +** {F17341} The [sqlite3_release_memory(N)] interface attempts to +** free N bytes of heap memory by deallocating non-essential +** memory allocations held by the database labrary. +** +** {F16342} The [sqlite3_release_memory(N)] returns the number +** of bytes actually freed, which might be more or less +** than the amount requested. +*/ +SQLITE_API int sqlite3_release_memory(int); + +/* +** CAPI3REF: Impose A Limit On Heap Size {F17350} +** +** The sqlite3_soft_heap_limit() interface +** places a "soft" limit on the amount of heap memory that may be allocated +** by SQLite. If an internal allocation is requested +** that would exceed the soft heap limit, [sqlite3_release_memory()] is +** invoked one or more times to free up some space before the allocation +** is made. +** +** The limit is called "soft", because if +** [sqlite3_release_memory()] cannot +** free sufficient memory to prevent the limit from being exceeded, +** the memory is allocated anyway and the current operation proceeds. +** +** A negative or zero value for N means that there is no soft heap limit and +** [sqlite3_release_memory()] will only be called when memory is exhausted. +** The default value for the soft heap limit is zero. +** +** SQLite makes a best effort to honor the soft heap limit. +** But if the soft heap limit cannot honored, execution will +** continue without error or notification. This is why the limit is +** called a "soft" limit. It is advisory only. +** +** Prior to SQLite version 3.5.0, this routine only constrained the memory +** allocated by a single thread - the same thread in which this routine +** runs. Beginning with SQLite version 3.5.0, the soft heap limit is +** applied to all threads. The value specified for the soft heap limit +** is an upper bound on the total memory allocation for all threads. In +** version 3.5.0 there is no mechanism for limiting the heap usage for +** individual threads. +** +** INVARIANTS: +** +** {F16351} The [sqlite3_soft_heap_limit(N)] interface places a soft limit +** of N bytes on the amount of heap memory that may be allocated +** using [sqlite3_malloc()] or [sqlite3_realloc()] at any point +** in time. +** +** {F16352} If a call to [sqlite3_malloc()] or [sqlite3_realloc()] would +** cause the total amount of allocated memory to exceed the +** soft heap limit, then [sqlite3_release_memory()] is invoked +** in an attempt to reduce the memory usage prior to proceeding +** with the memory allocation attempt. +** +** {F16353} Calls to [sqlite3_malloc()] or [sqlite3_realloc()] that trigger +** attempts to reduce memory usage through the soft heap limit +** mechanism continue even if the attempt to reduce memory +** usage is unsuccessful. +** +** {F16354} A negative or zero value for N in a call to +** [sqlite3_soft_heap_limit(N)] means that there is no soft +** heap limit and [sqlite3_release_memory()] will only be +** called when memory is completely exhausted. +** +** {F16355} The default value for the soft heap limit is zero. +** +** {F16358} Each call to [sqlite3_soft_heap_limit(N)] overrides the +** values set by all prior calls. +*/ +SQLITE_API void sqlite3_soft_heap_limit(int); + +/* +** CAPI3REF: Extract Metadata About A Column Of A Table {F12850} +** +** This routine +** returns meta-data about a specific column of a specific database +** table accessible using the connection handle passed as the first function +** argument. +** +** The column is identified by the second, third and fourth parameters to +** this function. The second parameter is either the name of the database +** (i.e. "main", "temp" or an attached database) containing the specified +** table or NULL. If it is NULL, then all attached databases are searched +** for the table using the same algorithm as the database engine uses to +** resolve unqualified table references. +** +** The third and fourth parameters to this function are the table and column +** name of the desired column, respectively. Neither of these parameters +** may be NULL. +** +** Meta information is returned by writing to the memory locations passed as +** the 5th and subsequent parameters to this function. Any of these +** arguments may be NULL, in which case the corresponding element of meta +** information is ommitted. +** +**
    +** Parameter     Output Type      Description
    +** -----------------------------------
    +**
    +**   5th         const char*      Data type
    +**   6th         const char*      Name of the default collation sequence 
    +**   7th         int              True if the column has a NOT NULL constraint
    +**   8th         int              True if the column is part of the PRIMARY KEY
    +**   9th         int              True if the column is AUTOINCREMENT
    +** 
    +** +** +** The memory pointed to by the character pointers returned for the +** declaration type and collation sequence is valid only until the next +** call to any sqlite API function. +** +** If the specified table is actually a view, then an error is returned. +** +** If the specified column is "rowid", "oid" or "_rowid_" and an +** INTEGER PRIMARY KEY column has been explicitly declared, then the output +** parameters are set for the explicitly declared column. If there is no +** explicitly declared IPK column, then the output parameters are set as +** follows: +** +**
    +**     data type: "INTEGER"
    +**     collation sequence: "BINARY"
    +**     not null: 0
    +**     primary key: 1
    +**     auto increment: 0
    +** 
    +** +** This function may load one or more schemas from database files. If an +** error occurs during this process, or if the requested table or column +** cannot be found, an SQLITE error code is returned and an error message +** left in the database handle (to be retrieved using sqlite3_errmsg()). +** +** This API is only available if the library was compiled with the +** SQLITE_ENABLE_COLUMN_METADATA preprocessor symbol defined. +*/ +SQLITE_API int sqlite3_table_column_metadata( + sqlite3 *db, /* Connection handle */ + const char *zDbName, /* Database name or NULL */ + const char *zTableName, /* Table name */ + const char *zColumnName, /* Column name */ + char const **pzDataType, /* OUTPUT: Declared data type */ + char const **pzCollSeq, /* OUTPUT: Collation sequence name */ + int *pNotNull, /* OUTPUT: True if NOT NULL constraint exists */ + int *pPrimaryKey, /* OUTPUT: True if column part of PK */ + int *pAutoinc /* OUTPUT: True if column is auto-increment */ +); + +/* +** CAPI3REF: Load An Extension {F12600} +** +** {F12601} The sqlite3_load_extension() interface +** attempts to load an SQLite extension library contained in the file +** zFile. {F12602} The entry point is zProc. {F12603} zProc may be 0 +** in which case the name of the entry point defaults +** to "sqlite3_extension_init". +** +** {F12604} The sqlite3_load_extension() interface shall +** return [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. +** +** {F12605} +** If an error occurs and pzErrMsg is not 0, then the +** sqlite3_load_extension() interface shall attempt to fill *pzErrMsg with +** error message text stored in memory obtained from [sqlite3_malloc()]. +** {END} The calling function should free this memory +** by calling [sqlite3_free()]. +** +** {F12606} +** Extension loading must be enabled using [sqlite3_enable_load_extension()] +** prior to calling this API or an error will be returned. +*/ +SQLITE_API int sqlite3_load_extension( + sqlite3 *db, /* Load the extension into this database connection */ + const char *zFile, /* Name of the shared library containing extension */ + const char *zProc, /* Entry point. Derived from zFile if 0 */ + char **pzErrMsg /* Put error message here if not 0 */ +); + +/* +** CAPI3REF: Enable Or Disable Extension Loading {F12620} +** +** So as not to open security holes in older applications that are +** unprepared to deal with extension loading, and as a means of disabling +** extension loading while evaluating user-entered SQL, the following +** API is provided to turn the [sqlite3_load_extension()] mechanism on and +** off. {F12622} It is off by default. {END} See ticket #1863. +** +** {F12621} Call the sqlite3_enable_load_extension() routine +** with onoff==1 to turn extension loading on +** and call it with onoff==0 to turn it back off again. {END} +*/ +SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); + +/* +** CAPI3REF: Make Arrangements To Automatically Load An Extension {F12640} +** +** {F12641} This function +** registers an extension entry point that is automatically invoked +** whenever a new database connection is opened using +** [sqlite3_open()], [sqlite3_open16()], or [sqlite3_open_v2()]. {END} +** +** This API can be invoked at program startup in order to register +** one or more statically linked extensions that will be available +** to all new database connections. +** +** {F12642} Duplicate extensions are detected so calling this routine multiple +** times with the same extension is harmless. +** +** {F12643} This routine stores a pointer to the extension in an array +** that is obtained from sqlite_malloc(). {END} If you run a memory leak +** checker on your program and it reports a leak because of this +** array, then invoke [sqlite3_reset_auto_extension()] prior +** to shutdown to free the memory. +** +** {F12644} Automatic extensions apply across all threads. {END} +** +** This interface is experimental and is subject to change or +** removal in future releases of SQLite. +*/ +SQLITE_API int sqlite3_auto_extension(void *xEntryPoint); + + +/* +** CAPI3REF: Reset Automatic Extension Loading {F12660} +** +** {F12661} This function disables all previously registered +** automatic extensions. {END} This +** routine undoes the effect of all prior [sqlite3_auto_extension()] +** calls. +** +** {F12662} This call disabled automatic extensions in all threads. {END} +** +** This interface is experimental and is subject to change or +** removal in future releases of SQLite. +*/ +SQLITE_API void sqlite3_reset_auto_extension(void); + + +/* +****** EXPERIMENTAL - subject to change without notice ************** +** +** The interface to the virtual-table mechanism is currently considered +** to be experimental. The interface might change in incompatible ways. +** If this is a problem for you, do not use the interface at this time. +** +** When the virtual-table mechanism stablizes, we will declare the +** interface fixed, support it indefinitely, and remove this comment. +*/ + +/* +** Structures used by the virtual table interface +*/ +typedef struct sqlite3_vtab sqlite3_vtab; +typedef struct sqlite3_index_info sqlite3_index_info; +typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor; +typedef struct sqlite3_module sqlite3_module; + +/* +** CAPI3REF: Virtual Table Object {F18000} +** KEYWORDS: sqlite3_module +** +** A module is a class of virtual tables. Each module is defined +** by an instance of the following structure. This structure consists +** mostly of methods for the module. +*/ +struct sqlite3_module { + int iVersion; + int (*xCreate)(sqlite3*, void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, char**); + int (*xConnect)(sqlite3*, void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, char**); + int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*); + int (*xDisconnect)(sqlite3_vtab *pVTab); + int (*xDestroy)(sqlite3_vtab *pVTab); + int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor); + int (*xClose)(sqlite3_vtab_cursor*); + int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr, + int argc, sqlite3_value **argv); + int (*xNext)(sqlite3_vtab_cursor*); + int (*xEof)(sqlite3_vtab_cursor*); + int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int); + int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64 *pRowid); + int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *); + int (*xBegin)(sqlite3_vtab *pVTab); + int (*xSync)(sqlite3_vtab *pVTab); + int (*xCommit)(sqlite3_vtab *pVTab); + int (*xRollback)(sqlite3_vtab *pVTab); + int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName, + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + void **ppArg); + + int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); +}; + +/* +** CAPI3REF: Virtual Table Indexing Information {F18100} +** KEYWORDS: sqlite3_index_info +** +** The sqlite3_index_info structure and its substructures is used to +** pass information into and receive the reply from the xBestIndex +** method of an sqlite3_module. The fields under **Inputs** are the +** inputs to xBestIndex and are read-only. xBestIndex inserts its +** results into the **Outputs** fields. +** +** The aConstraint[] array records WHERE clause constraints of the +** form: +** +** column OP expr +** +** Where OP is =, <, <=, >, or >=. +** The particular operator is stored +** in aConstraint[].op. The index of the column is stored in +** aConstraint[].iColumn. aConstraint[].usable is TRUE if the +** expr on the right-hand side can be evaluated (and thus the constraint +** is usable) and false if it cannot. +** +** The optimizer automatically inverts terms of the form "expr OP column" +** and makes other simplifications to the WHERE clause in an attempt to +** get as many WHERE clause terms into the form shown above as possible. +** The aConstraint[] array only reports WHERE clause terms in the correct +** form that refer to the particular virtual table being queried. +** +** Information about the ORDER BY clause is stored in aOrderBy[]. +** Each term of aOrderBy records a column of the ORDER BY clause. +** +** The xBestIndex method must fill aConstraintUsage[] with information +** about what parameters to pass to xFilter. If argvIndex>0 then +** the right-hand side of the corresponding aConstraint[] is evaluated +** and becomes the argvIndex-th entry in argv. If aConstraintUsage[].omit +** is true, then the constraint is assumed to be fully handled by the +** virtual table and is not checked again by SQLite. +** +** The idxNum and idxPtr values are recorded and passed into xFilter. +** sqlite3_free() is used to free idxPtr if needToFreeIdxPtr is true. +** +** The orderByConsumed means that output from xFilter will occur in +** the correct order to satisfy the ORDER BY clause so that no separate +** sorting step is required. +** +** The estimatedCost value is an estimate of the cost of doing the +** particular lookup. A full scan of a table with N entries should have +** a cost of N. A binary search of a table of N entries should have a +** cost of approximately log(N). +*/ +struct sqlite3_index_info { + /* Inputs */ + int nConstraint; /* Number of entries in aConstraint */ + struct sqlite3_index_constraint { + int iColumn; /* Column on left-hand side of constraint */ + unsigned char op; /* Constraint operator */ + unsigned char usable; /* True if this constraint is usable */ + int iTermOffset; /* Used internally - xBestIndex should ignore */ + } *aConstraint; /* Table of WHERE clause constraints */ + int nOrderBy; /* Number of terms in the ORDER BY clause */ + struct sqlite3_index_orderby { + int iColumn; /* Column number */ + unsigned char desc; /* True for DESC. False for ASC. */ + } *aOrderBy; /* The ORDER BY clause */ + + /* Outputs */ + struct sqlite3_index_constraint_usage { + int argvIndex; /* if >0, constraint is part of argv to xFilter */ + unsigned char omit; /* Do not code a test for this constraint */ + } *aConstraintUsage; + int idxNum; /* Number used to identify the index */ + char *idxStr; /* String, possibly obtained from sqlite3_malloc */ + int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ + int orderByConsumed; /* True if output is already ordered */ + double estimatedCost; /* Estimated cost of using this index */ +}; +#define SQLITE_INDEX_CONSTRAINT_EQ 2 +#define SQLITE_INDEX_CONSTRAINT_GT 4 +#define SQLITE_INDEX_CONSTRAINT_LE 8 +#define SQLITE_INDEX_CONSTRAINT_LT 16 +#define SQLITE_INDEX_CONSTRAINT_GE 32 +#define SQLITE_INDEX_CONSTRAINT_MATCH 64 + +/* +** CAPI3REF: Register A Virtual Table Implementation {F18200} +** +** This routine is used to register a new module name with an SQLite +** connection. Module names must be registered before creating new +** virtual tables on the module, or before using preexisting virtual +** tables of the module. +*/ +SQLITE_API int sqlite3_create_module( + sqlite3 *db, /* SQLite connection to register module with */ + const char *zName, /* Name of the module */ + const sqlite3_module *, /* Methods for the module */ + void * /* Client data for xCreate/xConnect */ +); + +/* +** CAPI3REF: Register A Virtual Table Implementation {F18210} +** +** This routine is identical to the sqlite3_create_module() method above, +** except that it allows a destructor function to be specified. It is +** even more experimental than the rest of the virtual tables API. +*/ +SQLITE_API int sqlite3_create_module_v2( + sqlite3 *db, /* SQLite connection to register module with */ + const char *zName, /* Name of the module */ + const sqlite3_module *, /* Methods for the module */ + void *, /* Client data for xCreate/xConnect */ + void(*xDestroy)(void*) /* Module destructor function */ +); + +/* +** CAPI3REF: Virtual Table Instance Object {F18010} +** KEYWORDS: sqlite3_vtab +** +** Every module implementation uses a subclass of the following structure +** to describe a particular instance of the module. Each subclass will +** be tailored to the specific needs of the module implementation. The +** purpose of this superclass is to define certain fields that are common +** to all module implementations. +** +** Virtual tables methods can set an error message by assigning a +** string obtained from sqlite3_mprintf() to zErrMsg. The method should +** take care that any prior string is freed by a call to sqlite3_free() +** prior to assigning a new string to zErrMsg. After the error message +** is delivered up to the client application, the string will be automatically +** freed by sqlite3_free() and the zErrMsg field will be zeroed. Note +** that sqlite3_mprintf() and sqlite3_free() are used on the zErrMsg field +** since virtual tables are commonly implemented in loadable extensions which +** do not have access to sqlite3MPrintf() or sqlite3Free(). +*/ +struct sqlite3_vtab { + const sqlite3_module *pModule; /* The module for this virtual table */ + int nRef; /* Used internally */ + char *zErrMsg; /* Error message from sqlite3_mprintf() */ + /* Virtual table implementations will typically add additional fields */ +}; + +/* +** CAPI3REF: Virtual Table Cursor Object {F18020} +** KEYWORDS: sqlite3_vtab_cursor +** +** Every module implementation uses a subclass of the following structure +** to describe cursors that point into the virtual table and are used +** to loop through the virtual table. Cursors are created using the +** xOpen method of the module. Each module implementation will define +** the content of a cursor structure to suit its own needs. +** +** This superclass exists in order to define fields of the cursor that +** are common to all implementations. +*/ +struct sqlite3_vtab_cursor { + sqlite3_vtab *pVtab; /* Virtual table of this cursor */ + /* Virtual table implementations will typically add additional fields */ +}; + +/* +** CAPI3REF: Declare The Schema Of A Virtual Table {F18280} +** +** The xCreate and xConnect methods of a module use the following API +** to declare the format (the names and datatypes of the columns) of +** the virtual tables they implement. +*/ +SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zCreateTable); + +/* +** CAPI3REF: Overload A Function For A Virtual Table {F18300} +** +** Virtual tables can provide alternative implementations of functions +** using the xFindFunction method. But global versions of those functions +** must exist in order to be overloaded. +** +** This API makes sure a global version of a function with a particular +** name and number of parameters exists. If no such function exists +** before this API is called, a new function is created. The implementation +** of the new function always causes an exception to be thrown. So +** the new function is not good for anything by itself. Its only +** purpose is to be a place-holder function that can be overloaded +** by virtual tables. +** +** This API should be considered part of the virtual table interface, +** which is experimental and subject to change. +*/ +SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); + +/* +** The interface to the virtual-table mechanism defined above (back up +** to a comment remarkably similar to this one) is currently considered +** to be experimental. The interface might change in incompatible ways. +** If this is a problem for you, do not use the interface at this time. +** +** When the virtual-table mechanism stabilizes, we will declare the +** interface fixed, support it indefinitely, and remove this comment. +** +****** EXPERIMENTAL - subject to change without notice ************** +*/ + +/* +** CAPI3REF: A Handle To An Open BLOB {F17800} +** +** An instance of this object represents an open BLOB on which +** incremental I/O can be preformed. +** Objects of this type are created by +** [sqlite3_blob_open()] and destroyed by [sqlite3_blob_close()]. +** The [sqlite3_blob_read()] and [sqlite3_blob_write()] interfaces +** can be used to read or write small subsections of the blob. +** The [sqlite3_blob_bytes()] interface returns the size of the +** blob in bytes. +*/ +typedef struct sqlite3_blob sqlite3_blob; + +/* +** CAPI3REF: Open A BLOB For Incremental I/O {F17810} +** +** This interfaces opens a handle to the blob located +** in row iRow, column zColumn, table zTable in database zDb; +** in other words, the same blob that would be selected by: +** +**
    +**     SELECT zColumn FROM zDb.zTable WHERE rowid = iRow;
    +** 
    {END} +** +** If the flags parameter is non-zero, the blob is opened for +** read and write access. If it is zero, the blob is opened for read +** access. +** +** Note that the database name is not the filename that contains +** the database but rather the symbolic name of the database that +** is assigned when the database is connected using [ATTACH]. +** For the main database file, the database name is "main". For +** TEMP tables, the database name is "temp". +** +** On success, [SQLITE_OK] is returned and the new +** [sqlite3_blob | blob handle] is written to *ppBlob. +** Otherwise an error code is returned and +** any value written to *ppBlob should not be used by the caller. +** This function sets the database-handle error code and message +** accessible via [sqlite3_errcode()] and [sqlite3_errmsg()]. +** +** INVARIANTS: +** +** {F17813} A successful invocation of the [sqlite3_blob_open(D,B,T,C,R,F,P)] +** interface opens an [sqlite3_blob] object P on the blob +** in column C of table T in database B on [database connection] D. +** +** {F17814} A successful invocation of [sqlite3_blob_open(D,...)] starts +** a new transaction on [database connection] D if that connection +** is not already in a transaction. +** +** {F17816} The [sqlite3_blob_open(D,B,T,C,R,F,P)] interface opens the blob +** for read and write access if and only if the F parameter +** is non-zero. +** +** {F17819} The [sqlite3_blob_open()] interface returns [SQLITE_OK] on +** success and an appropriate [error code] on failure. +** +** {F17821} If an error occurs during evaluation of [sqlite3_blob_open(D,...)] +** then subsequent calls to [sqlite3_errcode(D)], +** [sqlite3_errmsg(D)], and [sqlite3_errmsg16(D)] will return +** information approprate for that error. +*/ +SQLITE_API int sqlite3_blob_open( + sqlite3*, + const char *zDb, + const char *zTable, + const char *zColumn, + sqlite3_int64 iRow, + int flags, + sqlite3_blob **ppBlob +); + +/* +** CAPI3REF: Close A BLOB Handle {F17830} +** +** Close an open [sqlite3_blob | blob handle]. +** +** Closing a BLOB shall cause the current transaction to commit +** if there are no other BLOBs, no pending prepared statements, and the +** database connection is in autocommit mode. +** If any writes were made to the BLOB, they might be held in cache +** until the close operation if they will fit. {END} +** Closing the BLOB often forces the changes +** out to disk and so if any I/O errors occur, they will likely occur +** at the time when the BLOB is closed. {F17833} Any errors that occur during +** closing are reported as a non-zero return value. +** +** The BLOB is closed unconditionally. Even if this routine returns +** an error code, the BLOB is still closed. +** +** INVARIANTS: +** +** {F17833} The [sqlite3_blob_close(P)] interface closes an +** [sqlite3_blob] object P previously opened using +** [sqlite3_blob_open()]. +** +** {F17836} Closing an [sqlite3_blob] object using +** [sqlite3_blob_close()] shall cause the current transaction to +** commit if there are no other open [sqlite3_blob] objects +** or [prepared statements] on the same [database connection] and +** the [database connection] is in +** [sqlite3_get_autocommit | autocommit mode]. +** +** {F17839} The [sqlite3_blob_close(P)] interfaces closes the +** [sqlite3_blob] object P unconditionally, even if +** [sqlite3_blob_close(P)] returns something other than [SQLITE_OK]. +** +*/ +SQLITE_API int sqlite3_blob_close(sqlite3_blob *); + +/* +** CAPI3REF: Return The Size Of An Open BLOB {F17840} +** +** Return the size in bytes of the blob accessible via the open +** [sqlite3_blob] object in its only argument. +** +** INVARIANTS: +** +** {F17843} The [sqlite3_blob_bytes(P)] interface returns the size +** in bytes of the BLOB that the [sqlite3_blob] object P +** refers to. +*/ +SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *); + +/* +** CAPI3REF: Read Data From A BLOB Incrementally {F17850} +** +** This function is used to read data from an open +** [sqlite3_blob | blob-handle] into a caller supplied buffer. +** N bytes of data are copied into buffer +** Z from the open blob, starting at offset iOffset. +** +** If offset iOffset is less than N bytes from the end of the blob, +** [SQLITE_ERROR] is returned and no data is read. If N or iOffset is +** less than zero [SQLITE_ERROR] is returned and no data is read. +** +** On success, SQLITE_OK is returned. Otherwise, an +** [error code] or an [extended error code] is returned. +** +** INVARIANTS: +** +** {F17853} The [sqlite3_blob_read(P,Z,N,X)] interface reads N bytes +** beginning at offset X from +** the blob that [sqlite3_blob] object P refers to +** and writes those N bytes into buffer Z. +** +** {F17856} In [sqlite3_blob_read(P,Z,N,X)] if the size of the blob +** is less than N+X bytes, then the function returns [SQLITE_ERROR] +** and nothing is read from the blob. +** +** {F17859} In [sqlite3_blob_read(P,Z,N,X)] if X or N is less than zero +** then the function returns [SQLITE_ERROR] +** and nothing is read from the blob. +** +** {F17862} The [sqlite3_blob_read(P,Z,N,X)] interface returns [SQLITE_OK] +** if N bytes where successfully read into buffer Z. +** +** {F17865} If the requested read could not be completed, +** the [sqlite3_blob_read(P,Z,N,X)] interface returns an +** appropriate [error code] or [extended error code]. +** +** {F17868} If an error occurs during evaluation of [sqlite3_blob_read(P,...)] +** then subsequent calls to [sqlite3_errcode(D)], +** [sqlite3_errmsg(D)], and [sqlite3_errmsg16(D)] will return +** information approprate for that error, where D is the +** database handle that was used to open blob handle P. +*/ +SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); + +/* +** CAPI3REF: Write Data Into A BLOB Incrementally {F17870} +** +** This function is used to write data into an open +** [sqlite3_blob | blob-handle] from a user supplied buffer. +** n bytes of data are copied from the buffer +** pointed to by z into the open blob, starting at offset iOffset. +** +** If the [sqlite3_blob | blob-handle] passed as the first argument +** was not opened for writing (the flags parameter to [sqlite3_blob_open()] +*** was zero), this function returns [SQLITE_READONLY]. +** +** This function may only modify the contents of the blob; it is +** not possible to increase the size of a blob using this API. +** If offset iOffset is less than n bytes from the end of the blob, +** [SQLITE_ERROR] is returned and no data is written. If n is +** less than zero [SQLITE_ERROR] is returned and no data is written. +** +** On success, SQLITE_OK is returned. Otherwise, an +** [error code] or an [extended error code] is returned. +** +** INVARIANTS: +** +** {F17873} The [sqlite3_blob_write(P,Z,N,X)] interface writes N bytes +** from buffer Z into +** the blob that [sqlite3_blob] object P refers to +** beginning at an offset of X into the blob. +** +** {F17875} The [sqlite3_blob_write(P,Z,N,X)] interface returns +** [SQLITE_READONLY] if the [sqlite3_blob] object P was +** [sqlite3_blob_open | opened] for reading only. +** +** {F17876} In [sqlite3_blob_write(P,Z,N,X)] if the size of the blob +** is less than N+X bytes, then the function returns [SQLITE_ERROR] +** and nothing is written into the blob. +** +** {F17879} In [sqlite3_blob_write(P,Z,N,X)] if X or N is less than zero +** then the function returns [SQLITE_ERROR] +** and nothing is written into the blob. +** +** {F17882} The [sqlite3_blob_write(P,Z,N,X)] interface returns [SQLITE_OK] +** if N bytes where successfully written into blob. +** +** {F17885} If the requested write could not be completed, +** the [sqlite3_blob_write(P,Z,N,X)] interface returns an +** appropriate [error code] or [extended error code]. +** +** {F17888} If an error occurs during evaluation of [sqlite3_blob_write(D,...)] +** then subsequent calls to [sqlite3_errcode(D)], +** [sqlite3_errmsg(D)], and [sqlite3_errmsg16(D)] will return +** information approprate for that error. +*/ +SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); + +/* +** CAPI3REF: Virtual File System Objects {F11200} +** +** A virtual filesystem (VFS) is an [sqlite3_vfs] object +** that SQLite uses to interact +** with the underlying operating system. Most SQLite builds come with a +** single default VFS that is appropriate for the host computer. +** New VFSes can be registered and existing VFSes can be unregistered. +** The following interfaces are provided. +** +** The sqlite3_vfs_find() interface returns a pointer to +** a VFS given its name. Names are case sensitive. +** Names are zero-terminated UTF-8 strings. +** If there is no match, a NULL +** pointer is returned. If zVfsName is NULL then the default +** VFS is returned. +** +** New VFSes are registered with sqlite3_vfs_register(). +** Each new VFS becomes the default VFS if the makeDflt flag is set. +** The same VFS can be registered multiple times without injury. +** To make an existing VFS into the default VFS, register it again +** with the makeDflt flag set. If two different VFSes with the +** same name are registered, the behavior is undefined. If a +** VFS is registered with a name that is NULL or an empty string, +** then the behavior is undefined. +** +** Unregister a VFS with the sqlite3_vfs_unregister() interface. +** If the default VFS is unregistered, another VFS is chosen as +** the default. The choice for the new VFS is arbitrary. +** +** INVARIANTS: +** +** {F11203} The [sqlite3_vfs_find(N)] interface returns a pointer to the +** registered [sqlite3_vfs] object whose name exactly matches +** the zero-terminated UTF-8 string N, or it returns NULL if +** there is no match. +** +** {F11206} If the N parameter to [sqlite3_vfs_find(N)] is NULL then +** the function returns a pointer to the default [sqlite3_vfs] +** object if there is one, or NULL if there is no default +** [sqlite3_vfs] object. +** +** {F11209} The [sqlite3_vfs_register(P,F)] interface registers the +** well-formed [sqlite3_vfs] object P using the name given +** by the zName field of the object. +** +** {F11212} Using the [sqlite3_vfs_register(P,F)] interface to register +** the same [sqlite3_vfs] object multiple times is a harmless no-op. +** +** {F11215} The [sqlite3_vfs_register(P,F)] interface makes the +** the [sqlite3_vfs] object P the default [sqlite3_vfs] object +** if F is non-zero. +** +** {F11218} The [sqlite3_vfs_unregister(P)] interface unregisters the +** [sqlite3_vfs] object P so that it is no longer returned by +** subsequent calls to [sqlite3_vfs_find()]. +*/ +SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName); +SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); +SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); + +/* +** CAPI3REF: Mutexes {F17000} +** +** The SQLite core uses these routines for thread +** synchronization. Though they are intended for internal +** use by SQLite, code that links against SQLite is +** permitted to use any of these routines. +** +** The SQLite source code contains multiple implementations +** of these mutex routines. An appropriate implementation +** is selected automatically at compile-time. The following +** implementations are available in the SQLite core: +** +**
      +**
    • SQLITE_MUTEX_OS2 +**
    • SQLITE_MUTEX_PTHREAD +**
    • SQLITE_MUTEX_W32 +**
    • SQLITE_MUTEX_NOOP +**
    +** +** The SQLITE_MUTEX_NOOP implementation is a set of routines +** that does no real locking and is appropriate for use in +** a single-threaded application. The SQLITE_MUTEX_OS2, +** SQLITE_MUTEX_PTHREAD, and SQLITE_MUTEX_W32 implementations +** are appropriate for use on os/2, unix, and windows. +** +** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor +** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex +** implementation is included with the library. The +** mutex interface routines defined here become external +** references in the SQLite library for which implementations +** must be provided by the application. This facility allows an +** application that links against SQLite to provide its own mutex +** implementation without having to modify the SQLite core. +** +** {F17011} The sqlite3_mutex_alloc() routine allocates a new +** mutex and returns a pointer to it. {F17012} If it returns NULL +** that means that a mutex could not be allocated. {F17013} SQLite +** will unwind its stack and return an error. {F17014} The argument +** to sqlite3_mutex_alloc() is one of these integer constants: +** +**
      +**
    • SQLITE_MUTEX_FAST +**
    • SQLITE_MUTEX_RECURSIVE +**
    • SQLITE_MUTEX_STATIC_MASTER +**
    • SQLITE_MUTEX_STATIC_MEM +**
    • SQLITE_MUTEX_STATIC_MEM2 +**
    • SQLITE_MUTEX_STATIC_PRNG +**
    • SQLITE_MUTEX_STATIC_LRU +**
    • SQLITE_MUTEX_STATIC_LRU2 +**
    {END} +** +** {F17015} The first two constants cause sqlite3_mutex_alloc() to create +** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE +** is used but not necessarily so when SQLITE_MUTEX_FAST is used. {END} +** The mutex implementation does not need to make a distinction +** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does +** not want to. {F17016} But SQLite will only request a recursive mutex in +** cases where it really needs one. {END} If a faster non-recursive mutex +** implementation is available on the host platform, the mutex subsystem +** might return such a mutex in response to SQLITE_MUTEX_FAST. +** +** {F17017} The other allowed parameters to sqlite3_mutex_alloc() each return +** a pointer to a static preexisting mutex. {END} Four static mutexes are +** used by the current version of SQLite. Future versions of SQLite +** may add additional static mutexes. Static mutexes are for internal +** use by SQLite only. Applications that use SQLite mutexes should +** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or +** SQLITE_MUTEX_RECURSIVE. +** +** {F17018} Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST +** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() +** returns a different mutex on every call. {F17034} But for the static +** mutex types, the same mutex is returned on every call that has +** the same type number. {END} +** +** {F17019} The sqlite3_mutex_free() routine deallocates a previously +** allocated dynamic mutex. {F17020} SQLite is careful to deallocate every +** dynamic mutex that it allocates. {U17021} The dynamic mutexes must not be in +** use when they are deallocated. {U17022} Attempting to deallocate a static +** mutex results in undefined behavior. {F17023} SQLite never deallocates +** a static mutex. {END} +** +** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt +** to enter a mutex. {F17024} If another thread is already within the mutex, +** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return +** SQLITE_BUSY. {F17025} The sqlite3_mutex_try() interface returns SQLITE_OK +** upon successful entry. {F17026} Mutexes created using +** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread. +** {F17027} In such cases the, +** mutex must be exited an equal number of times before another thread +** can enter. {U17028} If the same thread tries to enter any other +** kind of mutex more than once, the behavior is undefined. +** {F17029} SQLite will never exhibit +** such behavior in its own use of mutexes. {END} +** +** Some systems (ex: windows95) do not the operation implemented by +** sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() will +** always return SQLITE_BUSY. {F17030} The SQLite core only ever uses +** sqlite3_mutex_try() as an optimization so this is acceptable behavior. {END} +** +** {F17031} The sqlite3_mutex_leave() routine exits a mutex that was +** previously entered by the same thread. {U17032} The behavior +** is undefined if the mutex is not currently entered by the +** calling thread or is not currently allocated. {F17033} SQLite will +** never do either. {END} +** +** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()]. +*/ +SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int); +SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*); +SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*); +SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); + +/* +** CAPI3REF: Mutex Verifcation Routines {F17080} +** +** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines +** are intended for use inside assert() statements. {F17081} The SQLite core +** never uses these routines except inside an assert() and applications +** are advised to follow the lead of the core. {F17082} The core only +** provides implementations for these routines when it is compiled +** with the SQLITE_DEBUG flag. {U17087} External mutex implementations +** are only required to provide these routines if SQLITE_DEBUG is +** defined and if NDEBUG is not defined. +** +** {F17083} These routines should return true if the mutex in their argument +** is held or not held, respectively, by the calling thread. {END} +** +** {X17084} The implementation is not required to provided versions of these +** routines that actually work. +** If the implementation does not provide working +** versions of these routines, it should at least provide stubs +** that always return true so that one does not get spurious +** assertion failures. {END} +** +** {F17085} If the argument to sqlite3_mutex_held() is a NULL pointer then +** the routine should return 1. {END} This seems counter-intuitive since +** clearly the mutex cannot be held if it does not exist. But the +** the reason the mutex does not exist is because the build is not +** using mutexes. And we do not want the assert() containing the +** call to sqlite3_mutex_held() to fail, so a non-zero return is +** the appropriate thing to do. {F17086} The sqlite3_mutex_notheld() +** interface should also return 1 when given a NULL pointer. +*/ +SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); + +/* +** CAPI3REF: Mutex Types {F17001} +** +** {F17002} The [sqlite3_mutex_alloc()] interface takes a single argument +** which is one of these integer constants. {END} +*/ +#define SQLITE_MUTEX_FAST 0 +#define SQLITE_MUTEX_RECURSIVE 1 +#define SQLITE_MUTEX_STATIC_MASTER 2 +#define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ +#define SQLITE_MUTEX_STATIC_MEM2 4 /* sqlite3_release_memory() */ +#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_random() */ +#define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ +#define SQLITE_MUTEX_STATIC_LRU2 7 /* lru page list */ + +/* +** CAPI3REF: Low-Level Control Of Database Files {F11300} +** +** {F11301} The [sqlite3_file_control()] interface makes a direct call to the +** xFileControl method for the [sqlite3_io_methods] object associated +** with a particular database identified by the second argument. {F11302} The +** name of the database is the name assigned to the database by the +** ATTACH SQL command that opened the +** database. {F11303} To control the main database file, use the name "main" +** or a NULL pointer. {F11304} The third and fourth parameters to this routine +** are passed directly through to the second and third parameters of +** the xFileControl method. {F11305} The return value of the xFileControl +** method becomes the return value of this routine. +** +** {F11306} If the second parameter (zDbName) does not match the name of any +** open database file, then SQLITE_ERROR is returned. {F11307} This error +** code is not remembered and will not be recalled by [sqlite3_errcode()] +** or [sqlite3_errmsg()]. {U11308} The underlying xFileControl method might +** also return SQLITE_ERROR. {U11309} There is no way to distinguish between +** an incorrect zDbName and an SQLITE_ERROR return from the underlying +** xFileControl method. {END} +** +** See also: [SQLITE_FCNTL_LOCKSTATE] +*/ +SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); + +/* +** CAPI3REF: Testing Interface {F11400} +** +** The sqlite3_test_control() interface is used to read out internal +** state of SQLite and to inject faults into SQLite for testing +** purposes. The first parameter a operation code that determines +** the number, meaning, and operation of all subsequent parameters. +** +** This interface is not for use by applications. It exists solely +** for verifying the correct operation of the SQLite library. Depending +** on how the SQLite library is compiled, this interface might not exist. +** +** The details of the operation codes, their meanings, the parameters +** they take, and what they do are all subject to change without notice. +** Unlike most of the SQLite API, this function is not guaranteed to +** operate consistently from one release to the next. +*/ +SQLITE_API int sqlite3_test_control(int op, ...); + +/* +** CAPI3REF: Testing Interface Operation Codes {F11410} +** +** These constants are the valid operation code parameters used +** as the first argument to [sqlite3_test_control()]. +** +** These parameters and their meansing are subject to change +** without notice. These values are for testing purposes only. +** Applications should not use any of these parameters or the +** [sqlite3_test_control()] interface. +*/ +#define SQLITE_TESTCTRL_FAULT_CONFIG 1 +#define SQLITE_TESTCTRL_FAULT_FAILURES 2 +#define SQLITE_TESTCTRL_FAULT_BENIGN_FAILURES 3 +#define SQLITE_TESTCTRL_FAULT_PENDING 4 +#define SQLITE_TESTCTRL_PRNG_SAVE 5 +#define SQLITE_TESTCTRL_PRNG_RESTORE 6 +#define SQLITE_TESTCTRL_PRNG_RESET 7 +#define SQLITE_TESTCTRL_BITVEC_TEST 8 + + +/* +** Undo the hack that converts floating point types to integer for +** builds on processors without floating point support. +*/ +#ifdef SQLITE_OMIT_FLOATING_POINT +# undef double +#endif + +#if 0 +} /* End of the 'extern "C"' block */ +#endif +#endif + +/************** End of sqlite3.h *********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include hash.h in the middle of sqliteInt.h ******************/ +/************** Begin file hash.h ********************************************/ +/* +** 2001 September 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This is the header file for the generic hash-table implemenation +** used in SQLite. +** +** $Id: hash.h,v 1.11 2007/09/04 14:31:47 danielk1977 Exp $ +*/ +#ifndef _SQLITE_HASH_H_ +#define _SQLITE_HASH_H_ + +/* Forward declarations of structures. */ +typedef struct Hash Hash; +typedef struct HashElem HashElem; + +/* A complete hash table is an instance of the following structure. +** The internals of this structure are intended to be opaque -- client +** code should not attempt to access or modify the fields of this structure +** directly. Change this structure only by using the routines below. +** However, many of the "procedures" and "functions" for modifying and +** accessing this structure are really macros, so we can't really make +** this structure opaque. +*/ +struct Hash { + char keyClass; /* SQLITE_HASH_INT, _POINTER, _STRING, _BINARY */ + char copyKey; /* True if copy of key made on insert */ + int count; /* Number of entries in this table */ + int htsize; /* Number of buckets in the hash table */ + HashElem *first; /* The first element of the array */ + struct _ht { /* the hash table */ + int count; /* Number of entries with this hash */ + HashElem *chain; /* Pointer to first entry with this hash */ + } *ht; +}; + +/* Each element in the hash table is an instance of the following +** structure. All elements are stored on a single doubly-linked list. +** +** Again, this structure is intended to be opaque, but it can't really +** be opaque because it is used by macros. +*/ +struct HashElem { + HashElem *next, *prev; /* Next and previous elements in the table */ + void *data; /* Data associated with this element */ + void *pKey; int nKey; /* Key associated with this element */ +}; + +/* +** There are 4 different modes of operation for a hash table: +** +** SQLITE_HASH_INT nKey is used as the key and pKey is ignored. +** +** SQLITE_HASH_POINTER pKey is used as the key and nKey is ignored. +** +** SQLITE_HASH_STRING pKey points to a string that is nKey bytes long +** (including the null-terminator, if any). Case +** is ignored in comparisons. +** +** SQLITE_HASH_BINARY pKey points to binary data nKey bytes long. +** memcmp() is used to compare keys. +** +** A copy of the key is made for SQLITE_HASH_STRING and SQLITE_HASH_BINARY +** if the copyKey parameter to HashInit is 1. +*/ +/* #define SQLITE_HASH_INT 1 // NOT USED */ +/* #define SQLITE_HASH_POINTER 2 // NOT USED */ +#define SQLITE_HASH_STRING 3 +#define SQLITE_HASH_BINARY 4 + +/* +** Access routines. To delete, insert a NULL pointer. +*/ +SQLITE_PRIVATE void sqlite3HashInit(Hash*, int keytype, int copyKey); +SQLITE_PRIVATE void *sqlite3HashInsert(Hash*, const void *pKey, int nKey, void *pData); +SQLITE_PRIVATE void *sqlite3HashFind(const Hash*, const void *pKey, int nKey); +SQLITE_PRIVATE HashElem *sqlite3HashFindElem(const Hash*, const void *pKey, int nKey); +SQLITE_PRIVATE void sqlite3HashClear(Hash*); + +/* +** Macros for looping over all elements of a hash table. The idiom is +** like this: +** +** Hash h; +** HashElem *p; +** ... +** for(p=sqliteHashFirst(&h); p; p=sqliteHashNext(p)){ +** SomeStructure *pData = sqliteHashData(p); +** // do something with pData +** } +*/ +#define sqliteHashFirst(H) ((H)->first) +#define sqliteHashNext(E) ((E)->next) +#define sqliteHashData(E) ((E)->data) +#define sqliteHashKey(E) ((E)->pKey) +#define sqliteHashKeysize(E) ((E)->nKey) + +/* +** Number of entries in a hash table +*/ +#define sqliteHashCount(H) ((H)->count) + +#endif /* _SQLITE_HASH_H_ */ + +/************** End of hash.h ************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include parse.h in the middle of sqliteInt.h *****************/ +/************** Begin file parse.h *******************************************/ +#define TK_SEMI 1 +#define TK_EXPLAIN 2 +#define TK_QUERY 3 +#define TK_PLAN 4 +#define TK_BEGIN 5 +#define TK_TRANSACTION 6 +#define TK_DEFERRED 7 +#define TK_IMMEDIATE 8 +#define TK_EXCLUSIVE 9 +#define TK_COMMIT 10 +#define TK_END 11 +#define TK_ROLLBACK 12 +#define TK_CREATE 13 +#define TK_TABLE 14 +#define TK_IF 15 +#define TK_NOT 16 +#define TK_EXISTS 17 +#define TK_TEMP 18 +#define TK_LP 19 +#define TK_RP 20 +#define TK_AS 21 +#define TK_COMMA 22 +#define TK_ID 23 +#define TK_ABORT 24 +#define TK_AFTER 25 +#define TK_ANALYZE 26 +#define TK_ASC 27 +#define TK_ATTACH 28 +#define TK_BEFORE 29 +#define TK_CASCADE 30 +#define TK_CAST 31 +#define TK_CONFLICT 32 +#define TK_DATABASE 33 +#define TK_DESC 34 +#define TK_DETACH 35 +#define TK_EACH 36 +#define TK_FAIL 37 +#define TK_FOR 38 +#define TK_IGNORE 39 +#define TK_INITIALLY 40 +#define TK_INSTEAD 41 +#define TK_LIKE_KW 42 +#define TK_MATCH 43 +#define TK_KEY 44 +#define TK_OF 45 +#define TK_OFFSET 46 +#define TK_PRAGMA 47 +#define TK_RAISE 48 +#define TK_REPLACE 49 +#define TK_RESTRICT 50 +#define TK_ROW 51 +#define TK_TRIGGER 52 +#define TK_VACUUM 53 +#define TK_VIEW 54 +#define TK_VIRTUAL 55 +#define TK_REINDEX 56 +#define TK_RENAME 57 +#define TK_CTIME_KW 58 +#define TK_ANY 59 +#define TK_OR 60 +#define TK_AND 61 +#define TK_IS 62 +#define TK_BETWEEN 63 +#define TK_IN 64 +#define TK_ISNULL 65 +#define TK_NOTNULL 66 +#define TK_NE 67 +#define TK_EQ 68 +#define TK_GT 69 +#define TK_LE 70 +#define TK_LT 71 +#define TK_GE 72 +#define TK_ESCAPE 73 +#define TK_BITAND 74 +#define TK_BITOR 75 +#define TK_LSHIFT 76 +#define TK_RSHIFT 77 +#define TK_PLUS 78 +#define TK_MINUS 79 +#define TK_STAR 80 +#define TK_SLASH 81 +#define TK_REM 82 +#define TK_CONCAT 83 +#define TK_COLLATE 84 +#define TK_UMINUS 85 +#define TK_UPLUS 86 +#define TK_BITNOT 87 +#define TK_STRING 88 +#define TK_JOIN_KW 89 +#define TK_CONSTRAINT 90 +#define TK_DEFAULT 91 +#define TK_NULL 92 +#define TK_PRIMARY 93 +#define TK_UNIQUE 94 +#define TK_CHECK 95 +#define TK_REFERENCES 96 +#define TK_AUTOINCR 97 +#define TK_ON 98 +#define TK_DELETE 99 +#define TK_UPDATE 100 +#define TK_INSERT 101 +#define TK_SET 102 +#define TK_DEFERRABLE 103 +#define TK_FOREIGN 104 +#define TK_DROP 105 +#define TK_UNION 106 +#define TK_ALL 107 +#define TK_EXCEPT 108 +#define TK_INTERSECT 109 +#define TK_SELECT 110 +#define TK_DISTINCT 111 +#define TK_DOT 112 +#define TK_FROM 113 +#define TK_JOIN 114 +#define TK_USING 115 +#define TK_ORDER 116 +#define TK_BY 117 +#define TK_GROUP 118 +#define TK_HAVING 119 +#define TK_LIMIT 120 +#define TK_WHERE 121 +#define TK_INTO 122 +#define TK_VALUES 123 +#define TK_INTEGER 124 +#define TK_FLOAT 125 +#define TK_BLOB 126 +#define TK_REGISTER 127 +#define TK_VARIABLE 128 +#define TK_CASE 129 +#define TK_WHEN 130 +#define TK_THEN 131 +#define TK_ELSE 132 +#define TK_INDEX 133 +#define TK_ALTER 134 +#define TK_TO 135 +#define TK_ADD 136 +#define TK_COLUMNKW 137 +#define TK_TO_TEXT 138 +#define TK_TO_BLOB 139 +#define TK_TO_NUMERIC 140 +#define TK_TO_INT 141 +#define TK_TO_REAL 142 +#define TK_END_OF_FILE 143 +#define TK_ILLEGAL 144 +#define TK_SPACE 145 +#define TK_UNCLOSED_STRING 146 +#define TK_COMMENT 147 +#define TK_FUNCTION 148 +#define TK_COLUMN 149 +#define TK_AGG_FUNCTION 150 +#define TK_AGG_COLUMN 151 +#define TK_CONST_FUNC 152 + +/************** End of parse.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +#include +#include +#include +#include +#include + +/* +** If compiling for a processor that lacks floating point support, +** substitute integer for floating-point +*/ +#ifdef SQLITE_OMIT_FLOATING_POINT +# define double sqlite_int64 +# define LONGDOUBLE_TYPE sqlite_int64 +# ifndef SQLITE_BIG_DBL +# define SQLITE_BIG_DBL (0x7fffffffffffffff) +# endif +# define SQLITE_OMIT_DATETIME_FUNCS 1 +# define SQLITE_OMIT_TRACE 1 +# undef SQLITE_MIXED_ENDIAN_64BIT_FLOAT +#endif +#ifndef SQLITE_BIG_DBL +# define SQLITE_BIG_DBL (1e99) +#endif + +/* +** OMIT_TEMPDB is set to 1 if SQLITE_OMIT_TEMPDB is defined, or 0 +** afterward. Having this macro allows us to cause the C compiler +** to omit code used by TEMP tables without messy #ifndef statements. +*/ +#ifdef SQLITE_OMIT_TEMPDB +#define OMIT_TEMPDB 1 +#else +#define OMIT_TEMPDB 0 +#endif + +/* +** If the following macro is set to 1, then NULL values are considered +** distinct when determining whether or not two entries are the same +** in a UNIQUE index. This is the way PostgreSQL, Oracle, DB2, MySQL, +** OCELOT, and Firebird all work. The SQL92 spec explicitly says this +** is the way things are suppose to work. +** +** If the following macro is set to 0, the NULLs are indistinct for +** a UNIQUE index. In this mode, you can only have a single NULL entry +** for a column declared UNIQUE. This is the way Informix and SQL Server +** work. +*/ +#define NULL_DISTINCT_FOR_UNIQUE 1 + +/* +** The "file format" number is an integer that is incremented whenever +** the VDBE-level file format changes. The following macros define the +** the default file format for new databases and the maximum file format +** that the library can read. +*/ +#define SQLITE_MAX_FILE_FORMAT 4 +#ifndef SQLITE_DEFAULT_FILE_FORMAT +# define SQLITE_DEFAULT_FILE_FORMAT 1 +#endif + +/* +** Provide a default value for TEMP_STORE in case it is not specified +** on the command-line +*/ +#ifndef TEMP_STORE +# define TEMP_STORE 1 +#endif + +/* +** GCC does not define the offsetof() macro so we'll have to do it +** ourselves. +*/ +#ifndef offsetof +#define offsetof(STRUCTURE,FIELD) ((int)((char*)&((STRUCTURE*)0)->FIELD)) +#endif + +/* +** Check to see if this machine uses EBCDIC. (Yes, believe it or +** not, there are still machines out there that use EBCDIC.) +*/ +#if 'A' == '\301' +# define SQLITE_EBCDIC 1 +#else +# define SQLITE_ASCII 1 +#endif + +/* +** Integers of known sizes. These typedefs might change for architectures +** where the sizes very. Preprocessor macros are available so that the +** types can be conveniently redefined at compile-type. Like this: +** +** cc '-DUINTPTR_TYPE=long long int' ... +*/ +#ifndef UINT32_TYPE +# ifdef HAVE_UINT32_T +# define UINT32_TYPE uint32_t +# else +# define UINT32_TYPE unsigned int +# endif +#endif +#ifndef UINT16_TYPE +# ifdef HAVE_UINT16_T +# define UINT16_TYPE uint16_t +# else +# define UINT16_TYPE unsigned short int +# endif +#endif +#ifndef INT16_TYPE +# ifdef HAVE_INT16_T +# define INT16_TYPE int16_t +# else +# define INT16_TYPE short int +# endif +#endif +#ifndef UINT8_TYPE +# ifdef HAVE_UINT8_T +# define UINT8_TYPE uint8_t +# else +# define UINT8_TYPE unsigned char +# endif +#endif +#ifndef INT8_TYPE +# ifdef HAVE_INT8_T +# define INT8_TYPE int8_t +# else +# define INT8_TYPE signed char +# endif +#endif +#ifndef LONGDOUBLE_TYPE +# define LONGDOUBLE_TYPE long double +#endif +typedef sqlite_int64 i64; /* 8-byte signed integer */ +typedef sqlite_uint64 u64; /* 8-byte unsigned integer */ +typedef UINT32_TYPE u32; /* 4-byte unsigned integer */ +typedef UINT16_TYPE u16; /* 2-byte unsigned integer */ +typedef INT16_TYPE i16; /* 2-byte signed integer */ +typedef UINT8_TYPE u8; /* 1-byte unsigned integer */ +typedef UINT8_TYPE i8; /* 1-byte signed integer */ + +/* +** Macros to determine whether the machine is big or little endian, +** evaluated at runtime. +*/ +#ifdef SQLITE_AMALGAMATION +SQLITE_PRIVATE const int sqlite3one; +#else +SQLITE_PRIVATE const int sqlite3one; +#endif +#if defined(i386) || defined(__i386__) || defined(_M_IX86) +# define SQLITE_BIGENDIAN 0 +# define SQLITE_LITTLEENDIAN 1 +# define SQLITE_UTF16NATIVE SQLITE_UTF16LE +#else +# define SQLITE_BIGENDIAN (*(char *)(&sqlite3one)==0) +# define SQLITE_LITTLEENDIAN (*(char *)(&sqlite3one)==1) +# define SQLITE_UTF16NATIVE (SQLITE_BIGENDIAN?SQLITE_UTF16BE:SQLITE_UTF16LE) +#endif + +/* +** Constants for the largest and smallest possible 64-bit signed integers. +** These macros are designed to work correctly on both 32-bit and 64-bit +** compilers. +*/ +#define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) +#define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) + +/* +** An instance of the following structure is used to store the busy-handler +** callback for a given sqlite handle. +** +** The sqlite.busyHandler member of the sqlite struct contains the busy +** callback for the database handle. Each pager opened via the sqlite +** handle is passed a pointer to sqlite.busyHandler. The busy-handler +** callback is currently invoked only from within pager.c. +*/ +typedef struct BusyHandler BusyHandler; +struct BusyHandler { + int (*xFunc)(void *,int); /* The busy callback */ + void *pArg; /* First arg to busy callback */ + int nBusy; /* Incremented with each busy call */ +}; + +/* +** Name of the master database table. The master database table +** is a special table that holds the names and attributes of all +** user tables and indices. +*/ +#define MASTER_NAME "sqlite_master" +#define TEMP_MASTER_NAME "sqlite_temp_master" + +/* +** The root-page of the master database table. +*/ +#define MASTER_ROOT 1 + +/* +** The name of the schema table. +*/ +#define SCHEMA_TABLE(x) ((!OMIT_TEMPDB)&&(x==1)?TEMP_MASTER_NAME:MASTER_NAME) + +/* +** A convenience macro that returns the number of elements in +** an array. +*/ +#define ArraySize(X) (sizeof(X)/sizeof(X[0])) + +/* +** Forward references to structures +*/ +typedef struct AggInfo AggInfo; +typedef struct AuthContext AuthContext; +typedef struct Bitvec Bitvec; +typedef struct CollSeq CollSeq; +typedef struct Column Column; +typedef struct Db Db; +typedef struct Schema Schema; +typedef struct Expr Expr; +typedef struct ExprList ExprList; +typedef struct FKey FKey; +typedef struct FuncDef FuncDef; +typedef struct IdList IdList; +typedef struct Index Index; +typedef struct KeyClass KeyClass; +typedef struct KeyInfo KeyInfo; +typedef struct Module Module; +typedef struct NameContext NameContext; +typedef struct Parse Parse; +typedef struct Select Select; +typedef struct SrcList SrcList; +typedef struct StrAccum StrAccum; +typedef struct Table Table; +typedef struct TableLock TableLock; +typedef struct Token Token; +typedef struct TriggerStack TriggerStack; +typedef struct TriggerStep TriggerStep; +typedef struct Trigger Trigger; +typedef struct WhereInfo WhereInfo; +typedef struct WhereLevel WhereLevel; + +/* +** Defer sourcing vdbe.h and btree.h until after the "u8" and +** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque +** pointer types (i.e. FuncDef) defined above. +*/ +/************** Include btree.h in the middle of sqliteInt.h *****************/ +/************** Begin file btree.h *******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite B-Tree file +** subsystem. See comments in the source code for a detailed description +** of what each interface routine does. +** +** @(#) $Id: btree.h,v 1.98 2008/04/26 13:39:47 drh Exp $ +*/ +#ifndef _BTREE_H_ +#define _BTREE_H_ + +/* TODO: This definition is just included so other modules compile. It +** needs to be revisited. +*/ +#define SQLITE_N_BTREE_META 10 + +/* +** If defined as non-zero, auto-vacuum is enabled by default. Otherwise +** it must be turned on for each database using "PRAGMA auto_vacuum = 1". +*/ +#ifndef SQLITE_DEFAULT_AUTOVACUUM + #define SQLITE_DEFAULT_AUTOVACUUM 0 +#endif + +#define BTREE_AUTOVACUUM_NONE 0 /* Do not do auto-vacuum */ +#define BTREE_AUTOVACUUM_FULL 1 /* Do full auto-vacuum */ +#define BTREE_AUTOVACUUM_INCR 2 /* Incremental vacuum */ + +/* +** Forward declarations of structure +*/ +typedef struct Btree Btree; +typedef struct BtCursor BtCursor; +typedef struct BtShared BtShared; +typedef struct BtreeMutexArray BtreeMutexArray; + +/* +** This structure records all of the Btrees that need to hold +** a mutex before we enter sqlite3VdbeExec(). The Btrees are +** are placed in aBtree[] in order of aBtree[]->pBt. That way, +** we can always lock and unlock them all quickly. +*/ +struct BtreeMutexArray { + int nMutex; + Btree *aBtree[SQLITE_MAX_ATTACHED+1]; +}; + + +SQLITE_PRIVATE int sqlite3BtreeOpen( + const char *zFilename, /* Name of database file to open */ + sqlite3 *db, /* Associated database connection */ + Btree **, /* Return open Btree* here */ + int flags, /* Flags */ + int vfsFlags /* Flags passed through to VFS open */ +); + +/* The flags parameter to sqlite3BtreeOpen can be the bitwise or of the +** following values. +** +** NOTE: These values must match the corresponding PAGER_ values in +** pager.h. +*/ +#define BTREE_OMIT_JOURNAL 1 /* Do not use journal. No argument */ +#define BTREE_NO_READLOCK 2 /* Omit readlocks on readonly files */ +#define BTREE_MEMORY 4 /* In-memory DB. No argument */ +#define BTREE_READONLY 8 /* Open the database in read-only mode */ +#define BTREE_READWRITE 16 /* Open for both reading and writing */ +#define BTREE_CREATE 32 /* Create the database if it does not exist */ + +/* Additional values for the 4th argument of sqlite3BtreeOpen that +** are not associated with PAGER_ values. +*/ +#define BTREE_PRIVATE 64 /* Never share with other connections */ + +SQLITE_PRIVATE int sqlite3BtreeClose(Btree*); +SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree*,int); +SQLITE_PRIVATE int sqlite3BtreeSetSafetyLevel(Btree*,int,int); +SQLITE_PRIVATE int sqlite3BtreeSyncDisabled(Btree*); +SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree*,int,int); +SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree*); +SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree*,int); +SQLITE_PRIVATE int sqlite3BtreeGetReserve(Btree*); +SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *, int); +SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *); +SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree*,int); +SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster); +SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree*); +SQLITE_PRIVATE int sqlite3BtreeCommit(Btree*); +SQLITE_PRIVATE int sqlite3BtreeRollback(Btree*); +SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree*); +SQLITE_PRIVATE int sqlite3BtreeCommitStmt(Btree*); +SQLITE_PRIVATE int sqlite3BtreeRollbackStmt(Btree*); +SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree*, int*, int flags); +SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree*); +SQLITE_PRIVATE int sqlite3BtreeIsInStmt(Btree*); +SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree*); +SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *, int, void(*)(void *)); +SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *); +SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *, int, u8); + +SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *); +SQLITE_PRIVATE const char *sqlite3BtreeGetDirname(Btree *); +SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *); +SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *, Btree *); + +SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *); + +/* The flags parameter to sqlite3BtreeCreateTable can be the bitwise OR +** of the following flags: +*/ +#define BTREE_INTKEY 1 /* Table has only 64-bit signed integer keys */ +#define BTREE_ZERODATA 2 /* Table has keys only - no data */ +#define BTREE_LEAFDATA 4 /* Data stored in leaves only. Implies INTKEY */ + +SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree*, int, int*); +SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree*, int); +SQLITE_PRIVATE int sqlite3BtreeGetMeta(Btree*, int idx, u32 *pValue); +SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree*, int idx, u32 value); +SQLITE_PRIVATE void sqlite3BtreeTripAllCursors(Btree*, int); + +struct UnpackedRecord; /* Forward declaration. Definition in vdbeaux.c. */ + +SQLITE_PRIVATE int sqlite3BtreeCursor( + Btree*, /* BTree containing table to open */ + int iTable, /* Index of root page */ + int wrFlag, /* 1 for writing. 0 for read-only */ + struct KeyInfo*, /* First argument to compare function */ + BtCursor *pCursor /* Space to write cursor structure */ +); +SQLITE_PRIVATE int sqlite3BtreeCursorSize(void); + +SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreeMoveto( + BtCursor*, + const void *pKey, + struct UnpackedRecord *pUnKey, + i64 nKey, + int bias, + int *pRes +); +SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const void *pKey, i64 nKey, + const void *pData, int nData, + int nZero, int bias); +SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreeFlags(BtCursor*); +SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeKeySize(BtCursor*, i64 *pSize); +SQLITE_PRIVATE int sqlite3BtreeKey(BtCursor*, u32 offset, u32 amt, void*); +SQLITE_PRIVATE sqlite3 *sqlite3BtreeCursorDb(const BtCursor*); +SQLITE_PRIVATE const void *sqlite3BtreeKeyFetch(BtCursor*, int *pAmt); +SQLITE_PRIVATE const void *sqlite3BtreeDataFetch(BtCursor*, int *pAmt); +SQLITE_PRIVATE int sqlite3BtreeDataSize(BtCursor*, u32 *pSize); +SQLITE_PRIVATE int sqlite3BtreeData(BtCursor*, u32 offset, u32 amt, void*); + +SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot, int, int*); +SQLITE_PRIVATE struct Pager *sqlite3BtreePager(Btree*); + +SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, void*); +SQLITE_PRIVATE void sqlite3BtreeCacheOverflow(BtCursor *); + +#ifdef SQLITE_TEST +SQLITE_PRIVATE int sqlite3BtreeCursorInfo(BtCursor*, int*, int); +SQLITE_PRIVATE void sqlite3BtreeCursorList(Btree*); +SQLITE_PRIVATE int sqlite3BtreePageDump(Btree*, int, int recursive); +#endif + +/* +** If we are not using shared cache, then there is no need to +** use mutexes to access the BtShared structures. So make the +** Enter and Leave procedures no-ops. +*/ +#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE +SQLITE_PRIVATE void sqlite3BtreeEnter(Btree*); +SQLITE_PRIVATE void sqlite3BtreeLeave(Btree*); +SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree*); +SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor*); +SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor*); +SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3*); +SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3*); +SQLITE_PRIVATE int sqlite3BtreeHoldsAllMutexes(sqlite3*); +SQLITE_PRIVATE void sqlite3BtreeMutexArrayEnter(BtreeMutexArray*); +SQLITE_PRIVATE void sqlite3BtreeMutexArrayLeave(BtreeMutexArray*); +SQLITE_PRIVATE void sqlite3BtreeMutexArrayInsert(BtreeMutexArray*, Btree*); +#else +# define sqlite3BtreeEnter(X) +# define sqlite3BtreeLeave(X) +# define sqlite3BtreeHoldsMutex(X) 1 +# define sqlite3BtreeEnterCursor(X) +# define sqlite3BtreeLeaveCursor(X) +# define sqlite3BtreeEnterAll(X) +# define sqlite3BtreeLeaveAll(X) +# define sqlite3BtreeHoldsAllMutexes(X) 1 +# define sqlite3BtreeMutexArrayEnter(X) +# define sqlite3BtreeMutexArrayLeave(X) +# define sqlite3BtreeMutexArrayInsert(X,Y) +#endif + + +#endif /* _BTREE_H_ */ + +/************** End of btree.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include vdbe.h in the middle of sqliteInt.h ******************/ +/************** Begin file vdbe.h ********************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Header file for the Virtual DataBase Engine (VDBE) +** +** This header defines the interface to the virtual database engine +** or VDBE. The VDBE implements an abstract machine that runs a +** simple program to access and modify the underlying database. +** +** $Id: vdbe.h,v 1.131 2008/05/01 17:03:49 drh Exp $ +*/ +#ifndef _SQLITE_VDBE_H_ +#define _SQLITE_VDBE_H_ + +/* +** A single VDBE is an opaque structure named "Vdbe". Only routines +** in the source file sqliteVdbe.c are allowed to see the insides +** of this structure. +*/ +typedef struct Vdbe Vdbe; + +/* +** The names of the following types declared in vdbeInt.h are required +** for the VdbeOp definition. +*/ +typedef struct VdbeFunc VdbeFunc; +typedef struct Mem Mem; +typedef struct UnpackedRecord UnpackedRecord; + +/* +** A single instruction of the virtual machine has an opcode +** and as many as three operands. The instruction is recorded +** as an instance of the following structure: +*/ +struct VdbeOp { + u8 opcode; /* What operation to perform */ + signed char p4type; /* One of the P4_xxx constants for p4 */ + u8 opflags; /* Not currently used */ + u8 p5; /* Fifth parameter is an unsigned character */ + int p1; /* First operand */ + int p2; /* Second parameter (often the jump destination) */ + int p3; /* The third parameter */ + union { /* forth parameter */ + int i; /* Integer value if p4type==P4_INT32 */ + void *p; /* Generic pointer */ + char *z; /* Pointer to data for string (char array) types */ + i64 *pI64; /* Used when p4type is P4_INT64 */ + double *pReal; /* Used when p4type is P4_REAL */ + FuncDef *pFunc; /* Used when p4type is P4_FUNCDEF */ + VdbeFunc *pVdbeFunc; /* Used when p4type is P4_VDBEFUNC */ + CollSeq *pColl; /* Used when p4type is P4_COLLSEQ */ + Mem *pMem; /* Used when p4type is P4_MEM */ + sqlite3_vtab *pVtab; /* Used when p4type is P4_VTAB */ + KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */ + } p4; +#ifdef SQLITE_DEBUG + char *zComment; /* Comment to improve readability */ +#endif +#ifdef VDBE_PROFILE + int cnt; /* Number of times this instruction was executed */ + long long cycles; /* Total time spend executing this instruction */ +#endif +}; +typedef struct VdbeOp VdbeOp; + +/* +** A smaller version of VdbeOp used for the VdbeAddOpList() function because +** it takes up less space. +*/ +struct VdbeOpList { + u8 opcode; /* What operation to perform */ + signed char p1; /* First operand */ + signed char p2; /* Second parameter (often the jump destination) */ + signed char p3; /* Third parameter */ +}; +typedef struct VdbeOpList VdbeOpList; + +/* +** Allowed values of VdbeOp.p3type +*/ +#define P4_NOTUSED 0 /* The P4 parameter is not used */ +#define P4_DYNAMIC (-1) /* Pointer to a string obtained from sqliteMalloc() */ +#define P4_STATIC (-2) /* Pointer to a static string */ +#define P4_COLLSEQ (-4) /* P4 is a pointer to a CollSeq structure */ +#define P4_FUNCDEF (-5) /* P4 is a pointer to a FuncDef structure */ +#define P4_KEYINFO (-6) /* P4 is a pointer to a KeyInfo structure */ +#define P4_VDBEFUNC (-7) /* P4 is a pointer to a VdbeFunc structure */ +#define P4_MEM (-8) /* P4 is a pointer to a Mem* structure */ +#define P4_TRANSIENT (-9) /* P4 is a pointer to a transient string */ +#define P4_VTAB (-10) /* P4 is a pointer to an sqlite3_vtab structure */ +#define P4_MPRINTF (-11) /* P4 is a string obtained from sqlite3_mprintf() */ +#define P4_REAL (-12) /* P4 is a 64-bit floating point value */ +#define P4_INT64 (-13) /* P4 is a 64-bit signed integer */ +#define P4_INT32 (-14) /* P4 is a 32-bit signed integer */ + +/* When adding a P4 argument using P4_KEYINFO, a copy of the KeyInfo structure +** is made. That copy is freed when the Vdbe is finalized. But if the +** argument is P4_KEYINFO_HANDOFF, the passed in pointer is used. It still +** gets freed when the Vdbe is finalized so it still should be obtained +** from a single sqliteMalloc(). But no copy is made and the calling +** function should *not* try to free the KeyInfo. +*/ +#define P4_KEYINFO_HANDOFF (-9) + +/* +** The Vdbe.aColName array contains 5n Mem structures, where n is the +** number of columns of data returned by the statement. +*/ +#define COLNAME_NAME 0 +#define COLNAME_DECLTYPE 1 +#define COLNAME_DATABASE 2 +#define COLNAME_TABLE 3 +#define COLNAME_COLUMN 4 +#ifdef SQLITE_ENABLE_COLUMN_METADATA +# define COLNAME_N 5 /* Number of COLNAME_xxx symbols */ +#else +# ifdef SQLITE_OMIT_DECLTYPE +# define COLNAME_N 1 /* Store only the name */ +# else +# define COLNAME_N 2 /* Store the name and decltype */ +# endif +#endif + +/* +** The following macro converts a relative address in the p2 field +** of a VdbeOp structure into a negative number so that +** sqlite3VdbeAddOpList() knows that the address is relative. Calling +** the macro again restores the address. +*/ +#define ADDR(X) (-1-(X)) + +/* +** The makefile scans the vdbe.c source file and creates the "opcodes.h" +** header file that defines a number for each opcode used by the VDBE. +*/ +/************** Include opcodes.h in the middle of vdbe.h ********************/ +/************** Begin file opcodes.h *****************************************/ +/* Automatically generated. Do not edit */ +/* See the mkopcodeh.awk script for details */ +#define OP_VNext 1 +#define OP_Affinity 2 +#define OP_Column 3 +#define OP_SetCookie 4 +#define OP_Real 125 /* same as TK_FLOAT */ +#define OP_Sequence 5 +#define OP_MoveGt 6 +#define OP_Ge 72 /* same as TK_GE */ +#define OP_RowKey 7 +#define OP_SCopy 8 +#define OP_Eq 68 /* same as TK_EQ */ +#define OP_OpenWrite 9 +#define OP_NotNull 66 /* same as TK_NOTNULL */ +#define OP_If 10 +#define OP_ToInt 141 /* same as TK_TO_INT */ +#define OP_String8 88 /* same as TK_STRING */ +#define OP_VRowid 11 +#define OP_CollSeq 12 +#define OP_OpenRead 13 +#define OP_Expire 14 +#define OP_AutoCommit 15 +#define OP_Gt 69 /* same as TK_GT */ +#define OP_IntegrityCk 17 +#define OP_Sort 18 +#define OP_Copy 19 +#define OP_Trace 20 +#define OP_Function 21 +#define OP_IfNeg 22 +#define OP_And 61 /* same as TK_AND */ +#define OP_Subtract 79 /* same as TK_MINUS */ +#define OP_Noop 23 +#define OP_Return 24 +#define OP_Remainder 82 /* same as TK_REM */ +#define OP_NewRowid 25 +#define OP_Multiply 80 /* same as TK_STAR */ +#define OP_Variable 26 +#define OP_String 27 +#define OP_RealAffinity 28 +#define OP_VRename 29 +#define OP_ParseSchema 30 +#define OP_VOpen 31 +#define OP_Close 32 +#define OP_CreateIndex 33 +#define OP_IsUnique 34 +#define OP_NotFound 35 +#define OP_Int64 36 +#define OP_MustBeInt 37 +#define OP_Halt 38 +#define OP_Rowid 39 +#define OP_IdxLT 40 +#define OP_AddImm 41 +#define OP_Statement 42 +#define OP_RowData 43 +#define OP_MemMax 44 +#define OP_Or 60 /* same as TK_OR */ +#define OP_NotExists 45 +#define OP_Gosub 46 +#define OP_Divide 81 /* same as TK_SLASH */ +#define OP_Integer 47 +#define OP_ToNumeric 140 /* same as TK_TO_NUMERIC*/ +#define OP_Prev 48 +#define OP_Concat 83 /* same as TK_CONCAT */ +#define OP_BitAnd 74 /* same as TK_BITAND */ +#define OP_VColumn 49 +#define OP_CreateTable 50 +#define OP_Last 51 +#define OP_IsNull 65 /* same as TK_ISNULL */ +#define OP_IncrVacuum 52 +#define OP_IdxRowid 53 +#define OP_ShiftRight 77 /* same as TK_RSHIFT */ +#define OP_ResetCount 54 +#define OP_FifoWrite 55 +#define OP_ContextPush 56 +#define OP_DropTrigger 57 +#define OP_DropIndex 58 +#define OP_IdxGE 59 +#define OP_IdxDelete 62 +#define OP_Vacuum 63 +#define OP_MoveLe 64 +#define OP_IfNot 73 +#define OP_DropTable 84 +#define OP_MakeRecord 85 +#define OP_ToBlob 139 /* same as TK_TO_BLOB */ +#define OP_ResultRow 86 +#define OP_Delete 89 +#define OP_AggFinal 90 +#define OP_ShiftLeft 76 /* same as TK_LSHIFT */ +#define OP_Goto 91 +#define OP_TableLock 92 +#define OP_FifoRead 93 +#define OP_Clear 94 +#define OP_MoveLt 95 +#define OP_Le 70 /* same as TK_LE */ +#define OP_VerifyCookie 96 +#define OP_AggStep 97 +#define OP_ToText 138 /* same as TK_TO_TEXT */ +#define OP_Not 16 /* same as TK_NOT */ +#define OP_ToReal 142 /* same as TK_TO_REAL */ +#define OP_SetNumColumns 98 +#define OP_Transaction 99 +#define OP_VFilter 100 +#define OP_Ne 67 /* same as TK_NE */ +#define OP_VDestroy 101 +#define OP_ContextPop 102 +#define OP_BitOr 75 /* same as TK_BITOR */ +#define OP_Next 103 +#define OP_IdxInsert 104 +#define OP_Lt 71 /* same as TK_LT */ +#define OP_Insert 105 +#define OP_Destroy 106 +#define OP_ReadCookie 107 +#define OP_ForceInt 108 +#define OP_LoadAnalysis 109 +#define OP_Explain 110 +#define OP_OpenPseudo 111 +#define OP_OpenEphemeral 112 +#define OP_Null 113 +#define OP_Move 114 +#define OP_Blob 115 +#define OP_Add 78 /* same as TK_PLUS */ +#define OP_Rewind 116 +#define OP_MoveGe 117 +#define OP_VBegin 118 +#define OP_VUpdate 119 +#define OP_IfZero 120 +#define OP_BitNot 87 /* same as TK_BITNOT */ +#define OP_VCreate 121 +#define OP_Found 122 +#define OP_IfPos 123 +#define OP_NullRow 124 + +/* The following opcode values are never used */ +#define OP_NotUsed_126 126 +#define OP_NotUsed_127 127 +#define OP_NotUsed_128 128 +#define OP_NotUsed_129 129 +#define OP_NotUsed_130 130 +#define OP_NotUsed_131 131 +#define OP_NotUsed_132 132 +#define OP_NotUsed_133 133 +#define OP_NotUsed_134 134 +#define OP_NotUsed_135 135 +#define OP_NotUsed_136 136 +#define OP_NotUsed_137 137 + + +/* Properties such as "out2" or "jump" that are specified in +** comments following the "case" for each opcode in the vdbe.c +** are encoded into bitvectors as follows: +*/ +#define OPFLG_JUMP 0x0001 /* jump: P2 holds jmp target */ +#define OPFLG_OUT2_PRERELEASE 0x0002 /* out2-prerelease: */ +#define OPFLG_IN1 0x0004 /* in1: P1 is an input */ +#define OPFLG_IN2 0x0008 /* in2: P2 is an input */ +#define OPFLG_IN3 0x0010 /* in3: P3 is an input */ +#define OPFLG_OUT3 0x0020 /* out3: P3 is an output */ +#define OPFLG_INITIALIZER {\ +/* 0 */ 0x00, 0x01, 0x00, 0x00, 0x10, 0x02, 0x11, 0x00,\ +/* 8 */ 0x00, 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00,\ +/* 16 */ 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00,\ +/* 24 */ 0x00, 0x02, 0x02, 0x02, 0x04, 0x00, 0x00, 0x00,\ +/* 32 */ 0x00, 0x02, 0x11, 0x11, 0x02, 0x05, 0x00, 0x02,\ +/* 40 */ 0x11, 0x04, 0x00, 0x00, 0x0c, 0x11, 0x01, 0x02,\ +/* 48 */ 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x04,\ +/* 56 */ 0x00, 0x00, 0x00, 0x11, 0x2c, 0x2c, 0x00, 0x00,\ +/* 64 */ 0x11, 0x05, 0x05, 0x15, 0x15, 0x15, 0x15, 0x15,\ +/* 72 */ 0x15, 0x05, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,\ +/* 80 */ 0x2c, 0x2c, 0x2c, 0x2c, 0x00, 0x00, 0x00, 0x04,\ +/* 88 */ 0x02, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x11,\ +/* 96 */ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,\ +/* 104 */ 0x08, 0x00, 0x02, 0x02, 0x05, 0x00, 0x00, 0x00,\ +/* 112 */ 0x00, 0x02, 0x00, 0x02, 0x01, 0x11, 0x00, 0x00,\ +/* 120 */ 0x05, 0x00, 0x11, 0x05, 0x00, 0x02, 0x00, 0x00,\ +/* 128 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 136 */ 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04,} + +/************** End of opcodes.h *********************************************/ +/************** Continuing where we left off in vdbe.h ***********************/ + +/* +** Prototypes for the VDBE interface. See comments on the implementation +** for a description of what each of these routines does. +*/ +SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(sqlite3*); +SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe*,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe*,int,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe*,int,int,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe*,int,int,int,int); +SQLITE_PRIVATE int sqlite3VdbeAddOp4(Vdbe*,int,int,int,int,const char *zP4,int); +SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp); +SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe*, int addr, int P1); +SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe*, int addr, int P2); +SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe*, int addr, int P3); +SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe*, u8 P5); +SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe*, int addr); +SQLITE_PRIVATE void sqlite3VdbeChangeToNoop(Vdbe*, int addr, int N); +SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N); +SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe*, int); +SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe*, int); +SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeMakeReady(Vdbe*,int,int,int,int); +SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe*, int); +SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe*); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE void sqlite3VdbeTrace(Vdbe*,FILE*); +#endif +SQLITE_PRIVATE void sqlite3VdbeResetStepResult(Vdbe*); +SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe*, int); +SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe*,int); +SQLITE_PRIVATE int sqlite3VdbeSetColName(Vdbe*, int, int, const char *, int); +SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe*); +SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe*); +SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe*, const char *z, int n); +SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe*,Vdbe*); + +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT +SQLITE_PRIVATE int sqlite3VdbeReleaseMemory(int); +#endif +SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,void*,int); +SQLITE_PRIVATE void sqlite3VdbeDeleteUnpackedRecord(UnpackedRecord*); +SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*); + + +#ifndef NDEBUG +SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe*, const char*, ...); +# define VdbeComment(X) sqlite3VdbeComment X +#else +# define VdbeComment(X) +#endif + +#endif + +/************** End of vdbe.h ************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include pager.h in the middle of sqliteInt.h *****************/ +/************** Begin file pager.h *******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite page cache +** subsystem. The page cache subsystem reads and writes a file a page +** at a time and provides a journal for rollback. +** +** @(#) $Id: pager.h,v 1.72 2008/05/01 17:03:49 drh Exp $ +*/ + +#ifndef _PAGER_H_ +#define _PAGER_H_ + +/* +** The type used to represent a page number. The first page in a file +** is called page 1. 0 is used to represent "not a page". +*/ +typedef unsigned int Pgno; + +/* +** Each open file is managed by a separate instance of the "Pager" structure. +*/ +typedef struct Pager Pager; + +/* +** Handle type for pages. +*/ +typedef struct PgHdr DbPage; + +/* +** Allowed values for the flags parameter to sqlite3PagerOpen(). +** +** NOTE: This values must match the corresponding BTREE_ values in btree.h. +*/ +#define PAGER_OMIT_JOURNAL 0x0001 /* Do not use a rollback journal */ +#define PAGER_NO_READLOCK 0x0002 /* Omit readlocks on readonly files */ + +/* +** Valid values for the second argument to sqlite3PagerLockingMode(). +*/ +#define PAGER_LOCKINGMODE_QUERY -1 +#define PAGER_LOCKINGMODE_NORMAL 0 +#define PAGER_LOCKINGMODE_EXCLUSIVE 1 + +/* +** Valid values for the second argument to sqlite3PagerJournalMode(). +*/ +#define PAGER_JOURNALMODE_QUERY -1 +#define PAGER_JOURNALMODE_DELETE 0 /* Commit by deleting journal file */ +#define PAGER_JOURNALMODE_PERSIST 1 /* Commit by zeroing journal header */ +#define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */ + +/* +** See source code comments for a detailed description of the following +** routines: +*/ +SQLITE_PRIVATE int sqlite3PagerOpen(sqlite3_vfs *, Pager **ppPager, const char*, int,int,int); +SQLITE_PRIVATE void sqlite3PagerSetBusyhandler(Pager*, BusyHandler *pBusyHandler); +SQLITE_PRIVATE void sqlite3PagerSetDestructor(Pager*, void(*)(DbPage*,int)); +SQLITE_PRIVATE void sqlite3PagerSetReiniter(Pager*, void(*)(DbPage*,int)); +SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager*, u16*); +SQLITE_PRIVATE int sqlite3PagerMaxPageCount(Pager*, int); +SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager*, int, unsigned char*); +SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager*, int); +SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager); +SQLITE_PRIVATE int sqlite3PagerAcquire(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag); +#define sqlite3PagerGet(A,B,C) sqlite3PagerAcquire(A,B,C,0) +SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno); +SQLITE_PRIVATE int sqlite3PagerRef(DbPage*); +SQLITE_PRIVATE int sqlite3PagerUnref(DbPage*); +SQLITE_PRIVATE int sqlite3PagerWrite(DbPage*); +SQLITE_PRIVATE int sqlite3PagerPagecount(Pager*); +SQLITE_PRIVATE int sqlite3PagerTruncate(Pager*,Pgno); +SQLITE_PRIVATE int sqlite3PagerBegin(DbPage*, int exFlag); +SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, Pgno, int); +SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager*); +SQLITE_PRIVATE int sqlite3PagerRollback(Pager*); +SQLITE_PRIVATE int sqlite3PagerIsreadonly(Pager*); +SQLITE_PRIVATE int sqlite3PagerStmtBegin(Pager*); +SQLITE_PRIVATE int sqlite3PagerStmtCommit(Pager*); +SQLITE_PRIVATE int sqlite3PagerStmtRollback(Pager*); +SQLITE_PRIVATE void sqlite3PagerDontRollback(DbPage*); +SQLITE_PRIVATE void sqlite3PagerDontWrite(DbPage*); +SQLITE_PRIVATE int sqlite3PagerRefcount(Pager*); +SQLITE_PRIVATE void sqlite3PagerSetSafetyLevel(Pager*,int,int); +SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager*); +SQLITE_PRIVATE const sqlite3_vfs *sqlite3PagerVfs(Pager*); +SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager*); +SQLITE_PRIVATE const char *sqlite3PagerDirname(Pager*); +SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager*); +SQLITE_PRIVATE int sqlite3PagerNosync(Pager*); +SQLITE_PRIVATE int sqlite3PagerMovepage(Pager*,DbPage*,Pgno); +SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *); +SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *); +SQLITE_PRIVATE int sqlite3PagerLockingMode(Pager *, int); +SQLITE_PRIVATE int sqlite3PagerJournalMode(Pager *, int); +SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*); +SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager); + +#if defined(SQLITE_ENABLE_MEMORY_MANAGEMENT) && !defined(SQLITE_OMIT_DISKIO) +SQLITE_PRIVATE int sqlite3PagerReleaseMemory(int); +#endif + +#ifdef SQLITE_HAS_CODEC +SQLITE_PRIVATE void sqlite3PagerSetCodec(Pager*,void*(*)(void*,void*,Pgno,int),void*); +#endif + +#if !defined(NDEBUG) || defined(SQLITE_TEST) +SQLITE_PRIVATE Pgno sqlite3PagerPagenumber(DbPage*); +SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage*); +#endif + +#ifdef SQLITE_TEST +SQLITE_PRIVATE int *sqlite3PagerStats(Pager*); +SQLITE_PRIVATE void sqlite3PagerRefdump(Pager*); +#endif + +#ifdef SQLITE_TEST +void disable_simulated_io_errors(void); +void enable_simulated_io_errors(void); +#else +# define disable_simulated_io_errors() +# define enable_simulated_io_errors() +#endif + +#endif /* _PAGER_H_ */ + +/************** End of pager.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + +/************** Include os.h in the middle of sqliteInt.h ********************/ +/************** Begin file os.h **********************************************/ +/* +** 2001 September 16 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This header file (together with is companion C source-code file +** "os.c") attempt to abstract the underlying operating system so that +** the SQLite library will work on both POSIX and windows systems. +** +** This header file is #include-ed by sqliteInt.h and thus ends up +** being included by every source file. +*/ +#ifndef _SQLITE_OS_H_ +#define _SQLITE_OS_H_ + +/* +** Figure out if we are dealing with Unix, Windows, or some other +** operating system. After the following block of preprocess macros, +** all of OS_UNIX, OS_WIN, OS_OS2, and OS_OTHER will defined to either +** 1 or 0. One of the four will be 1. The other three will be 0. +*/ +#if defined(OS_OTHER) +# if OS_OTHER==1 +# undef OS_UNIX +# define OS_UNIX 0 +# undef OS_WIN +# define OS_WIN 0 +# undef OS_OS2 +# define OS_OS2 0 +# else +# undef OS_OTHER +# endif +#endif +#if !defined(OS_UNIX) && !defined(OS_OTHER) +# define OS_OTHER 0 +# ifndef OS_WIN +# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) +# define OS_WIN 1 +# define OS_UNIX 0 +# define OS_OS2 0 +# elif defined(__EMX__) || defined(_OS2) || defined(OS2) || defined(_OS2_) || defined(__OS2__) +# define OS_WIN 0 +# define OS_UNIX 0 +# define OS_OS2 1 +# else +# define OS_WIN 0 +# define OS_UNIX 1 +# define OS_OS2 0 +# endif +# else +# define OS_UNIX 0 +# define OS_OS2 0 +# endif +#else +# ifndef OS_WIN +# define OS_WIN 0 +# endif +#endif + + + +/* +** Define the maximum size of a temporary filename +*/ +#if OS_WIN +# include +# define SQLITE_TEMPNAME_SIZE (MAX_PATH+50) +#elif OS_OS2 +# if (__GNUC__ > 3 || __GNUC__ == 3 && __GNUC_MINOR__ >= 3) && defined(OS2_HIGH_MEMORY) +# include /* has to be included before os2.h for linking to work */ +# endif +# define INCL_DOSDATETIME +# define INCL_DOSFILEMGR +# define INCL_DOSERRORS +# define INCL_DOSMISC +# define INCL_DOSPROCESS +# define INCL_DOSMODULEMGR +# define INCL_DOSSEMAPHORES +# include +# include +# define SQLITE_TEMPNAME_SIZE (CCHMAXPATHCOMP) +#else +# define SQLITE_TEMPNAME_SIZE 200 +#endif + +/* If the SET_FULLSYNC macro is not defined above, then make it +** a no-op +*/ +#ifndef SET_FULLSYNC +# define SET_FULLSYNC(x,y) +#endif + +/* +** The default size of a disk sector +*/ +#ifndef SQLITE_DEFAULT_SECTOR_SIZE +# define SQLITE_DEFAULT_SECTOR_SIZE 512 +#endif + +/* +** Temporary files are named starting with this prefix followed by 16 random +** alphanumeric characters, and no file extension. They are stored in the +** OS's standard temporary file directory, and are deleted prior to exit. +** If sqlite is being embedded in another program, you may wish to change the +** prefix to reflect your program's name, so that if your program exits +** prematurely, old temporary files can be easily identified. This can be done +** using -DSQLITE_TEMP_FILE_PREFIX=myprefix_ on the compiler command line. +** +** 2006-10-31: The default prefix used to be "sqlite_". But then +** Mcafee started using SQLite in their anti-virus product and it +** started putting files with the "sqlite" name in the c:/temp folder. +** This annoyed many windows users. Those users would then do a +** Google search for "sqlite", find the telephone numbers of the +** developers and call to wake them up at night and complain. +** For this reason, the default name prefix is changed to be "sqlite" +** spelled backwards. So the temp files are still identified, but +** anybody smart enough to figure out the code is also likely smart +** enough to know that calling the developer will not help get rid +** of the file. +*/ +#ifndef SQLITE_TEMP_FILE_PREFIX +# define SQLITE_TEMP_FILE_PREFIX "etilqs_" +#endif + +/* +** The following values may be passed as the second argument to +** sqlite3OsLock(). The various locks exhibit the following semantics: +** +** SHARED: Any number of processes may hold a SHARED lock simultaneously. +** RESERVED: A single process may hold a RESERVED lock on a file at +** any time. Other processes may hold and obtain new SHARED locks. +** PENDING: A single process may hold a PENDING lock on a file at +** any one time. Existing SHARED locks may persist, but no new +** SHARED locks may be obtained by other processes. +** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks. +** +** PENDING_LOCK may not be passed directly to sqlite3OsLock(). Instead, a +** process that requests an EXCLUSIVE lock may actually obtain a PENDING +** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to +** sqlite3OsLock(). +*/ +#define NO_LOCK 0 +#define SHARED_LOCK 1 +#define RESERVED_LOCK 2 +#define PENDING_LOCK 3 +#define EXCLUSIVE_LOCK 4 + +/* +** File Locking Notes: (Mostly about windows but also some info for Unix) +** +** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because +** those functions are not available. So we use only LockFile() and +** UnlockFile(). +** +** LockFile() prevents not just writing but also reading by other processes. +** A SHARED_LOCK is obtained by locking a single randomly-chosen +** byte out of a specific range of bytes. The lock byte is obtained at +** random so two separate readers can probably access the file at the +** same time, unless they are unlucky and choose the same lock byte. +** An EXCLUSIVE_LOCK is obtained by locking all bytes in the range. +** There can only be one writer. A RESERVED_LOCK is obtained by locking +** a single byte of the file that is designated as the reserved lock byte. +** A PENDING_LOCK is obtained by locking a designated byte different from +** the RESERVED_LOCK byte. +** +** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available, +** which means we can use reader/writer locks. When reader/writer locks +** are used, the lock is placed on the same range of bytes that is used +** for probabilistic locking in Win95/98/ME. Hence, the locking scheme +** will support two or more Win95 readers or two or more WinNT readers. +** But a single Win95 reader will lock out all WinNT readers and a single +** WinNT reader will lock out all other Win95 readers. +** +** The following #defines specify the range of bytes used for locking. +** SHARED_SIZE is the number of bytes available in the pool from which +** a random byte is selected for a shared lock. The pool of bytes for +** shared locks begins at SHARED_FIRST. +** +** These #defines are available in sqlite_aux.h so that adaptors for +** connecting SQLite to other operating systems can use the same byte +** ranges for locking. In particular, the same locking strategy and +** byte ranges are used for Unix. This leaves open the possiblity of having +** clients on win95, winNT, and unix all talking to the same shared file +** and all locking correctly. To do so would require that samba (or whatever +** tool is being used for file sharing) implements locks correctly between +** windows and unix. I'm guessing that isn't likely to happen, but by +** using the same locking range we are at least open to the possibility. +** +** Locking in windows is manditory. For this reason, we cannot store +** actual data in the bytes used for locking. The pager never allocates +** the pages involved in locking therefore. SHARED_SIZE is selected so +** that all locks will fit on a single page even at the minimum page size. +** PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE +** is set high so that we don't have to allocate an unused page except +** for very large databases. But one should test the page skipping logic +** by setting PENDING_BYTE low and running the entire regression suite. +** +** Changing the value of PENDING_BYTE results in a subtly incompatible +** file format. Depending on how it is changed, you might not notice +** the incompatibility right away, even running a full regression test. +** The default location of PENDING_BYTE is the first byte past the +** 1GB boundary. +** +*/ +#ifndef SQLITE_TEST +#define PENDING_BYTE 0x40000000 /* First byte past the 1GB boundary */ +#else +SQLITE_API extern unsigned int sqlite3_pending_byte; +#define PENDING_BYTE sqlite3_pending_byte +#endif + +#define RESERVED_BYTE (PENDING_BYTE+1) +#define SHARED_FIRST (PENDING_BYTE+2) +#define SHARED_SIZE 510 + +/* +** Functions for accessing sqlite3_file methods +*/ +SQLITE_PRIVATE int sqlite3OsClose(sqlite3_file*); +SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file*, void*, int amt, i64 offset); +SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file*, const void*, int amt, i64 offset); +SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file*, i64 size); +SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file*, int); +SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file*, i64 *pSize); +SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file*, int); +SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file*, int); +SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id); +SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file*,int,void*); +SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id); +SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id); + +/* +** Functions for accessing sqlite3_vfs methods +*/ +SQLITE_PRIVATE int sqlite3OsOpen(sqlite3_vfs *, const char *, sqlite3_file*, int, int *); +SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *, const char *, int); +SQLITE_PRIVATE int sqlite3OsAccess(sqlite3_vfs *, const char *, int); +SQLITE_PRIVATE int sqlite3OsGetTempname(sqlite3_vfs *, int, char *); +SQLITE_PRIVATE int sqlite3OsFullPathname(sqlite3_vfs *, const char *, int, char *); +SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *, const char *); +SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *, int, char *); +SQLITE_PRIVATE void *sqlite3OsDlSym(sqlite3_vfs *, void *, const char *); +SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *, void *); +SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *, int, char *); +SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *, int); +SQLITE_PRIVATE int sqlite3OsCurrentTime(sqlite3_vfs *, double*); + +/* +** Convenience functions for opening and closing files using +** sqlite3_malloc() to obtain space for the file-handle structure. +*/ +SQLITE_PRIVATE int sqlite3OsOpenMalloc(sqlite3_vfs *, const char *, sqlite3_file **, int,int*); +SQLITE_PRIVATE int sqlite3OsCloseFree(sqlite3_file *); + +/* +** Each OS-specific backend defines an instance of the following +** structure for returning a pointer to its sqlite3_vfs. If OS_OTHER +** is defined (meaning that the application-defined OS interface layer +** is used) then there is no default VFS. The application must +** register one or more VFS structures using sqlite3_vfs_register() +** before attempting to use SQLite. +*/ +SQLITE_PRIVATE sqlite3_vfs *sqlite3OsDefaultVfs(void); + +#endif /* _SQLITE_OS_H_ */ + +/************** End of os.h **************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include mutex.h in the middle of sqliteInt.h *****************/ +/************** Begin file mutex.h *******************************************/ +/* +** 2007 August 28 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains the common header for all mutex implementations. +** The sqliteInt.h header #includes this file so that it is available +** to all source files. We break it out in an effort to keep the code +** better organized. +** +** NOTE: source files should *not* #include this header file directly. +** Source files should #include the sqliteInt.h file and let that file +** include this one indirectly. +** +** $Id: mutex.h,v 1.2 2007/08/30 14:10:30 drh Exp $ +*/ + + +#ifdef SQLITE_MUTEX_APPDEF +/* +** If SQLITE_MUTEX_APPDEF is defined, then this whole module is +** omitted and equivalent functionality must be provided by the +** application that links against the SQLite library. +*/ +#else +/* +** Figure out what version of the code to use. The choices are +** +** SQLITE_MUTEX_NOOP For single-threaded applications that +** do not desire error checking. +** +** SQLITE_MUTEX_NOOP_DEBUG For single-threaded applications with +** error checking to help verify that mutexes +** are being used correctly even though they +** are not needed. Used when SQLITE_DEBUG is +** defined on single-threaded builds. +** +** SQLITE_MUTEX_PTHREADS For multi-threaded applications on Unix. +** +** SQLITE_MUTEX_W32 For multi-threaded applications on Win32. +** +** SQLITE_MUTEX_OS2 For multi-threaded applications on OS/2. +*/ +#define SQLITE_MUTEX_NOOP 1 /* The default */ +#if defined(SQLITE_DEBUG) && !SQLITE_THREADSAFE +# undef SQLITE_MUTEX_NOOP +# define SQLITE_MUTEX_NOOP_DEBUG +#endif +#if defined(SQLITE_MUTEX_NOOP) && SQLITE_THREADSAFE && OS_UNIX +# undef SQLITE_MUTEX_NOOP +# define SQLITE_MUTEX_PTHREADS +#endif +#if defined(SQLITE_MUTEX_NOOP) && SQLITE_THREADSAFE && OS_WIN +# undef SQLITE_MUTEX_NOOP +# define SQLITE_MUTEX_W32 +#endif +#if defined(SQLITE_MUTEX_NOOP) && SQLITE_THREADSAFE && OS_OS2 +# undef SQLITE_MUTEX_NOOP +# define SQLITE_MUTEX_OS2 +#endif + +#ifdef SQLITE_MUTEX_NOOP +/* +** If this is a no-op implementation, implement everything as macros. +*/ +#define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) +#define sqlite3_mutex_free(X) +#define sqlite3_mutex_enter(X) +#define sqlite3_mutex_try(X) SQLITE_OK +#define sqlite3_mutex_leave(X) +#define sqlite3_mutex_held(X) 1 +#define sqlite3_mutex_notheld(X) 1 +#endif + +#endif /* SQLITE_MUTEX_APPDEF */ + +/************** End of mutex.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + + +/* +** Each database file to be accessed by the system is an instance +** of the following structure. There are normally two of these structures +** in the sqlite.aDb[] array. aDb[0] is the main database file and +** aDb[1] is the database file used to hold temporary tables. Additional +** databases may be attached. +*/ +struct Db { + char *zName; /* Name of this database */ + Btree *pBt; /* The B*Tree structure for this database file */ + u8 inTrans; /* 0: not writable. 1: Transaction. 2: Checkpoint */ + u8 safety_level; /* How aggressive at synching data to disk */ + void *pAux; /* Auxiliary data. Usually NULL */ + void (*xFreeAux)(void*); /* Routine to free pAux */ + Schema *pSchema; /* Pointer to database schema (possibly shared) */ +}; + +/* +** An instance of the following structure stores a database schema. +** +** If there are no virtual tables configured in this schema, the +** Schema.db variable is set to NULL. After the first virtual table +** has been added, it is set to point to the database connection +** used to create the connection. Once a virtual table has been +** added to the Schema structure and the Schema.db variable populated, +** only that database connection may use the Schema to prepare +** statements. +*/ +struct Schema { + int schema_cookie; /* Database schema version number for this file */ + Hash tblHash; /* All tables indexed by name */ + Hash idxHash; /* All (named) indices indexed by name */ + Hash trigHash; /* All triggers indexed by name */ + Hash aFKey; /* Foreign keys indexed by to-table */ + Table *pSeqTab; /* The sqlite_sequence table used by AUTOINCREMENT */ + u8 file_format; /* Schema format version for this file */ + u8 enc; /* Text encoding used by this database */ + u16 flags; /* Flags associated with this schema */ + int cache_size; /* Number of pages to use in the cache */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + sqlite3 *db; /* "Owner" connection. See comment above */ +#endif +}; + +/* +** These macros can be used to test, set, or clear bits in the +** Db.flags field. +*/ +#define DbHasProperty(D,I,P) (((D)->aDb[I].pSchema->flags&(P))==(P)) +#define DbHasAnyProperty(D,I,P) (((D)->aDb[I].pSchema->flags&(P))!=0) +#define DbSetProperty(D,I,P) (D)->aDb[I].pSchema->flags|=(P) +#define DbClearProperty(D,I,P) (D)->aDb[I].pSchema->flags&=~(P) + +/* +** Allowed values for the DB.flags field. +** +** The DB_SchemaLoaded flag is set after the database schema has been +** read into internal hash tables. +** +** DB_UnresetViews means that one or more views have column names that +** have been filled out. If the schema changes, these column names might +** changes and so the view will need to be reset. +*/ +#define DB_SchemaLoaded 0x0001 /* The schema has been loaded */ +#define DB_UnresetViews 0x0002 /* Some views have defined column names */ +#define DB_Empty 0x0004 /* The file is empty (length 0 bytes) */ + +/* +** The number of different kinds of things that can be limited +** using the sqlite3_limit() interface. +*/ +#define SQLITE_N_LIMIT (SQLITE_LIMIT_VARIABLE_NUMBER+1) + +/* +** Each database is an instance of the following structure. +** +** The sqlite.lastRowid records the last insert rowid generated by an +** insert statement. Inserts on views do not affect its value. Each +** trigger has its own context, so that lastRowid can be updated inside +** triggers as usual. The previous value will be restored once the trigger +** exits. Upon entering a before or instead of trigger, lastRowid is no +** longer (since after version 2.8.12) reset to -1. +** +** The sqlite.nChange does not count changes within triggers and keeps no +** context. It is reset at start of sqlite3_exec. +** The sqlite.lsChange represents the number of changes made by the last +** insert, update, or delete statement. It remains constant throughout the +** length of a statement and is then updated by OP_SetCounts. It keeps a +** context stack just like lastRowid so that the count of changes +** within a trigger is not seen outside the trigger. Changes to views do not +** affect the value of lsChange. +** The sqlite.csChange keeps track of the number of current changes (since +** the last statement) and is used to update sqlite_lsChange. +** +** The member variables sqlite.errCode, sqlite.zErrMsg and sqlite.zErrMsg16 +** store the most recent error code and, if applicable, string. The +** internal function sqlite3Error() is used to set these variables +** consistently. +*/ +struct sqlite3 { + sqlite3_vfs *pVfs; /* OS Interface */ + int nDb; /* Number of backends currently in use */ + Db *aDb; /* All backends */ + int flags; /* Miscellanous flags. See below */ + int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */ + int errCode; /* Most recent error code (SQLITE_*) */ + int errMask; /* & result codes with this before returning */ + u8 autoCommit; /* The auto-commit flag. */ + u8 temp_store; /* 1: file 2: memory 0: default */ + u8 mallocFailed; /* True if we have seen a malloc failure */ + u8 dfltLockMode; /* Default locking-mode for attached dbs */ + u8 dfltJournalMode; /* Default journal mode for attached dbs */ + signed char nextAutovac; /* Autovac setting after VACUUM if >=0 */ + int nextPagesize; /* Pagesize after VACUUM if >0 */ + int nTable; /* Number of tables in the database */ + CollSeq *pDfltColl; /* The default collating sequence (BINARY) */ + i64 lastRowid; /* ROWID of most recent insert (see above) */ + i64 priorNewRowid; /* Last randomly generated ROWID */ + int magic; /* Magic number for detect library misuse */ + int nChange; /* Value returned by sqlite3_changes() */ + int nTotalChange; /* Value returned by sqlite3_total_changes() */ + sqlite3_mutex *mutex; /* Connection mutex */ + int aLimit[SQLITE_N_LIMIT]; /* Limits */ + struct sqlite3InitInfo { /* Information used during initialization */ + int iDb; /* When back is being initialized */ + int newTnum; /* Rootpage of table being initialized */ + u8 busy; /* TRUE if currently initializing */ + } init; + int nExtension; /* Number of loaded extensions */ + void **aExtension; /* Array of shared libraray handles */ + struct Vdbe *pVdbe; /* List of active virtual machines */ + int activeVdbeCnt; /* Number of vdbes currently executing */ + void (*xTrace)(void*,const char*); /* Trace function */ + void *pTraceArg; /* Argument to the trace function */ + void (*xProfile)(void*,const char*,u64); /* Profiling function */ + void *pProfileArg; /* Argument to profile function */ + void *pCommitArg; /* Argument to xCommitCallback() */ + int (*xCommitCallback)(void*); /* Invoked at every commit. */ + void *pRollbackArg; /* Argument to xRollbackCallback() */ + void (*xRollbackCallback)(void*); /* Invoked at every commit. */ + void *pUpdateArg; + void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64); + void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*); + void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*); + void *pCollNeededArg; + sqlite3_value *pErr; /* Most recent error message */ + char *zErrMsg; /* Most recent error message (UTF-8 encoded) */ + char *zErrMsg16; /* Most recent error message (UTF-16 encoded) */ + union { + int isInterrupted; /* True if sqlite3_interrupt has been called */ + double notUsed1; /* Spacer */ + } u1; +#ifndef SQLITE_OMIT_AUTHORIZATION + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*); + /* Access authorization function */ + void *pAuthArg; /* 1st argument to the access auth function */ +#endif +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + int (*xProgress)(void *); /* The progress callback */ + void *pProgressArg; /* Argument to the progress callback */ + int nProgressOps; /* Number of opcodes for progress callback */ +#endif +#ifndef SQLITE_OMIT_VIRTUALTABLE + Hash aModule; /* populated by sqlite3_create_module() */ + Table *pVTab; /* vtab with active Connect/Create method */ + sqlite3_vtab **aVTrans; /* Virtual tables with open transactions */ + int nVTrans; /* Allocated size of aVTrans */ +#endif + Hash aFunc; /* All functions that can be in SQL exprs */ + Hash aCollSeq; /* All collating sequences */ + BusyHandler busyHandler; /* Busy callback */ + int busyTimeout; /* Busy handler timeout, in msec */ + Db aDbStatic[2]; /* Static space for the 2 default backends */ +#ifdef SQLITE_SSE + sqlite3_stmt *pFetch; /* Used by SSE to fetch stored statements */ +#endif +}; + +/* +** A macro to discover the encoding of a database. +*/ +#define ENC(db) ((db)->aDb[0].pSchema->enc) + +/* +** Possible values for the sqlite.flags and or Db.flags fields. +** +** On sqlite.flags, the SQLITE_InTrans value means that we have +** executed a BEGIN. On Db.flags, SQLITE_InTrans means a statement +** transaction is active on that particular database file. +*/ +#define SQLITE_VdbeTrace 0x00000001 /* True to trace VDBE execution */ +#define SQLITE_InTrans 0x00000008 /* True if in a transaction */ +#define SQLITE_InternChanges 0x00000010 /* Uncommitted Hash table changes */ +#define SQLITE_FullColNames 0x00000020 /* Show full column names on SELECT */ +#define SQLITE_ShortColNames 0x00000040 /* Show short columns names */ +#define SQLITE_CountRows 0x00000080 /* Count rows changed by INSERT, */ + /* DELETE, or UPDATE and return */ + /* the count using a callback. */ +#define SQLITE_NullCallback 0x00000100 /* Invoke the callback once if the */ + /* result set is empty */ +#define SQLITE_SqlTrace 0x00000200 /* Debug print SQL as it executes */ +#define SQLITE_VdbeListing 0x00000400 /* Debug listings of VDBE programs */ +#define SQLITE_WriteSchema 0x00000800 /* OK to update SQLITE_MASTER */ +#define SQLITE_NoReadlock 0x00001000 /* Readlocks are omitted when + ** accessing read-only databases */ +#define SQLITE_IgnoreChecks 0x00002000 /* Do not enforce check constraints */ +#define SQLITE_ReadUncommitted 0x00004000 /* For shared-cache mode */ +#define SQLITE_LegacyFileFmt 0x00008000 /* Create new databases in format 1 */ +#define SQLITE_FullFSync 0x00010000 /* Use full fsync on the backend */ +#define SQLITE_LoadExtension 0x00020000 /* Enable load_extension */ + +#define SQLITE_RecoveryMode 0x00040000 /* Ignore schema errors */ +#define SQLITE_SharedCache 0x00080000 /* Cache sharing is enabled */ +#define SQLITE_Vtab 0x00100000 /* There exists a virtual table */ + +/* +** Possible values for the sqlite.magic field. +** The numbers are obtained at random and have no special meaning, other +** than being distinct from one another. +*/ +#define SQLITE_MAGIC_OPEN 0xa029a697 /* Database is open */ +#define SQLITE_MAGIC_CLOSED 0x9f3c2d33 /* Database is closed */ +#define SQLITE_MAGIC_SICK 0x4b771290 /* Error and awaiting close */ +#define SQLITE_MAGIC_BUSY 0xf03b7906 /* Database currently in use */ +#define SQLITE_MAGIC_ERROR 0xb5357930 /* An SQLITE_MISUSE error occurred */ + +/* +** Each SQL function is defined by an instance of the following +** structure. A pointer to this structure is stored in the sqlite.aFunc +** hash table. When multiple functions have the same name, the hash table +** points to a linked list of these structures. +*/ +struct FuncDef { + i16 nArg; /* Number of arguments. -1 means unlimited */ + u8 iPrefEnc; /* Preferred text encoding (SQLITE_UTF8, 16LE, 16BE) */ + u8 needCollSeq; /* True if sqlite3GetFuncCollSeq() might be called */ + u8 flags; /* Some combination of SQLITE_FUNC_* */ + void *pUserData; /* User data parameter */ + FuncDef *pNext; /* Next function with same name */ + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); /* Regular function */ + void (*xStep)(sqlite3_context*,int,sqlite3_value**); /* Aggregate step */ + void (*xFinalize)(sqlite3_context*); /* Aggregate finializer */ + char zName[1]; /* SQL name of the function. MUST BE LAST */ +}; + +/* +** Each SQLite module (virtual table definition) is defined by an +** instance of the following structure, stored in the sqlite3.aModule +** hash table. +*/ +struct Module { + const sqlite3_module *pModule; /* Callback pointers */ + const char *zName; /* Name passed to create_module() */ + void *pAux; /* pAux passed to create_module() */ + void (*xDestroy)(void *); /* Module destructor function */ +}; + +/* +** Possible values for FuncDef.flags +*/ +#define SQLITE_FUNC_LIKE 0x01 /* Candidate for the LIKE optimization */ +#define SQLITE_FUNC_CASE 0x02 /* Case-sensitive LIKE-type function */ +#define SQLITE_FUNC_EPHEM 0x04 /* Ephermeral. Delete with VDBE */ + +/* +** information about each column of an SQL table is held in an instance +** of this structure. +*/ +struct Column { + char *zName; /* Name of this column */ + Expr *pDflt; /* Default value of this column */ + char *zType; /* Data type for this column */ + char *zColl; /* Collating sequence. If NULL, use the default */ + u8 notNull; /* True if there is a NOT NULL constraint */ + u8 isPrimKey; /* True if this column is part of the PRIMARY KEY */ + char affinity; /* One of the SQLITE_AFF_... values */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + u8 isHidden; /* True if this column is 'hidden' */ +#endif +}; + +/* +** A "Collating Sequence" is defined by an instance of the following +** structure. Conceptually, a collating sequence consists of a name and +** a comparison routine that defines the order of that sequence. +** +** There may two seperate implementations of the collation function, one +** that processes text in UTF-8 encoding (CollSeq.xCmp) and another that +** processes text encoded in UTF-16 (CollSeq.xCmp16), using the machine +** native byte order. When a collation sequence is invoked, SQLite selects +** the version that will require the least expensive encoding +** translations, if any. +** +** The CollSeq.pUser member variable is an extra parameter that passed in +** as the first argument to the UTF-8 comparison function, xCmp. +** CollSeq.pUser16 is the equivalent for the UTF-16 comparison function, +** xCmp16. +** +** If both CollSeq.xCmp and CollSeq.xCmp16 are NULL, it means that the +** collating sequence is undefined. Indices built on an undefined +** collating sequence may not be read or written. +*/ +struct CollSeq { + char *zName; /* Name of the collating sequence, UTF-8 encoded */ + u8 enc; /* Text encoding handled by xCmp() */ + u8 type; /* One of the SQLITE_COLL_... values below */ + void *pUser; /* First argument to xCmp() */ + int (*xCmp)(void*,int, const void*, int, const void*); + void (*xDel)(void*); /* Destructor for pUser */ +}; + +/* +** Allowed values of CollSeq flags: +*/ +#define SQLITE_COLL_BINARY 1 /* The default memcmp() collating sequence */ +#define SQLITE_COLL_NOCASE 2 /* The built-in NOCASE collating sequence */ +#define SQLITE_COLL_REVERSE 3 /* The built-in REVERSE collating sequence */ +#define SQLITE_COLL_USER 0 /* Any other user-defined collating sequence */ + +/* +** A sort order can be either ASC or DESC. +*/ +#define SQLITE_SO_ASC 0 /* Sort in ascending order */ +#define SQLITE_SO_DESC 1 /* Sort in ascending order */ + +/* +** Column affinity types. +** +** These used to have mnemonic name like 'i' for SQLITE_AFF_INTEGER and +** 't' for SQLITE_AFF_TEXT. But we can save a little space and improve +** the speed a little by number the values consecutively. +** +** But rather than start with 0 or 1, we begin with 'a'. That way, +** when multiple affinity types are concatenated into a string and +** used as the P4 operand, they will be more readable. +** +** Note also that the numeric types are grouped together so that testing +** for a numeric type is a single comparison. +*/ +#define SQLITE_AFF_TEXT 'a' +#define SQLITE_AFF_NONE 'b' +#define SQLITE_AFF_NUMERIC 'c' +#define SQLITE_AFF_INTEGER 'd' +#define SQLITE_AFF_REAL 'e' + +#define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC) + +/* +** The SQLITE_AFF_MASK values masks off the significant bits of an +** affinity value. +*/ +#define SQLITE_AFF_MASK 0x67 + +/* +** Additional bit values that can be ORed with an affinity without +** changing the affinity. +*/ +#define SQLITE_JUMPIFNULL 0x08 /* jumps if either operand is NULL */ +#define SQLITE_NULLEQUAL 0x10 /* compare NULLs equal */ +#define SQLITE_STOREP2 0x80 /* Store result in reg[P2] rather than jump */ + +/* +** Each SQL table is represented in memory by an instance of the +** following structure. +** +** Table.zName is the name of the table. The case of the original +** CREATE TABLE statement is stored, but case is not significant for +** comparisons. +** +** Table.nCol is the number of columns in this table. Table.aCol is a +** pointer to an array of Column structures, one for each column. +** +** If the table has an INTEGER PRIMARY KEY, then Table.iPKey is the index of +** the column that is that key. Otherwise Table.iPKey is negative. Note +** that the datatype of the PRIMARY KEY must be INTEGER for this field to +** be set. An INTEGER PRIMARY KEY is used as the rowid for each row of +** the table. If a table has no INTEGER PRIMARY KEY, then a random rowid +** is generated for each row of the table. Table.hasPrimKey is true if +** the table has any PRIMARY KEY, INTEGER or otherwise. +** +** Table.tnum is the page number for the root BTree page of the table in the +** database file. If Table.iDb is the index of the database table backend +** in sqlite.aDb[]. 0 is for the main database and 1 is for the file that +** holds temporary tables and indices. If Table.isEphem +** is true, then the table is stored in a file that is automatically deleted +** when the VDBE cursor to the table is closed. In this case Table.tnum +** refers VDBE cursor number that holds the table open, not to the root +** page number. Transient tables are used to hold the results of a +** sub-query that appears instead of a real table name in the FROM clause +** of a SELECT statement. +*/ +struct Table { + char *zName; /* Name of the table */ + int nCol; /* Number of columns in this table */ + Column *aCol; /* Information about each column */ + int iPKey; /* If not less then 0, use aCol[iPKey] as the primary key */ + Index *pIndex; /* List of SQL indexes on this table. */ + int tnum; /* Root BTree node for this table (see note above) */ + Select *pSelect; /* NULL for tables. Points to definition if a view. */ + int nRef; /* Number of pointers to this Table */ + Trigger *pTrigger; /* List of SQL triggers on this table */ + FKey *pFKey; /* Linked list of all foreign keys in this table */ + char *zColAff; /* String defining the affinity of each column */ +#ifndef SQLITE_OMIT_CHECK + Expr *pCheck; /* The AND of all CHECK constraints */ +#endif +#ifndef SQLITE_OMIT_ALTERTABLE + int addColOffset; /* Offset in CREATE TABLE statement to add a new column */ +#endif + u8 readOnly; /* True if this table should not be written by the user */ + u8 isEphem; /* True if created using OP_OpenEphermeral */ + u8 hasPrimKey; /* True if there exists a primary key */ + u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ + u8 autoInc; /* True if the integer primary key is autoincrement */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + u8 isVirtual; /* True if this is a virtual table */ + u8 isCommit; /* True once the CREATE TABLE has been committed */ + Module *pMod; /* Pointer to the implementation of the module */ + sqlite3_vtab *pVtab; /* Pointer to the module instance */ + int nModuleArg; /* Number of arguments to the module */ + char **azModuleArg; /* Text of all module args. [0] is module name */ +#endif + Schema *pSchema; /* Schema that contains this table */ +}; + +/* +** Test to see whether or not a table is a virtual table. This is +** done as a macro so that it will be optimized out when virtual +** table support is omitted from the build. +*/ +#ifndef SQLITE_OMIT_VIRTUALTABLE +# define IsVirtual(X) ((X)->isVirtual) +# define IsHiddenColumn(X) ((X)->isHidden) +#else +# define IsVirtual(X) 0 +# define IsHiddenColumn(X) 0 +#endif + +/* +** Each foreign key constraint is an instance of the following structure. +** +** A foreign key is associated with two tables. The "from" table is +** the table that contains the REFERENCES clause that creates the foreign +** key. The "to" table is the table that is named in the REFERENCES clause. +** Consider this example: +** +** CREATE TABLE ex1( +** a INTEGER PRIMARY KEY, +** b INTEGER CONSTRAINT fk1 REFERENCES ex2(x) +** ); +** +** For foreign key "fk1", the from-table is "ex1" and the to-table is "ex2". +** +** Each REFERENCES clause generates an instance of the following structure +** which is attached to the from-table. The to-table need not exist when +** the from-table is created. The existance of the to-table is not checked +** until an attempt is made to insert data into the from-table. +** +** The sqlite.aFKey hash table stores pointers to this structure +** given the name of a to-table. For each to-table, all foreign keys +** associated with that table are on a linked list using the FKey.pNextTo +** field. +*/ +struct FKey { + Table *pFrom; /* The table that constains the REFERENCES clause */ + FKey *pNextFrom; /* Next foreign key in pFrom */ + char *zTo; /* Name of table that the key points to */ + FKey *pNextTo; /* Next foreign key that points to zTo */ + int nCol; /* Number of columns in this key */ + struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ + int iFrom; /* Index of column in pFrom */ + char *zCol; /* Name of column in zTo. If 0 use PRIMARY KEY */ + } *aCol; /* One entry for each of nCol column s */ + u8 isDeferred; /* True if constraint checking is deferred till COMMIT */ + u8 updateConf; /* How to resolve conflicts that occur on UPDATE */ + u8 deleteConf; /* How to resolve conflicts that occur on DELETE */ + u8 insertConf; /* How to resolve conflicts that occur on INSERT */ +}; + +/* +** SQLite supports many different ways to resolve a constraint +** error. ROLLBACK processing means that a constraint violation +** causes the operation in process to fail and for the current transaction +** to be rolled back. ABORT processing means the operation in process +** fails and any prior changes from that one operation are backed out, +** but the transaction is not rolled back. FAIL processing means that +** the operation in progress stops and returns an error code. But prior +** changes due to the same operation are not backed out and no rollback +** occurs. IGNORE means that the particular row that caused the constraint +** error is not inserted or updated. Processing continues and no error +** is returned. REPLACE means that preexisting database rows that caused +** a UNIQUE constraint violation are removed so that the new insert or +** update can proceed. Processing continues and no error is reported. +** +** RESTRICT, SETNULL, and CASCADE actions apply only to foreign keys. +** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the +** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign +** key is set to NULL. CASCADE means that a DELETE or UPDATE of the +** referenced table row is propagated into the row that holds the +** foreign key. +** +** The following symbolic values are used to record which type +** of action to take. +*/ +#define OE_None 0 /* There is no constraint to check */ +#define OE_Rollback 1 /* Fail the operation and rollback the transaction */ +#define OE_Abort 2 /* Back out changes but do no rollback transaction */ +#define OE_Fail 3 /* Stop the operation but leave all prior changes */ +#define OE_Ignore 4 /* Ignore the error. Do not do the INSERT or UPDATE */ +#define OE_Replace 5 /* Delete existing record, then do INSERT or UPDATE */ + +#define OE_Restrict 6 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */ +#define OE_SetNull 7 /* Set the foreign key value to NULL */ +#define OE_SetDflt 8 /* Set the foreign key value to its default */ +#define OE_Cascade 9 /* Cascade the changes */ + +#define OE_Default 99 /* Do whatever the default action is */ + + +/* +** An instance of the following structure is passed as the first +** argument to sqlite3VdbeKeyCompare and is used to control the +** comparison of the two index keys. +** +** If the KeyInfo.incrKey value is true and the comparison would +** otherwise be equal, then return a result as if the second key +** were larger. +*/ +struct KeyInfo { + sqlite3 *db; /* The database connection */ + u8 enc; /* Text encoding - one of the TEXT_Utf* values */ + u8 incrKey; /* Increase 2nd key by epsilon before comparison */ + u8 prefixIsEqual; /* Treat a prefix as equal */ + int nField; /* Number of entries in aColl[] */ + u8 *aSortOrder; /* If defined an aSortOrder[i] is true, sort DESC */ + CollSeq *aColl[1]; /* Collating sequence for each term of the key */ +}; + +/* +** Each SQL index is represented in memory by an +** instance of the following structure. +** +** The columns of the table that are to be indexed are described +** by the aiColumn[] field of this structure. For example, suppose +** we have the following table and index: +** +** CREATE TABLE Ex1(c1 int, c2 int, c3 text); +** CREATE INDEX Ex2 ON Ex1(c3,c1); +** +** In the Table structure describing Ex1, nCol==3 because there are +** three columns in the table. In the Index structure describing +** Ex2, nColumn==2 since 2 of the 3 columns of Ex1 are indexed. +** The value of aiColumn is {2, 0}. aiColumn[0]==2 because the +** first column to be indexed (c3) has an index of 2 in Ex1.aCol[]. +** The second column to be indexed (c1) has an index of 0 in +** Ex1.aCol[], hence Ex2.aiColumn[1]==0. +** +** The Index.onError field determines whether or not the indexed columns +** must be unique and what to do if they are not. When Index.onError=OE_None, +** it means this is not a unique index. Otherwise it is a unique index +** and the value of Index.onError indicate the which conflict resolution +** algorithm to employ whenever an attempt is made to insert a non-unique +** element. +*/ +struct Index { + char *zName; /* Name of this index */ + int nColumn; /* Number of columns in the table used by this index */ + int *aiColumn; /* Which columns are used by this index. 1st is 0 */ + unsigned *aiRowEst; /* Result of ANALYZE: Est. rows selected by each column */ + Table *pTable; /* The SQL table being indexed */ + int tnum; /* Page containing root of this index in database file */ + u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ + u8 autoIndex; /* True if is automatically created (ex: by UNIQUE) */ + char *zColAff; /* String defining the affinity of each column */ + Index *pNext; /* The next index associated with the same table */ + Schema *pSchema; /* Schema containing this index */ + u8 *aSortOrder; /* Array of size Index.nColumn. True==DESC, False==ASC */ + char **azColl; /* Array of collation sequence names for index */ +}; + +/* +** Each token coming out of the lexer is an instance of +** this structure. Tokens are also used as part of an expression. +** +** Note if Token.z==0 then Token.dyn and Token.n are undefined and +** may contain random values. Do not make any assuptions about Token.dyn +** and Token.n when Token.z==0. +*/ +struct Token { + const unsigned char *z; /* Text of the token. Not NULL-terminated! */ + unsigned dyn : 1; /* True for malloced memory, false for static */ + unsigned n : 31; /* Number of characters in this token */ +}; + +/* +** An instance of this structure contains information needed to generate +** code for a SELECT that contains aggregate functions. +** +** If Expr.op==TK_AGG_COLUMN or TK_AGG_FUNCTION then Expr.pAggInfo is a +** pointer to this structure. The Expr.iColumn field is the index in +** AggInfo.aCol[] or AggInfo.aFunc[] of information needed to generate +** code for that node. +** +** AggInfo.pGroupBy and AggInfo.aFunc.pExpr point to fields within the +** original Select structure that describes the SELECT statement. These +** fields do not need to be freed when deallocating the AggInfo structure. +*/ +struct AggInfo { + u8 directMode; /* Direct rendering mode means take data directly + ** from source tables rather than from accumulators */ + u8 useSortingIdx; /* In direct mode, reference the sorting index rather + ** than the source table */ + int sortingIdx; /* Cursor number of the sorting index */ + ExprList *pGroupBy; /* The group by clause */ + int nSortingColumn; /* Number of columns in the sorting index */ + struct AggInfo_col { /* For each column used in source tables */ + Table *pTab; /* Source table */ + int iTable; /* Cursor number of the source table */ + int iColumn; /* Column number within the source table */ + int iSorterColumn; /* Column number in the sorting index */ + int iMem; /* Memory location that acts as accumulator */ + Expr *pExpr; /* The original expression */ + } *aCol; + int nColumn; /* Number of used entries in aCol[] */ + int nColumnAlloc; /* Number of slots allocated for aCol[] */ + int nAccumulator; /* Number of columns that show through to the output. + ** Additional columns are used only as parameters to + ** aggregate functions */ + struct AggInfo_func { /* For each aggregate function */ + Expr *pExpr; /* Expression encoding the function */ + FuncDef *pFunc; /* The aggregate function implementation */ + int iMem; /* Memory location that acts as accumulator */ + int iDistinct; /* Ephermeral table used to enforce DISTINCT */ + } *aFunc; + int nFunc; /* Number of entries in aFunc[] */ + int nFuncAlloc; /* Number of slots allocated for aFunc[] */ +}; + +/* +** Each node of an expression in the parse tree is an instance +** of this structure. +** +** Expr.op is the opcode. The integer parser token codes are reused +** as opcodes here. For example, the parser defines TK_GE to be an integer +** code representing the ">=" operator. This same integer code is reused +** to represent the greater-than-or-equal-to operator in the expression +** tree. +** +** Expr.pRight and Expr.pLeft are subexpressions. Expr.pList is a list +** of argument if the expression is a function. +** +** Expr.token is the operator token for this node. For some expressions +** that have subexpressions, Expr.token can be the complete text that gave +** rise to the Expr. In the latter case, the token is marked as being +** a compound token. +** +** An expression of the form ID or ID.ID refers to a column in a table. +** For such expressions, Expr.op is set to TK_COLUMN and Expr.iTable is +** the integer cursor number of a VDBE cursor pointing to that table and +** Expr.iColumn is the column number for the specific column. If the +** expression is used as a result in an aggregate SELECT, then the +** value is also stored in the Expr.iAgg column in the aggregate so that +** it can be accessed after all aggregates are computed. +** +** If the expression is a function, the Expr.iTable is an integer code +** representing which function. If the expression is an unbound variable +** marker (a question mark character '?' in the original SQL) then the +** Expr.iTable holds the index number for that variable. +** +** If the expression is a subquery then Expr.iColumn holds an integer +** register number containing the result of the subquery. If the +** subquery gives a constant result, then iTable is -1. If the subquery +** gives a different answer at different times during statement processing +** then iTable is the address of a subroutine that computes the subquery. +** +** The Expr.pSelect field points to a SELECT statement. The SELECT might +** be the right operand of an IN operator. Or, if a scalar SELECT appears +** in an expression the opcode is TK_SELECT and Expr.pSelect is the only +** operand. +** +** If the Expr is of type OP_Column, and the table it is selecting from +** is a disk table or the "old.*" pseudo-table, then pTab points to the +** corresponding table definition. +*/ +struct Expr { + u8 op; /* Operation performed by this node */ + char affinity; /* The affinity of the column or 0 if not a column */ + u16 flags; /* Various flags. See below */ + CollSeq *pColl; /* The collation type of the column or 0 */ + Expr *pLeft, *pRight; /* Left and right subnodes */ + ExprList *pList; /* A list of expressions used as function arguments + ** or in " IN (aCol[] or ->aFunc[] */ + int iRightJoinTable; /* If EP_FromJoin, the right table of the join */ + Select *pSelect; /* When the expression is a sub-select. Also the + ** right side of " IN (